diff --git a/app/controllers/foreman_azure_rm/concerns/hosts_controller_extensions.rb b/app/controllers/foreman_azure_rm/concerns/hosts_controller_extensions.rb index 666492f..bf294b0 100644 --- a/app/controllers/foreman_azure_rm/concerns/hosts_controller_extensions.rb +++ b/app/controllers/foreman_azure_rm/concerns/hosts_controller_extensions.rb @@ -1,11 +1,12 @@ module ForemanAzureRM module Concerns module HostsControllerExtensions + extend ActiveSupport::Concern def sizes - if (azure_rm_resource = Image.unscoped.find_by_uuid(params[:image_id])).present? - resource = azure_rm_resource.compute_resource - render :json => resource.vm_sizes(params[:region_string]).map { |size| size.name } + azure_rm_resource = ComputeResource.unscoped.find_by_id(params[:compute_resource_id]) + if azure_rm_resource.present? + render :json => azure_rm_resource.vm_sizes.map { |size| size.name } else no_sizes = _('The region you selected has no sizes associated with it') render :json => "[\"#{no_sizes}\"]" @@ -13,10 +14,9 @@ def sizes end def subnets - azure_rm_image = Image.unscoped.find_by_uuid(params[:image_id]) - if azure_rm_image.present? - azure_rm_resource = azure_rm_image.compute_resource - subnets = azure_rm_resource.subnets(params[:region]) + azure_rm_resource = ComputeResource.unscoped.find_by_id(params[:compute_resource_id]) + if azure_rm_resource.present? + subnets = azure_rm_resource.subnets if subnets.present? render :json => subnets else diff --git a/app/models/concerns/foreman_azure_rm/vm_extensions/managed_vm.rb b/app/models/concerns/foreman_azure_rm/vm_extensions/managed_vm.rb index 1e5fd4c..29ae521 100644 --- a/app/models/concerns/foreman_azure_rm/vm_extensions/managed_vm.rb +++ b/app/models/concerns/foreman_azure_rm/vm_extensions/managed_vm.rb @@ -35,7 +35,7 @@ def define_managed_storage_profile(vm_name, vhd_path, publisher, offer, sku, ver os_disk.managed_disk = managed_disk_params storage_profile.os_disk = os_disk - # Currently eliminating data disk creation since capability does not exist. + # TODO - disk creation for volume capability if vhd_path.nil? # We are using a marketplace image @@ -72,45 +72,44 @@ def define_network_profile(network_interface_card_ids) network_profile end - def create_nics(args = {}) + def create_nics(region, args = {}) nics = [] - formatted_region = args[:azure_vm][:location].gsub(/\s+/, '').downcase args[:interfaces_attributes].each do |nic, attrs| - attrs[:pubip_alloc] = attrs[:bridge] - attrs[:privip_alloc] = (attrs[:name] == 'false') ? false : true - pip_alloc = case attrs[:pubip_alloc] - when 'Static' - NetworkModels::IPAllocationMethod::Static - when 'Dynamic' - NetworkModels::IPAllocationMethod::Dynamic - when 'None' - nil - end - priv_ip_alloc = if attrs[:priv_ip_alloc] - NetworkModels::IPAllocationMethod::Static - else - NetworkModels::IPAllocationMethod::Dynamic - end - if pip_alloc.present? + private_ip = (attrs[:private_ip] == 'false') ? false : true + priv_ip_alloc = if private_ip + NetworkModels::IPAllocationMethod::Static + else + NetworkModels::IPAllocationMethod::Dynamic + end + pub_ip_alloc = case attrs[:public_ip] + when 'Static' + NetworkModels::IPAllocationMethod::Static + when 'Dynamic' + NetworkModels::IPAllocationMethod::Dynamic + when 'None' + nil + end + if pub_ip_alloc.present? public_ip_params = NetworkModels::PublicIPAddress.new.tap do |ip| - ip.location = formatted_region - ip.public_ipallocation_method = pip_alloc + ip.location = region + ip.public_ipallocation_method = pub_ip_alloc end - pip = sdk.create_or_update_pip(args[:azure_vm][:resource_group], - "#{args[:azure_vm][:vm_name]}-pip#{nic}", + + pip = sdk.create_or_update_pip(args[:resource_group], + "#{args[:vm_name]}-pip#{nic}", public_ip_params) end new_nic = sdk.create_or_update_nic( - args[:azure_vm][:resource_group], - "#{args[:azure_vm][:vm_name]}-nic#{nic}", + args[:resource_group], + "#{args[:vm_name]}-nic#{nic}", NetworkModels::NetworkInterface.new.tap do |interface| - interface.location = formatted_region + interface.location = region interface.ip_configurations = [ NetworkModels::NetworkInterfaceIPConfiguration.new.tap do |nic_conf| - nic_conf.name = "#{args[:azure_vm][:vm_name]}-nic#{nic}" + nic_conf.name = "#{args[:vm_name]}-nic#{nic}" nic_conf.private_ipallocation_method = priv_ip_alloc - nic_conf.subnet = subnets(args[:azure_vm][:location]).select{ |subnet| subnet.id == attrs[:network] }.first - nic_conf.public_ipaddress = pip.present? ? pip : nil + nic_conf.subnet = subnets.select{ |subnet| subnet.id == attrs[:network] }.first + nic_conf.public_ipaddress = pip end ] end @@ -120,7 +119,7 @@ def create_nics(args = {}) nics end - def create_managed_virtual_machine(vm_hash) + def initialize_vm(vm_hash) custom_data = vm_hash[:custom_data] msg = "Creating Virtual Machine #{vm_hash[:name]} in Resource Group #{vm_hash[:resource_group]}." logger.debug msg @@ -183,30 +182,34 @@ def create_managed_virtual_machine(vm_hash) vm.hardware_profile = ComputeModels::HardwareProfile.new.tap do |hw_profile| hw_profile.vm_size = vm_hash[:vm_size] end - vm.network_profile = define_network_profile(vm_hash[:network_interface_card_ids]) end - response = sdk.create_or_update_vm(vm_hash[:resource_group], vm_hash[:name], vm_create_params) - logger.debug "Virtual Machine #{vm_hash[:name]} Created Successfully." - response + vm_create_params + end + + def create_managed_virtual_machine(vm_hash) + vm_params = initialize_vm(vm_hash) + vm_params.network_profile = define_network_profile(vm_hash[:network_interface_card_ids]) + sdk.create_or_update_vm(vm_hash[:resource_group], vm_hash[:name], vm_params) end - def create_vm_extension(args = {}) - if args[:azure_vm][:script_command].present? || args[:azure_vm][:script_uris].present? + def create_vm_extension(region, args = {}) + if args[:script_command].present? || args[:script_uris].present? + args[:script_uris] ||= args[:script_uris].to_s extension = ComputeModels::VirtualMachineExtension.new - if args[:azure_vm][:platform] == 'Linux' + if args[:platform] == 'Linux' extension.publisher = 'Microsoft.Azure.Extensions' extension.virtual_machine_extension_type = 'CustomScript' extension.type_handler_version = '2.0' end extension.auto_upgrade_minor_version = true - extension.location = args[:azure_vm][:location].gsub(/\s+/, '').downcase + extension.location = region extension.settings = { - 'commandToExecute' => args[:azure_vm][:script_command], - 'fileUris' => args[:azure_vm][:script_uris].split(',') + 'commandToExecute' => args[:script_command], + 'fileUris' => args[:script_uris].split(',') } - sdk.create_or_update_vm_extensions(args[:azure_vm][:resource_group], - args[:azure_vm][:vm_name], + sdk.create_or_update_vm_extensions(args[:resource_group], + args[:vm_name], 'ForemanCustomScript', extension) end diff --git a/app/models/foreman_azure_rm/azure_rm.rb b/app/models/foreman_azure_rm/azure_rm.rb index 036b5cc..162a2e2 100644 --- a/app/models/foreman_azure_rm/azure_rm.rb +++ b/app/models/foreman_azure_rm/azure_rm.rb @@ -1,6 +1,6 @@ # This Model contains code modified as per azure-sdk # and removed dependencies from fog-azure-rm. -# + require 'base64' module ForemanAzureRM @@ -9,13 +9,10 @@ class AzureRM < ComputeResource include VMExtensions::ManagedVM alias_attribute :sub_id, :user alias_attribute :secret_key, :password - alias_attribute :app_ident, :url + alias_attribute :region, :url alias_attribute :tenant, :uuid - validates :user, :presence => true - validates :password, :presence => true - validates :url, :presence => true - validates :uuid, :presence => true + validates :user, :password, :url, :uuid, :app_ident, :presence => true has_one :key_pair, :foreign_key => :compute_resource_id, :dependent => :destroy @@ -23,6 +20,7 @@ class AzureRM < ComputeResource class VMContainer attr_accessor :virtualmachines + delegate :each, to: :virtualmachines def initialize @virtualmachines = [] @@ -33,6 +31,14 @@ def all(_options = {}) end end + def app_ident + attrs[:app_ident] + end + + def app_ident=(name) + attrs[:app_ident] = name + end + def sdk @sdk ||= ForemanAzureRM::AzureSDKAdapter.new(tenant, app_ident, secret_key, sub_id) end @@ -45,7 +51,7 @@ def self.model_name ComputeResource.model_name end - def provider_friendly_name + def self.provider_friendly_name 'Azure Resource Manager' end @@ -55,33 +61,18 @@ def capabilities def regions [ - 'West Europe', - 'Central US', - 'South Central US', - 'North Central US', - 'West Central US', - 'East US', - 'East US 2', - 'West US', - 'West US 2' + ['West Europe', 'westeurope'], + ['Central US', 'centralus'], + ['South Central US', 'southcentralus'], + ['North Central US', 'northcentralus'], + ['West Central US', 'westcentralus'], + ['East US', 'eastus'], + ['East US 2', 'eastus2'], + ['West US', 'westus'], + ['West US 2', 'westus2'] ] end - def storage_accts(region = nil) - stripped_region = region.gsub(/\s+/, '').downcase - acct_names = [] - if region.nil? - sdk.get_storage_accts.each do |acct| - acct_names << acct.name - end - else - (sdk.get_storage_accts.select { |acct| acct.region == stripped_region }).each do |acct| - acct_names << acct.name - end - end - acct_names - end - def resource_groups sdk.rgs end @@ -93,20 +84,39 @@ def test_connection(options = {}) super(options) end - def new_vm(attr = {}) - AzureRMCompute.new(sdk: sdk) + def new_vm(args = {}) + return AzureRMCompute.new(sdk: sdk) if args.empty? || args[:image_id].nil? + opts = vm_instance_defaults.merge(args.to_h).deep_symbolize_keys + # convert rails nested_attributes into a plain hash + nested_args = opts.delete(:interfaces_attributes) + opts[:interfaces] = nested_attributes_for(:interfaces, nested_args) if nested_args + + opts.reject! { |k, v| v.nil? } + + raw_vm = initialize_vm(location: region, + resource_group: opts[:resource_group], + vm_size: opts[:vm_size], + username: opts[:username], + password: opts[:password], + platform: opts[:platform], + ssh_key_data: opts[:ssh_key_data], + os_disk_caching: opts[:os_disk_caching], + vhd_path: opts[:image_id], + premium_os_disk: opts[:premium_os_disk] + ) + if opts[:interfaces].present? + ifaces = [] + opts[:interfaces].each_with_index do |iface_attrs, i| + ifaces << new_interface(iface_attrs) + end + end + AzureRMCompute.new(azure_vm: raw_vm ,sdk: sdk, resource_group: opts[:resource_group], nics: ifaces) end def provided_attributes super.merge({ :ip => :provisioning_ip_address }) end - def host_interfaces_attrs(host) - host.interfaces.select(&:physical?).each.with_index.reduce({}) do |hash, (nic, index)| - hash.merge(index.to_s => nic.compute_attributes.merge(ip: nic.ip, ip6: nic.ip6)) - end - end - def available_vnets(attr = {}) virtual_networks end @@ -115,22 +125,12 @@ def available_networks(attr = {}) subnets end - def available_subnets - subnets - end - - def virtual_networks(region = nil) - if region.nil? - sdk.vnets - else - stripped_region = region.gsub(/\s+/, '').downcase - sdk.vnets.select { |vnet| vnet.location == stripped_region } - end + def virtual_networks + @virtual_networks ||= sdk.vnets.select { |vnet| vnet.location == region } end - def subnets(region = nil) - stripped_region = region.gsub(/\s+/, '').downcase - vnets = virtual_networks(stripped_region) + def subnets + vnets = virtual_networks subnets = [] vnets.each do |vnet| subnets.concat(sdk.subnets(vnet.resource_group, vnet.name)) @@ -138,14 +138,18 @@ def subnets(region = nil) subnets end - def new_interface(attr = {}) - # WIP - # calls nic_cards method in adapter - # causes compute profiles issue - # NetworkModels::NetworkInterface.new + alias_method :available_subnets, :subnets + + def new_interface(attrs = {}) + args = { :network => "", :public_ip => "", :private_ip => false, 'persisted?' => false }.merge(attrs.to_h) + OpenStruct.new(args) end - def vm_sizes(region) + def editable_network_interfaces? + true + end + + def vm_sizes sdk.list_vm_sizes(region) end @@ -154,16 +158,26 @@ def associated_host(vm) end def vm_instance_defaults - ActiveSupport::HashWithIndifferentAccess.new + super.deep_merge( + interfaces: [new_interface] + ) end - def vms + def vm_nics(vm) + ifaces = [] + vm.network_profile.network_interfaces.each do |nic| + nic_rg = (split_nic_id = nic.id.split('/'))[4] + nic_name = split_nic_id[-1] + ifaces << sdk.vm_nic(nic_rg, nic_name) + end + ifaces + end + + def vms(attrs = {}) container = VMContainer.new - # Load all vms - resource_groups.each do |rg| - sdk.list_vms(rg).each do |vm| - container.virtualmachines << AzureRMCompute.new(azure_vm: vm, sdk:sdk) - end + # Load all vms of the region + sdk.list_vms(region).each do |vm| + container.virtualmachines << AzureRMCompute.new(azure_vm: vm, sdk:sdk, nics: vm_nics(vm)) end container end @@ -187,44 +201,46 @@ def user_data_supported? end def create_vm(args = {}) - args[:azure_vm][:vm_name] = args[:name].split('.')[0] - nics = create_nics(args) - if args[:azure_vm][:password].present? && !args[:azure_vm][:ssh_key_data].present? - sudoers_cmd = "$echo #{args[:azure_vm][:password]} | sudo -S echo '\"#{args[:azure_vm][:username]}\" ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/waagent" - if args[:azure_vm][:script_command].present? - # to run the script_cmd given through form - # as username - args[:azure_vm][:script_command] = sudoers_cmd + " ; su - \"#{args[:azure_vm][:username]}\" -c \"#{args[:azure_vm][:script_command]}\"" + args = args.to_h.deep_symbolize_keys + args[:vm_name] = args[:name].split('.')[0] + nics = create_nics(region, args) + if args[:password].present? && !args[:ssh_key_data].present? + sudoers_cmd = "$echo #{args[:password]} | sudo -S echo '\"#{args[:username]}\" ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/waagent" + if args[:script_command].present? + # to run the script_cmd given through form as username + args[:script_command] = sudoers_cmd + " ; su - \"#{args[:username]}\" -c \"#{args[:script_command]}\"" else - args[:azure_vm][:script_command] = sudoers_cmd + args[:script_command] = sudoers_cmd end disable_password_auth = false - elsif args[:azure_vm][:ssh_key_data].present? && !args[:azure_vm][:password].present? + elsif args[:ssh_key_data].present? && !args[:password].present? disable_password_auth = true else disable_password_auth = false end + vm = create_managed_virtual_machine( - name: args[:azure_vm][:vm_name], - location: args[:azure_vm][:location], - resource_group: args[:azure_vm][:resource_group], - vm_size: args[:azure_vm][:vm_size], - username: args[:azure_vm][:username], - password: args[:azure_vm][:password], - ssh_key_data: args[:azure_vm][:ssh_key_data], + name: args[:vm_name], + location: region, + resource_group: args[:resource_group], + vm_size: args[:vm_size], + username: args[:username], + password: args[:password], + ssh_key_data: args[:ssh_key_data], disable_password_authentication: disable_password_auth, network_interface_card_ids: nics.map(&:id), - platform: args[:azure_vm][:platform], + platform: args[:platform], vhd_path: args[:image_id], - os_disk_caching: args[:azure_vm][:os_disk_caching], - premium_os_disk: args[:azure_vm][:premium_os_disk], + os_disk_caching: args[:os_disk_caching], + premium_os_disk: args[:premium_os_disk], custom_data: args[:user_data], - script_command: args[:azure_vm][:script_command], - script_uris: args[:azure_vm][:script_uris], + script_command: args[:script_command], + script_uris: args[:script_uris], ) - create_vm_extension(args) + logger.debug "Virtual Machine #{args[:vm_name]} Created Successfully." + create_vm_extension(region, args) # return the vm object using azure_vm - return_vm = AzureRMCompute.new(azure_vm: vm, sdk: sdk) + AzureRMCompute.new(azure_vm: vm, sdk: sdk, resource_group: args[:resource_group], nics: vm_nics(vm)) rescue RuntimeError => e Foreman::Logging.exception('Unhandled Azure RM error', e) destroy_vm vm.id if vm @@ -232,15 +248,13 @@ def create_vm(args = {}) end def destroy_vm(uuid) - #vm.azure_vm because that's the azure object and vm is the wrapper vm = find_vm_by_uuid(uuid) - vm_name = vm.name - rg_name = vm.azure_vm.resource_group + rg_name = vm.resource_group os_disk = vm.azure_vm.storage_profile.os_disk data_disks = vm.azure_vm.storage_profile.data_disks nic_ids = vm.network_interface_card_ids - sdk.delete_vm(rg_name, vm_name) + sdk.delete_vm(rg_name, vm.name) nic_ids.each do |nic_id| nic = sdk.vm_nic(rg_name, nic_id.split('/')[-1]) diff --git a/app/models/foreman_azure_rm/azure_rm_compute.rb b/app/models/foreman_azure_rm/azure_rm_compute.rb index 4048475..ded7f05 100644 --- a/app/models/foreman_azure_rm/azure_rm_compute.rb +++ b/app/models/foreman_azure_rm/azure_rm_compute.rb @@ -2,12 +2,29 @@ module ForemanAzureRM class AzureRMCompute attr_accessor :sdk attr_accessor :azure_vm + attr_accessor :resource_group + attr_accessor :nics + attr_accessor :image_id delegate :name, to: :azure_vm, allow_nil: true - def initialize(azure_vm: ComputeModels::VirtualMachine.new, sdk: sdk) + def initialize(azure_vm: ComputeModels::VirtualMachine.new, + sdk: sdk, + resource_group: azure_vm.resource_group, + nics: []) + @azure_vm = azure_vm @sdk = sdk + @resource_group ||= resource_group + @nics ||= nics + @azure_vm.hardware_profile ||= ComputeModels::HardwareProfile.new + @azure_vm.os_profile ||= ComputeModels::OSProfile.new + @azure_vm.os_profile.linux_configuration ||= ComputeModels::LinuxConfiguration.new + @azure_vm.os_profile.linux_configuration.ssh ||= ComputeModels::SshConfiguration.new + @azure_vm.os_profile.linux_configuration.ssh.public_keys ||= [ComputeModels::SshPublicKey.new] + @azure_vm.storage_profile ||= ComputeModels::StorageProfile.new + @azure_vm.storage_profile.os_disk ||= ComputeModels::OSDisk.new + @azure_vm.storage_profile.os_disk.managed_disk ||= ComputeModels::ManagedDiskParameters.new end def id @@ -18,10 +35,6 @@ def persisted? !!identity && !!id end - def vm_size - @azure_vm.hardware_profile.vm_size - end - def wait_for(_timeout = 0, _interval = 0, &block) instance_eval(&block) return true @@ -37,10 +50,12 @@ def state def start sdk.start_vm(@azure_vm.resource_group, name) + true end def stop sdk.stop_vm(@azure_vm.resource_group, name) + true end def vm_status @@ -48,41 +63,45 @@ def vm_status end def network_interface_card_ids + return nil unless @azure_vm.network_profile nics = @azure_vm.network_profile.network_interfaces nics.map(&:id) end def provisioning_ip_address + public_ip_address || private_ip_address + end + + def public_ip_address interfaces.each do |nic| nic.ip_configurations.each do |configuration| next unless configuration.primary - if configuration.public_ipaddress.present? - ip_id = configuration.public_ipaddress.id - ip_rg = ip_id.split('/')[4] - ip_name = ip_id.split('/')[-1] - public_ip = sdk.public_ip(ip_rg, ip_name) - return public_ip.ip_address - else - return configuration.private_ipaddress - end + return nil if configuration.public_ipaddress.blank? + ip_id = configuration.public_ipaddress.id + ip_rg = ip_id.split('/')[4] + ip_name = ip_id.split('/')[-1] + public_ip = sdk.public_ip(ip_rg, ip_name) + return public_ip.ip_address end - end + end end - def interfaces - interfaces = [] - unless network_interface_card_ids.nil? - network_interface_card_ids.each do |nic_id| - nic_rg = nic_id.split('/')[4] - nic_name = nic_id.split('/')[-1] - interfaces << sdk.vm_nic(nic_rg, nic_name) + def private_ip_address + interfaces.each do |nic| + nic.ip_configurations.each do |configuration| + next unless configuration.primary + if configuration.private_ipaddress.present? + return private_ip_address = configuration.private_ipaddress + end end end - interfaces end - def interfaces=(setifaces) - @azure_vm.network_profile.network_interfaces = setifaces + def interfaces + nics + end + + def interfaces_attributes=(attrs) end def ip_addresses @@ -97,7 +116,45 @@ def identity=(setuuid) @azure_vm.name = setuuid end - def image_id + # Following properties are for AzureRM + # These are not part of Foreman's interface + + def vm_size + @azure_vm.hardware_profile.vm_size + end + + def platform + @azure_vm.storage_profile.os_disk.os_type + end + + def username + @azure_vm.os_profile.admin_username + end + + def password + @azure_vm.os_profile.admin_password + end + + def ssh_key_data + # since you can only give one additional + # sshkey through foreman's UI + sshkey = @azure_vm.os_profile.linux_configuration.ssh.public_keys[1] + return unless sshkey.present? + sshkey.key_data + end + + def premium_os_disk + @azure_vm.storage_profile.os_disk.managed_disk.storage_account_type + end + + def os_disk_caching + @azure_vm.storage_profile.os_disk.caching + end + + def script_command + end + + def script_uris end end end diff --git a/app/views/api/v2/compute_resources/azure_rm.json.rabl b/app/views/api/v2/compute_resources/azure_rm.json.rabl deleted file mode 100644 index e8c1c0f..0000000 --- a/app/views/api/v2/compute_resources/azure_rm.json.rabl +++ /dev/null @@ -1 +0,0 @@ -attributes :user, :uuid diff --git a/app/views/api/v2/compute_resources/azurerm.json.rabl b/app/views/api/v2/compute_resources/azurerm.json.rabl new file mode 100644 index 0000000..b3e9ef0 --- /dev/null +++ b/app/views/api/v2/compute_resources/azurerm.json.rabl @@ -0,0 +1 @@ +attributes :tenant, :app_ident, :sub_id, :secret_key, :region diff --git a/app/views/compute_resources/form/_azurerm.html.erb b/app/views/compute_resources/form/_azurerm.html.erb index 18b4fdb..90e38a9 100644 --- a/app/views/compute_resources/form/_azurerm.html.erb +++ b/app/views/compute_resources/form/_azurerm.html.erb @@ -1,8 +1,10 @@ -<%= text_f f, :url, :label => _('Client ID'), :required => true %> +<%= text_f f, :app_ident, :label => _('Client ID'), :required => true %> <%= password_f f, :password, :label => _('Client Secret'), :keep_value => true, :required => true %> <%= text_f f, :user, :label => _('Subscription ID'), :required => true %> <%= text_f f, :uuid, :label => _('Tenant ID'), :required => true %> +<%= selectable_f(f, :url, f.object.regions, {}, {:label => _('Azure Region'), :required => true }) %> +
<%= test_connection_button_f(f, true) %>
diff --git a/app/views/compute_resources_vms/form/azurerm/_base.html.erb b/app/views/compute_resources_vms/form/azurerm/_base.html.erb index a05fa9c..b99dab7 100644 --- a/app/views/compute_resources_vms/form/azurerm/_base.html.erb +++ b/app/views/compute_resources_vms/form/azurerm/_base.html.erb @@ -2,16 +2,17 @@ os ||= nil images = possible_images(compute_resource, arch, os) resource_groups = compute_resource.resource_groups + compute_resource_id = compute_resource.id %> - -<% #This view has been modified and wraps the properties of azure_vm - # using fields_for rails method. +<% # This view has been modified and refers to the properties wrapper class %> + +<%= selectable_f f, :resource_group, resource_groups, + { :include_blank => true }, + { + :disabled => resource_groups.empty?, + :label => _('Resource Group'), + :required => true, + :id => 'azure_rm_rg', + :help_inline => spinner_button_f(f, _('Reload Images, Sizes, vNets'), + 'azure_rm_region_callback();', + { + :id => 'load_subnets_btn', + :spinner_id => 'load_subnets_indicator', + :class => 'btn-success', + :spinner_class => 'spinner-inverse' + }) + } %> -<%= f.fields_for :azure_vm do |vm_f| %> +<%= selectable_f f, :vm_size, compute_resource.vm_sizes.map { |size| size.name }, + {}, + { + :label => _('VM Size'), + :required => true, + :id => 'azure_rm_size' + } +%> - <%= selectable_f vm_f, :location, compute_resource.regions, - { :include_blank => _('Please select an Azure region') }, +<%= selectable_f f, :platform, %w(Linux Windows), + {}, { - :label => _('Azure Region'), - :required => true, - :id => 'azure_rm_region', - :label_size => "col-md-2", - :onchange => 'azure_rm_region_callback();', - :help_inline => spinner_button_f(f, _('Reload Images, Sizes, vNets'), - 'azure_rm_region_callback();', - { - :id => 'load_subnets_btn', - :spinner_id => 'load_subnets_indicator', - :class => 'btn-success', - :spinner_class => 'spinner-inverse' - }) + :label => _('Platform'), + :required => true } - %> +%> - <%= selectable_f vm_f, :resource_group, resource_groups, - { :include_blank => true }, - { - :disabled => resource_groups.empty?, - :label => _('Resource Group'), - :required => true, - :id => 'azure_rm_rg' - } - %> +<%= text_f f, :username, + { + :label => _('Username'), + :required => true + } +%> - <%= selectable_f vm_f, :vm_size, [], - { :include_blank => _('Please first select an Azure region') }, - { - :label => _('VM Size'), - :required => true, - :id => 'azure_rm_size' - } - %> +<%= password_f f, :password, + { + :label => _('Password'), + :required => true, + :placeholder => "********", + :value => f.object.password - <%= selectable_f vm_f, :platform, %w(Linux Windows), - {}, - { - :label => _('Platform'), - :required => true - } - %> + } +%> - <%= text_f vm_f, :username, - { - :label => _('Username'), - :required => true - } - %> +<%= textarea_f f, :ssh_key_data, + { + :label => _('SSH Key') + } +%> - <%= password_f vm_f, :password, - { - :label => _('Password'), - :required => true, - :placeholder => "********" - } - %> +<%= checkbox_f f, :premium_os_disk, + { :checked => f.object.premium_os_disk == "Premium_LRS", + :label => _('Premium OS Disk'), + :label_size => "col-md-2" + }, + 'true', + 'false' +%> - <%= textarea_f vm_f, :ssh_key_data, +<%= selectable_f f, :os_disk_caching, %w(None ReadOnly ReadWrite), + {}, { - :label => _('SSH Key') + :label => _('OS Disk Caching'), + :required => true, + :class => "col-md-2" } - %> - - <%= checkbox_f vm_f, :premium_os_disk, - { - :label => _('Premium OS Disk'), :label_size => "col-md-2" - }, - 'true', - 'false' - %> - - <%= selectable_f vm_f, :os_disk_caching, %w(None ReadOnly ReadWrite), - {}, - { - :label => _('OS Disk Caching'), - :required => true, - :class => "col-md-2" - } - %> - - <%= text_f vm_f, :script_command, - { - :label => _('Custom Script Command'), - :help_inline => _("To perform commands as root, prefix it with 'sudo'.") - } - %> +%> - <%= text_f vm_f, :script_uris, - { - :label => _('Comma seperated file URIs') - } - %> +<%= text_f f, :script_command, + { + :label => _('Custom Script Command'), + :help_inline => _("To perform commands as root, prefix it with 'sudo'.") + } +%> - <% checked = params[:host] && params[:host][:compute_attributes] && params[:host][:compute_attributes][:start] || '1' - %> +<%= text_f f, :script_uris, + { + :label => _('Comma seperated file URIs') + } +%> - <%= checkbox_f f, :start, { :checked => (checked == '1'), :help_inline => _("Power ON this machine upon creation"), :label => _('Start'), :label_size => "col-md-2" } if new_vm && controller_name != "compute_attributes" - %> +<% checked = params[:host] && params[:host][:compute_attributes] && params[:host][:compute_attributes][:start] || '1' +%> -<% end %> +<%= checkbox_f f, :start, { :checked => (checked == '1'), :help_inline => _("Power ON this machine upon creation"), :label => _('Start'), :label_size => "col-md-2" } if new_vm && controller_name != "compute_attributes" +%>
<%= select_f f, :image_id, images, :uuid, :name, diff --git a/app/views/compute_resources_vms/form/azurerm/_network.html.erb b/app/views/compute_resources_vms/form/azurerm/_network.html.erb index af5a91e..346dcd4 100644 --- a/app/views/compute_resources_vms/form/azurerm/_network.html.erb +++ b/app/views/compute_resources_vms/form/azurerm/_network.html.erb @@ -1,5 +1,8 @@ -<%= selectable_f f, :network, [], - { :include_blank => _('Please first select an image and an Azure region')}, +<% nat = compute_resource.subnets.collect { |s| [ "#{s.id.split('/')[8]}-#{s.name} #{s.address_prefix}", s.id ] } %> + +<%= selectable_f f, :network, options_for_select(nat, + :selected => f.object.network), + { :include_blank => _('Select') }, { :label => _('Azure Subnet'), :required => true, @@ -8,16 +11,21 @@ } %> -<%= selectable_f f, :bridge, ['None', 'Dynamic', 'Static'], +<%= selectable_f f, :public_ip, options_for_select(['None', 'Dynamic', 'Static'], + :selected => f.object.public_ip), {}, { - :label => _("Public IP"), - :label_size => "col-md-2" + :label => _("Public IP"), + :label_size => "col-md-2" } %> -<%= checkbox_f f, :name, - { :label => _("Static Private IP"), :label_size => "col-md-2" }, - 'true', - 'false' -%> \ No newline at end of file +<%= checkbox_f f, :private_ip, + { + :label => _("Static Private IP"), + :label_size => "col-md-2" + }, + 'true', + 'false' + +%> diff --git a/app/views/compute_resources_vms/index/_azurerm.html.erb b/app/views/compute_resources_vms/index/_azurerm.html.erb index cf4e1c9..e481460 100644 --- a/app/views/compute_resources_vms/index/_azurerm.html.erb +++ b/app/views/compute_resources_vms/index/_azurerm.html.erb @@ -6,14 +6,14 @@ <%= _('Resource Group') %> <%= _('Region') %> <%= _('State') %> - + <%= _('Actions') %> <% @vms.each do |vm| %> <%= link_to_if_authorized vm.name, hash_for_compute_resource_vm_path(:compute_resource_id => @compute_resource, :id => vm.identity).merge(:auth_object => @compute_resource, :authorizer => :authorizer) %> <%= vm.vm_size %> - <%= vm.azure_vm.resource_group %> + <%= vm.resource_group %> <%= vm.azure_vm.location %> > <%= vm_state(vm) %> diff --git a/app/views/images/form/_azurerm.html.erb b/app/views/images/form/_azurerm.html.erb index 3159a8a..72965a0 100644 --- a/app/views/images/form/_azurerm.html.erb +++ b/app/views/images/form/_azurerm.html.erb @@ -2,5 +2,5 @@ :help_inline => _("The user that will be used to SSH into the VM for completion") %> <%= password_f f, :password, :help_inline => _("Password to authenticate with - used for SSH finish step.") %> -<%= image_field(f, :label => _("Managed Image ID or Marketplace URN")) %> +<%= image_field(f, :label => _("Marketplace Image URN"), :help_inline => _("Marketplace URN (e.g. OpenLogic:CentOS:7.5:latest)")) %> <%= checkbox_f f, :user_data, :help_inline => _("Does this image support user data input?") %> \ No newline at end of file diff --git a/lib/foreman_azure_rm.rb b/lib/foreman_azure_rm.rb index 1d45155..4d151ae 100644 --- a/lib/foreman_azure_rm.rb +++ b/lib/foreman_azure_rm.rb @@ -1,5 +1,17 @@ require 'foreman_azure_rm/engine.rb' +require 'azure_mgmt_resources' +require 'azure_mgmt_network' +require 'azure_mgmt_storage' +require 'azure_mgmt_compute' module ForemanAzureRM + Storage = Azure::Storage::Profiles::Latest::Mgmt + Network = Azure::Network::Profiles::Latest::Mgmt + Compute = Azure::Compute::Profiles::Latest::Mgmt + Resources = Azure::Resources::Profiles::Latest::Mgmt + StorageModels = Storage::Models + NetworkModels = Network::Models + ComputeModels = Compute::Models + ResourceModels = Resources::Models end \ No newline at end of file diff --git a/lib/foreman_azure_rm/azure_sdk_adapter.rb b/lib/foreman_azure_rm/azure_sdk_adapter.rb index 0e41c77..76481bd 100644 --- a/lib/foreman_azure_rm/azure_sdk_adapter.rb +++ b/lib/foreman_azure_rm/azure_sdk_adapter.rb @@ -1,14 +1,4 @@ module ForemanAzureRM - Storage = Azure::Storage::Profiles::Latest::Mgmt - Network = Azure::Network::Profiles::Latest::Mgmt - Compute = Azure::Compute::Profiles::Latest::Mgmt - Resources = Azure::Resources::Profiles::Latest::Mgmt - - StorageModels = Storage::Models - NetworkModels = Network::Models - ComputeModels = Compute::Models - ResourceModels = Resources::Models - class AzureSDKAdapter def initialize(tenant, app_ident, secret_key, sub_id) @tenant = tenant @@ -56,8 +46,8 @@ def vnets network_client.virtual_networks.list_all end - def subnets(resource_group, vnet_name) - network_client.subnets.list(resource_group, vnet_name) + def subnets(rg_name, vnet_name) + network_client.subnets.list(rg_name, vnet_name) end def public_ip(rg_name, pip_name) @@ -68,14 +58,19 @@ def vm_nic(rg_name,nic_name) network_client.network_interfaces.get(rg_name, nic_name) end + def get_vm_extension(rg_name, vm_name, vm_extension_name) + compute_client.virtual_machine_extensions.get(rg_name, vm_name, vm_extension_name) + end + def list_vm_sizes(region) + return [] unless region.present? stripped_region = region.gsub(/\s+/, '').downcase compute_client.virtual_machine_sizes.list(stripped_region).value() end - def list_vms(rg_name) + def list_vms(region) # List all VMs in a resource group - virtual_machines = compute_client.virtual_machines.list(rg_name) + virtual_machines = compute_client.virtual_machines.list_by_location(region) end def get_vm(rg_name, vm_name) diff --git a/lib/foreman_azure_rm/engine.rb b/lib/foreman_azure_rm/engine.rb index 01806b7..59f4e11 100644 --- a/lib/foreman_azure_rm/engine.rb +++ b/lib/foreman_azure_rm/engine.rb @@ -4,13 +4,13 @@ class Engine < ::Rails::Engine #autoloading all files inside lib dir config.autoload_paths += Dir["#{config.root}/lib"] - config.autoload_paths += Dir["#{config.root}/app/models/concerns"] + config.autoload_paths += Dir["#{config.root}/app/models/concerns/foreman_azure_rm/vm_extensions/"] initializer 'foreman_azure_rm.register_plugin', :before => :finisher_hook do Foreman::Plugin.register :foreman_azure_rm do requires_foreman '>= 1.17' compute_resource ForemanAzureRM::AzureRM - parameter_filter ComputeResource, :azure_vm, :tenant, :app_ident, :secret_key, :sub_id + parameter_filter ComputeResource, :azure_vm, :tenant, :app_ident, :secret_key, :sub_id, :region end end diff --git a/test/factories/azure.rb b/test/factories/azure.rb index 289dbb7..d866bb7 100644 --- a/test/factories/azure.rb +++ b/test/factories/azure.rb @@ -1,8 +1,9 @@ FactoryBot.define do factory :azure_rm, :parent => :compute_resource, :class => ForemanAzureRM::AzureRM do - add_attribute(:user) { 'azurermuser' } - add_attribute(:password) { 'azurermpassword' } - add_attribute(:url) { 'http://azurerm.example.com' } - add_attribute(:uuid) { 'azurermuuid' } + add_attribute(:user) { '11111111-1111-1111-1111-111111111111' } + add_attribute(:password) { '22222222-2222-2222-2222-222222222222' } + add_attribute(:app_ident) { '33333333-3333-3333-3333-333333333333' } + add_attribute(:uuid) { '44444444-4444-4444-4444-444444444444' } + add_attribute(:url) { 'eastus' } end end diff --git a/test/functional/azure_rm_test.rb b/test/functional/azure_rm_test.rb index 95b5a5f..58bce0b 100644 --- a/test/functional/azure_rm_test.rb +++ b/test/functional/azure_rm_test.rb @@ -13,8 +13,8 @@ class ComputeResourcesControllerTest < ActionController::TestCase test "should return compute resource edit page" do @test_sdk.stubs(:rgs).returns(['a', 'b', 'c']) - @compute_resource = FactoryBot.create(:azure_rm) - get :edit, params: { :id => @compute_resource.to_param }, session: set_session_user + compute_resource = FactoryBot.create(:azure_rm) + get :edit, params: { :id => compute_resource.to_param }, session: set_session_user assert_response :success assert_template 'edit' end