class Chef::Provisioning::AzureRM::Driver

Attributes

region[R]
subscription[R]
subscription_id[RW]

Public Class Methods

canonicalize_url(driver_url, config) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 40
def self.canonicalize_url(driver_url, config)
  scheme, account_id = driver_url.split(':', 2)
  if account_id.nil? || account_id.empty?
    subscription = Credentials.new
    subscription.azure_credentials_for_subscription(subscription_id)
    unless subscription
      raise "Driver #{driver_url} did not specify a subscription ID, and no default subscription was found.  Have you downloaded the Azure CLI and used `azure account download` and `azure account import` to set up Azure?  Alternately, you can set azure_subscriptions to [ { subscription_id: '...', management_credentials: ... }] in your Chef configuration."
    end
    config = Cheffish::MergedConfig.new({ azure_subscriptions: subscription }, config)
  end
  if subscription
    ["#{scheme}:#{subscription[:subscription_id]}", config]
  else
    [driver_url, config]
  end
end
from_url(driver_url, config) click to toggle source

Construct an AzureDriver object from a URL - used to parse existing URL data to hydrate a driver object. URL scheme: azure:subscription_id @return [AzureDriver] A chef-provisioning Azure driver object for the given URL

# File lib/chef/provisioning/azurerm/driver.rb, line 36
def self.from_url(driver_url, config)
  Driver.new(driver_url, config)
end
new(driver_url, config) click to toggle source
Calls superclass method
# File lib/chef/provisioning/azurerm/driver.rb, line 57
def initialize(driver_url, config)
  super
  scheme, subscription_id = driver_url.split(':', 2)
  self.subscription_id = subscription_id
  @subscription = Credentials.new.azure_credentials_for_subscription(subscription_id)
  unless subscription
    raise "Driver #{driver_url} has a subscription ID, but the system has no credentials configured for it!  If you have access to this subscription, you can use `azure account download` and `azure account import` in the Azure CLI to get the credentials, or set azure_subscriptions to [ { subscription_id: '...', management_credentials: ... }] in your Chef configuration."
  end
  Chef::Config.chef_provisioning ||= {}
end

Public Instance Methods

allocate_load_balancer(action_handler, lb_spec, lb_options, machine_specs) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 357
def allocate_load_balancer(action_handler, lb_spec, lb_options, machine_specs)
  tags = lb_options[:tags]
  resource_group = lb_options[:resource_group]
  location = lb_options[:location]
  virtual_network = lb_options[:virtual_network]
  subnet_name = lb_options[:subnet_name]

  subinfo = azure_net_service.subnets.get(lb_options.resource_group, lb_options.virtual_network, lb_options.subnet_name)

  backend_pool = []

  count = backend_pool.count
  backend_pool[0] = Azure::ARM::Network::Models::BackendAddressPool.new
  backend_pool[0].name = lb_spec.name


  frontend_ipconf = Azure::ARM::Network::Models::FrontendIPConfiguration.new.tap do |feip|
    lb_options[:frontendIPConfigurations].each do |fendip|
      feip.name = fendip[:name]
      feip.subnet = subinfo
      feip.private_ipallocation_method = fendip[:private_ipallocation_method]
    end
  end

  probes = []
  lb_options[:probes].each do |prbes|
    probe = Azure::ARM::Network::Models::Probe.new.tap do |prb|
      prb.name = prbes[:name]
      prb.port = prbes[:port]
      prb.protocol = prbes[:protocol]
      if prbes[:protocol].to_s == "http"
        prb.request_path = prbes[:request_path]
      else
        prb.request_path = nil
      end
      prb.interval_in_seconds = prbes[:interval_in_seconds]
      prb.number_of_probes = prbes[:number_of_probes]
    end
    probes.push(probe)
  end

  lb = Azure::ARM::Network::Models::LoadBalancer.new.tap do |lbinfo|
    lbinfo.location = location
    lbinfo.tags = tags
    lbinfo.frontend_ipconfigurations = [frontend_ipconf]
    lbinfo.backend_address_pools = backend_pool
    lbinfo.probes = probes
  end

  lb_update(resource_group, lb_spec.name, lb) # inital build

  mylb = azure_net_service.load_balancers.get(resource_group, lb_spec.name)

  machine_specs.each do |ivm|
    vmnic = azure_net_service.network_interfaces.get(resource_group, ivm.name)
    mylb.backend_address_pools.each do |pool|
      lb_backend_pools = vmnic.ip_configurations.first.load_balancer_backend_address_pools
      if lb_backend_pools
        lb_backend_pools.each do |backend_pool|
          next if backend_pool.id.eql?(pool.id)
          vmnic.ip_configurations.first.load_balancer_backend_address_pools.push(pool)
        end
      else
        vmnic.ip_configurations.first.load_balancer_backend_address_pools = [pool]
      end
      azure_net_service.network_interfaces.create_or_update(resource_group, ivm.name, vmnic)
    end
  end
  frontend_ipconf_sub = MsRestAzure::SubResource.new.tap do |subresource|
    subresource.id = azure_net_service.load_balancers.get(resource_group, lb_spec.name).frontend_ipconfigurations.first.id
  end

  backend_ipconf_sub = MsRestAzure::SubResource.new.tap do |subresource|
    subresource.id = mylb.backend_address_pools.first.id
  end

  unless lb_options[:inboundNatRules].nil?
    inboudnats = []
    inat_machines = {}
    lb_options[:inboundNatRules].each do |ibnat|
      inboudnat = Azure::ARM::Network::Models::InboundNatRule.new.tap do |inat|
        inat_machines[ibnat[:name]] = find_vm_name(machine_specs, ibnat[:backend_ipconfiguration])
        inat.name = ibnat[:name]
        inat.frontend_port = ibnat[:frontend_port]
        inat.backend_port = ibnat[:backend_port]
        inat.protocol = ibnat[:protocol]
        inat.enable_floating_ip = ibnat[:enable_floating_ip]
        inat.idle_timeout_in_minutes = ibnat[:idle_timeout_in_minutes]
        inat.frontend_ipconfiguration = frontend_ipconf_sub
      end
      inboudnats.push(inboudnat)
    end
  end

  lbrules = []
  lb_options[:loadBalancingRules].each do |lbr|
    probe_sub = MsRestAzure::SubResource.new.tap do |subresource|
      subresource.id = find_probe_id(resource_group, lb_spec.name, lbr[:probe])
    end
    lbrule = Azure::ARM::Network::Models::LoadBalancingRule.new.tap do |lbrs|
      lbrs.name = lbr[:name]
      lbrs.backend_address_pool = backend_ipconf_sub
      lbrs.protocol = lbr[:protocol]
      lbrs.backend_port = lbr[:backend_port]
      lbrs.frontend_port = lbr[:frontend_port]
      lbrs.idle_timeout_in_minutes = lbr[:idle_timeout_in_minutes]
      lbrs.enable_floating_ip = lbr[:enable_floating_ip]
      lbrs.load_distribution = lbr[:load_distribution]
      lbrs.frontend_ipconfiguration = frontend_ipconf_sub
      lbrs.backend_address_pool = backend_ipconf_sub
      lbrs.probe = probe_sub
    end
    lbrules.push(lbrule)
  end

  lb = Azure::ARM::Network::Models::LoadBalancer.new.tap do |lbinfo|
    lbinfo.location = location
    lbinfo.tags = tags
    lbinfo.frontend_ipconfigurations = [frontend_ipconf]
    lbinfo.backend_address_pools = backend_pool
    unless lb_options[:inboundNatRules].nil?
      lbinfo.inbound_nat_rules = inboudnats
    end
    lbinfo.probes = probes
    lbinfo.load_balancing_rules = lbrules
  end

  lb_update(resource_group, lb_spec.name, lb) # update additions build
  mylb = azure_net_service.load_balancers.get(resource_group, lb_spec.name)
  if lb_options[:inboundNatRules]
    mylb.inbound_nat_rules.each do |inat|
      vm_name = inat_machines[inat.name]
      vmnic = azure_net_service.network_interfaces.get(resource_group, vm_name)
      lb_inbound_nat_rules = vmnic.ip_configurations.first.load_balancer_inbound_nat_rules
      if lb_inbound_nat_rules
        lb_inbound_nat_rules.each do |inat_rule|
          next if inat_rule.id.eql?(inat.id)
          vmnic.ip_configurations.first.load_balancer_inbound_nat_rules.push(inat)
        end
      else
        vmnic.ip_configurations.first.load_balancer_inbound_nat_rules = [inat]
      end
      azure_net_service.network_interfaces.create_or_update(resource_group, vm_name, vmnic)
    end
  end

  lb_spec.reference = {
    'frontend_ipconfigurations' => mylb.frontend_ipconfigurations.first.private_ipaddress,
    'allocated_at' => Time.now.utc.to_s,
  }
end
allocate_machine(action_handler, machine_spec, machine_options) click to toggle source

Allocate a new machine with the Azure API and start it up, without blocking to wait for it. Creates any needed resources to get a machine up and running. @param (see Chef::Provisioning::Driver#allocate_machine)

# File lib/chef/provisioning/azurerm/driver.rb, line 87
def allocate_machine(action_handler, machine_spec, machine_options)
  existing_vm = vm_for(machine_spec)

  # We don't need to do anything if the existing VM is found
  return if existing_vm

  bootstrap_options = (machine_options[:bootstrap_options] || {}).dup
  vm_name = machine_spec.name
  location = bootstrap_options[:location] || machine_options[:location]
  resource_group_name = bootstrap_options[:resource_group_name]
  availability_set = bootstrap_options[:availability_set]
  osProfile = bootstrap_options[:osProfile]
  network_security_group_name = bootstrap_options[:network_security_group_name]
  tags = bootstrap_options[:tags]
  raise 'location not provided, where the virtual machine should be created' unless location
  raise 'resource_group_name not provided, where the virtual machine should be created' unless resource_group_name
  raise 'osProfile properties not provided in bootstrap_options' unless osProfile
  computerName = osProfile[:computerName]
  adminPassword = osProfile[:adminPassword]
  adminUsername = osProfile[:adminUsername]
  linuxConfiguration = osProfile[:linuxConfiguration]
  raise 'adminUsername under osProfile properties not provided in bootstrap_options' unless adminUsername

  Chef::Log.debug "Azure machine_options: #{machine_options.inspect}"
  action_handler.report_progress "Creating #{machine_spec.name} in #{location} with supplied parameters..."

  if availability_set
    action_handler.report_progress "Looking for availability_set #{availability_set}."
    begin
      availability_set_id = azure_vm_service.availability_sets.get(resource_group_name, availability_set).id
    rescue MsRestAzure::AzureOperationError => e
      if e.error_code.eql?('ResourceNotFound')
        action_handler.report_progress "availability_set #{availability_set} not found, Creating it..."
        sku = Azure::ARM::Compute::Models::Sku.new.tap do |sku|
          sku.name = 'Aligned'
        end
        aSet = Azure::ARM::Compute::Models::AvailabilitySet.new.tap do |set|
          set.platform_fault_domain_count = 3
          set.platform_update_domain_count = 3
          set.location = location
          set.sku = sku
        end
        res = azure_vm_service.availability_sets.create_or_update(resource_group_name, availability_set, aSet)
        availability_set_id = res.id
      else
        raise e
      end
    end
    raise "availability_set #{availability_set}  under resource group #{resource_group_name} was not found." unless availability_set_id
    availabilitySet = MsRestAzure::SubResource.new.tap do |subResource|
      subResource.id = availability_set_id
    end
  end

  action_handler.report_progress "Preparing OS disk for the VM #{vm_name}..."
  begin
    os_disk_exist = azure_vm_service.disks.get(resource_group_name, "#{vm_name}_os_disk")
  rescue MsRestAzure::AzureOperationError
    os_disk_exist = nil
  end
  if os_disk_exist
    action_handler.report_progress "OS disk with the name #{vm_name}_os_disk already exist."
    action_handler.report_progress 'checking attached status...'
    if os_disk_exist.managed_by.nil?
      action_handler.report_progress "OS disk #{vm_name} not attached to any VM. Using this..."
      os_disk = Azure::ARM::Compute::Models::OSDisk.new.tap do |os_disk|
        os_disk.create_option = 'attach'
        os_disk.os_type = machine_spec.reference['is_windows'] ? 'Windows' : 'Linux'
        os_disk.managed_disk = Azure::ARM::Compute::Models::ManagedDiskParameters.new.tap do |managedDisk|
          managedDisk.id = os_disk_exist.id
        end
      end
    else
      raise "OS disk #{vm_name} attached to a VM. Contact your cloud administrator or change VM name."
    end
  else
    os_disk = Azure::ARM::Compute::Models::OSDisk.new.tap do |os_disk|
      os_disk.name = "#{vm_name}_os_disk"
      os_disk.create_option = Azure::ARM::Compute::Models::DiskCreateOptionTypes::FromImage
      os_disk.managed_disk = Azure::ARM::Compute::Models::ManagedDiskParameters.new.tap do |managedDisk|
        managedDisk.storage_account_type = bootstrap_options[:storageProfile][:storage_account_type]
      end
    end
  end

  begin
    subnet = azure_net_service.subnets.get(resource_group_name, bootstrap_options[:virtual_network_name], bootstrap_options[:subnet_name])
  rescue MsRestAzure::AzureOperationError => e
    raise e.message
  end
  action_handler.report_progress "Creating a network interface for the VM #{vm_name}"
  begin
    nic_exist = azure_net_service.network_interfaces.get(resource_group_name, vm_name)
  rescue MsRestAzure::AzureOperationError
    nic_exist = nil
  end
  if nic_exist
    action_handler.report_progress "network interface with the name #{vm_name} already exist."
    action_handler.report_progress 'checking attached status...'
    if nic_exist.virtual_machine.nil?
      action_handler.report_progress "network interface #{vm_name} not attached to any VM. Using this..."
      nic = nic_exist
    else
      raise "network interface #{vm_name} attached to a VM. Contact your cloud administrator or change VM name."
    end
  else
    begin
      network_security_group = azure_net_service.network_security_groups.get(resource_group_name, network_security_group_name) if network_security_group_name
    rescue MsRestAzure::AzureOperationError => e
      raise e.message
    end
    nic = azure_net_service.network_interfaces.create_or_update(
      resource_group_name,
      vm_name,
      Azure::ARM::Network::Models::NetworkInterface.new.tap do |interface|
        interface.location = location
        interface.network_security_group = network_security_group if network_security_group_name
        interface.ip_configurations = [
          Azure::ARM::Network::Models::NetworkInterfaceIPConfiguration.new.tap do |nic_conf|
            nic_conf.name = vm_name
            nic_conf.private_ipallocation_method = Azure::ARM::Network::Models::IPAllocationMethod::Dynamic
            nic_conf.subnet = subnet
          end
        ]
      end
    )
  end

  vm_create_params = Azure::ARM::Compute::Models::VirtualMachine.new.tap do |vm|
    vm.location = location
    vm.availability_set = availabilitySet if availability_set && availability_set_id
    vm.tags = tags if tags
    unless os_disk_exist
      vm.os_profile = Azure::ARM::Compute::Models::OSProfile.new.tap do |os_profile|
        os_profile.computer_name = computerName if computerName
        os_profile.admin_username = adminUsername
        os_profile.admin_password = adminPassword if adminPassword
        if linuxConfiguration
          os_profile.linux_configuration = Azure::ARM::Compute::Models::LinuxConfiguration.new.tap do |linux|
            linux.disable_password_authentication = linuxConfiguration[:disablePasswordAuthentication].nil? ? true : linuxConfiguration[:disablePasswordAuthentication]
            if ssh = linuxConfiguration[:ssh]
              linux.ssh = Azure::ARM::Compute::Models::SshConfiguration.new.tap do |ssh_config|
                if publicKeys = ssh[:publicKeys]
                  public_keys = []
                  publicKeys.each do |publicKey|
                    sshPublicKey = Azure::ARM::Compute::Models::SshPublicKey.new.tap do |pub_key|
                      pub_key.path = publicKey[:path] || "/home/#{adminUsername}/.ssh/authorized_keys"
                      if publicKey[:keyData]
                        pub_key.key_data = publicKey[:keyData]
                      else
                        raise 'public key keyData not provided'
                      end
                    end
                    public_keys << sshPublicKey
                  end
                  ssh_config.public_keys = public_keys
                end
              end
            end
          end
        end
      end
    end

    vm.storage_profile = Azure::ARM::Compute::Models::StorageProfile.new.tap do |store_profile|
      unless os_disk_exist
        store_profile.image_reference = Azure::ARM::Compute::Models::ImageReference.new.tap do |ref|
          ref.publisher = bootstrap_options[:storageProfile][:imageReference][:publisher]
          ref.offer = bootstrap_options[:storageProfile][:imageReference][:offer]
          ref.sku = bootstrap_options[:storageProfile][:imageReference][:sku]
          ref.version = bootstrap_options[:storageProfile][:imageReference][:version]
        end
      end
      store_profile.os_disk = os_disk
    end

    vm.hardware_profile = Azure::ARM::Compute::Models::HardwareProfile.new.tap do |hardware|
      hardware.vm_size = bootstrap_options[:hardwareProfile][:vmSize]
    end

    vm.network_profile = Azure::ARM::Compute::Models::NetworkProfile.new.tap do |net_profile|
      net_profile.network_interfaces = [
        Azure::ARM::Compute::Models::NetworkInterfaceReference.new.tap do |ref|
          ref.id = nic.id
          ref.primary = true
        end
      ]
    end
  end

  vm = azure_vm_service.virtual_machines.create_or_update(resource_group_name, machine_spec.name, vm_create_params)

  machine_spec.reference = {
    'driver_version' => Chef::Provisioning::AzureRM::VERSION,
    'allocated_at' => Time.now.utc.to_s,
    'host_node' => action_handler.host_node,
    'location' => location,
    'resource_group_name' => resource_group_name,
    'adminUsername' => adminUsername
  }
  machine_spec.driver_url = driver_url
  machine_spec.reference['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
  machine_spec.reference['transport_address_location'] = machine_options[:transport_address_location] if machine_options[:transport_address_location]
  machine_spec.reference['vm_name'] = machine_spec.name
  machine_spec.reference['is_windows'] = case vm.storage_profile.os_disk.os_type.downcase
                                         when 'windows'
                                           true
                                         else
                                           false
                                         end
  action_handler.report_progress "Created #{machine_spec.name} in #{location}..."
end
connect_to_machine(name, chef_server = nil) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 300
def connect_to_machine(name, chef_server = nil)
  machine_spec = if name.is_a?(MachineSpec)
                   name
                 else
                   Chef::Provisioning::ChefMachineSpec.get(name, chef_server)
                 end

  machine_for(machine_spec, machine_spec.reference)
end
deep_symbolize_keys(hash_like) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 71
def deep_symbolize_keys(hash_like)
  return {} if hash_like.nil? || hash_like.empty?
  r = {}
  hash_like.each do |key, value|
    value = deep_symbolize_keys(value) if value.respond_to?(:values)
    r[key.to_sym] = value
  end
  r
end
destroy_load_balancer(action_handler, lb_spec, lb_options) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 526
def destroy_load_balancer(action_handler, lb_spec, lb_options)
  raise "#{lb_spec.name} hasn't recived a resource group. Unable to destroy." unless lb_options.resource_group
  azure_net_service.load_balancers.delete(lb_options.resource_group, lb_spec.name)

  # Remove LB spec from databag
  lb_spec.delete(action_handler)
end
destroy_machine(action_handler, machine_spec, _machine_options) click to toggle source

(see Chef::Provisioning::Driver#destroy_machine)

# File lib/chef/provisioning/azurerm/driver.rb, line 337
def destroy_machine(action_handler, machine_spec, _machine_options)
  vm = vm_for(machine_spec)
  vm_name = machine_spec.name
  resource_group_name = machine_spec.reference['resource_group_name']
  # Check if we need to proceed
  return if vm.nil? || vm_name.nil? || resource_group_name.nil?
  # Skip if we don't actually need to do anything
  return unless action_handler.should_perform_actions

  # TODO: action_handler.do |block| ?
  action_handler.report_progress "Destroying VM #{machine_spec.name}"
  azure_vm_service.virtual_machines.delete(resource_group_name, vm_name)
  action_handler.report_progress "Destroying OS disk for machine #{vm_name}"
  azure_vm_service.disks.delete(resource_group_name, "#{vm_name}_os_disk")
  action_handler.report_progress "Destroying network interface #{machine_spec.name}"
  azure_net_service.network_interfaces.delete(resource_group_name, vm_name)
  action_handler.report_progress "Destroyed VM #{machine_spec.name}"
end
find_probe_id(resource_group, lb_name, probe_name) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 515
def find_probe_id(resource_group, lb_name, probe_name)
  probes = azure_net_service.load_balancers.get(resource_group, lb_name).probes
  probes.each do |probe|
    return probe.id if probe.name.eql?(probe_name)
  end
end
find_vm_name(machine_specs, vm_name) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 509
def find_vm_name(machine_specs, vm_name)
  machine_specs.each do |vm|
    return vm.name if vm.name.include?(vm_name)
  end
end
lb_update(resource_group, name, lb) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 522
def lb_update(resource_group, name, lb)
  azure_net_service.load_balancers.create_or_update(resource_group, name, lb)
end
ready_machine(action_handler, machine_spec, machine_options) click to toggle source

(see Chef::Provisioning::Driver#ready_machine)

# File lib/chef/provisioning/azurerm/driver.rb, line 311
def ready_machine(action_handler, machine_spec, machine_options)
  machine_options = deep_symbolize_keys(machine_options)
  vm = vm_for(machine_spec)

  if vm.nil?
    raise "Machine #{machine_spec.name} does not have a VM associated with it, or the VM does not exist."
  end

  case machine_power_state(vm)
  when 'running'
    action_handler.report_progress "#{machine_spec.name} is ready"
  when 'stopped'
    action_handler.report_progress "#{machine_spec.name} is stopped. Driver can't start it, please contact admin"
    raise "#{machine_spec.name} is stopped"
    # TODO: start the VM
  when nil
    raise "Could not find power state of #{machine_spec.name}"
  else
    action_handler.report_progress "Waiting for #{machine_spec.name} to become ready..."
    wait_until_ready(action_handler, machine_spec)
  end
  wait_for_transport(action_handler, machine_spec, machine_options)
  machine_for(machine_spec, machine_options, vm)
end

Private Instance Methods

azure_net_service() click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 557
def azure_net_service
  credentials = Credentials.new.azure_credentials_for_subscription(subscription_id)
  client = Azure::ARM::Network::NetworkManagementClient.new(credentials)
  client.subscription_id = subscription_id
  client
end
azure_vm_service() click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 550
def azure_vm_service
  credentials = Credentials.new.azure_credentials_for_subscription(subscription_id)
  client = Azure::ARM::Compute::ComputeManagementClient.new(credentials)
  client.subscription_id = subscription_id
  client
end
convergence_strategy_for(machine_spec, machine_options) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 679
def convergence_strategy_for(machine_spec, machine_options)
  # Tell Ohai that this is an Azure VM so that it runs the azure plugin
  convergence_options = Cheffish::MergedConfig.new(
    machine_options[:convergence_options] || {},
    ohai_hints: { 'azure' => '' }
  )

  # Defaults
  unless machine_spec.reference
    return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(convergence_options, config)
  end

  if machine_spec.reference['is_windows']
    raise 'Sorry, No windows convergance support yet.'
  elsif machine_options[:cached_installer] == true
    Chef::Provisioning::ConvergenceStrategy::InstallCached.new(convergence_options, config)
  else
    Chef::Provisioning::ConvergenceStrategy::InstallSh.new(convergence_options, config)
  end
end
create_ssh_transport(machine_spec, machine_options, vm) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 591
def create_ssh_transport(machine_spec, machine_options, vm)
  ssh_options = ssh_options_for(machine_spec, machine_options, vm)
  username = machine_spec.reference['adminUsername'] || default_ssh_username
  options = {}
  if machine_spec.reference[:sudo] || (!machine_spec.reference.key?(:sudo) && username != 'root')
    options[:prefix] = 'sudo '
  end
  # Enable pty by default
  options[:ssh_pty_enable] = true
  if machine_spec.reference.key?('ssh_gateway')
    options[:ssh_gateway] = machine_spec.reference['ssh_gateway']
  elsif machine_options[:ssh_gateway]
    options[:ssh_gateway] = machine_options[:ssh_gateway]
  end

  remote_host = determine_remote_host(machine_spec, vm)
  Chef::Provisioning::Transport::SSH.new(remote_host, username, ssh_options, options, config)
end
default_ssh_username() click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 564
def default_ssh_username
  'ubuntu'
end
determine_remote_host(machine_spec, vm) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 610
def determine_remote_host(machine_spec, vm)
  transport_address_location = (machine_spec.reference['transport_address_location'] || :private_ip).to_sym
  Chef::Log.warn("This driver only supports private ip address. Using private ip.  Set machine_options ':transport_address_location => :private_ip' ...") if transport_address_location != :private_ip
  private_ip_address_for(vm)
end
get_primary_network_interface(network_interfaces) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 650
def get_primary_network_interface(network_interfaces)
  network_interfaces.each do |network_interface|
    return network_interface if network_interface.primary
  end
end
get_primary_private_ip_address(ip_configurations) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 656
def get_primary_private_ip_address(ip_configurations)
  ip_configurations.each do |ip_configuration|
    return ip_configuration['properties']['privateIPAddress'] if ip_configuration['properties']['primary']
  end
end
machine_for(machine_spec, machine_options, vm = nil) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 536
def machine_for(machine_spec, machine_options, vm = nil)
  vm ||= vm_for(machine_spec)

  raise "VM for node #{machine_spec.name} has not been created!" unless vm
  transport = transport_for(machine_spec, machine_options, vm)
  convergence_strategy = convergence_strategy_for(machine_spec, machine_options)

  if machine_spec.reference['is_windows']
    raise 'Sorry, No windows convergance support yet.'
  else
    Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport, convergence_strategy)
  end
end
machine_power_state(vm) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 746
def machine_power_state(vm)
  statuses = vm.instance_view.statuses
  statuses.each do |status|
    return status.code.split('/').last if status.code.split('/').first.eql?('PowerState')
  end
  nil
end
machine_provisioning_state(vm) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 754
def machine_provisioning_state(vm)
  statuses = vm.instance_view.statuses
  statuses.each do |status|
    return status.code.split('/').last if status.code.split('/').first.eql?('ProvisioningState')
  end
  nil
end
private_ip_address_for(vm) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 628
def private_ip_address_for(vm)
  network_profile = vm.network_profile
  network_interfaces = network_profile.network_interfaces
  primary_network_interface = get_primary_network_interface(network_interfaces)
  api_version = '2017-03-01'
  request_headers = {}
  # Set Headers
  request_headers['x-ms-client-request-id'] = SecureRandom.uuid
  request_headers['accept-language'] = azure_net_service.accept_language unless azure_net_service.accept_language.nil?

  request_url = azure_net_service.base_url
  options = {
    middlewares: [[MsRest::RetryPolicyMiddleware, times: 3, retry: 0.02], [:cookie_jar]],
    query_params: { 'api-version' => api_version, '$expand' => nil },
    headers: request_headers,
    base_url: request_url
  }

  ip_configurations = azure_net_service.make_request(:get, primary_network_interface.id, options)['properties']['ipConfigurations']
  get_primary_private_ip_address(ip_configurations)
end
private_key_for(machine_spec, machine_options, _vm) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 662
def private_key_for(machine_spec, machine_options, _vm)
  if machine_spec.reference['key_name']
    key = get_private_key(machine_spec.reference['key_name'])
    unless key
      raise "Server was created with key name '#{machine_spec.reference['key_name']}', but the corresponding private key was not found locally.  Check if the key is in Chef::Config.private_key_paths: #{Chef::Config.private_key_paths.join(', ')}"
    end
    key
  elsif machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:key_path]
    IO.read(machine_options[:bootstrap_options][:key_path])
  elsif machine_options[:bootstrap_options] && machine_options[:bootstrap_options][:key_name]
    get_private_key(machine_options[:bootstrap_options][:key_name])
  else
    # TODO: make a way to suggest other keys to try ...
    raise "No key found to connect to #{machine_spec.name} (#{machine_spec.reference.inspect})!"
  end
end
ssh_options_for(machine_spec, machine_options, vm) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 616
def ssh_options_for(machine_spec, machine_options, vm)
  result = {
    auth_methods: ['publickey'],
    keys_only: true
  }.merge(machine_options[:ssh_options] || {})
  unless result.key?(:key_data)
    result[:keys_only] = true
    result[:key_data] = [private_key_for(machine_spec, machine_options, vm)]
  end
  result
end
transport_for(machine_spec, machine_options, vm) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 583
def transport_for(machine_spec, machine_options, vm)
  if machine_spec.reference['is_windows']
    raise 'Sorry, No windows convergance support yet.'
  else
    create_ssh_transport(machine_spec, machine_options, vm)
  end
end
vm_for(machine_spec) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 568
def vm_for(machine_spec)
  if machine_spec.reference
    if machine_spec.driver_url != driver_url
      raise "Switching a machine's driver from #{machine_spec.driver_url} to #{driver_url} is not currently supported!  Use machine :destroy and then re-create the machine on the new driver."
    elsif machine_spec.reference['resource_group_name'].nil? || machine_spec.name.nil?
      return nil
    end
    begin
      return azure_vm_service.virtual_machines.get(machine_spec.reference['resource_group_name'], machine_spec.name, 'instanceView')
    rescue MsRestAzure::AzureOperationError
      return nil
    end
  end
end
wait_for_transport(action_handler, machine_spec, machine_options) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 724
def wait_for_transport(action_handler, machine_spec, machine_options)
  vm = vm_for(machine_spec)
  sleep_time = 10
  transport = transport_for(machine_spec, machine_options, vm)
  unless transport.available?
    if action_handler.should_perform_actions
      action_handler.report_progress "waiting for #{machine_spec.name} (#{driver_url}) to be connectable (transport up and running) ..."
      max_wait_time = Chef::Config.chef_provisioning[:machine_max_wait_time] || 120
      Retryable.retryable(
        tries: (max_wait_time / sleep_time).to_i,
        sleep: sleep_time,
        matching: /did not become connectable within/
      ) do |retries, _exception|
        action_handler.report_progress "been waiting #{sleep_time * retries}/#{max_wait_time} -- sleeping #{sleep_time} seconds for #{machine_spec.name} (#{driver_url}) to become connectable ..."
        unless transport.available?
          raise "VM #{machine_spec.name} (#{driver_url}) did not become connectable within #{max_wait_time} seconds"
        end
      end
    end
  end
end
wait_until_ready(action_handler, machine_spec) click to toggle source
# File lib/chef/provisioning/azurerm/driver.rb, line 700
def wait_until_ready(action_handler, machine_spec)
  vm = vm_for(machine_spec)

  # If the machine is ready, nothing to do
  return if machine_power_state(vm) == 'running'

  # Skip if we don't actually need to do anything
  return unless action_handler.should_perform_actions

  time_elapsed = 0
  sleep_time = 10
  max_wait_time = 120

  action_handler.report_progress "waiting for #{machine_spec.name} to be ready ..."
  while time_elapsed < 120 && machine_power_state(vm) == 'running'
    action_handler.report_progress "#{time_elapsed}/#{max_wait_time}s..."
    sleep(sleep_time)
    time_elapsed += sleep_time
    # Azure caches results
    vm = vm_for(machine_spec)
  end
  action_handler.report_progress "#{machine_spec.name} is now ready"
end