class Chef::Provisioning::FogDriver::Driver

Provisions cloud machines with the Fog driver.

## Fog Driver URLs

All Chef Provisioning drivers use URLs to uniquely identify a driver's “bucket” of machines. Fog URLs are of the form fog:<provider>:<identifier:> - see individual providers for sample URLs.

Identifier is generally something uniquely identifying the account. If multiple users can access the account, the identifier should be the same for all of them (do not use the username in these cases, use an account ID or auth server URL).

In particular, the identifier should be specific enough that if you create a server with a driver with this URL, the server should be retrievable from the same URL *no matter what else changes*. For example, an AWS account ID is not enough for this–if you varied the region, you would no longer see your server in the list. Thus, AWS uses both the account ID and the region.

## Supporting a new Fog provider

The Fog driver does not immediately support all Fog providers out of the box. Some minor work needs to be done to plug them into Chef.

To add a new supported Fog provider, pick an appropriate identifier, go to from_provider and compute_options_for, and add the new provider in the case statements so that URLs for your Fog provider can be generated. If your cloud provider has environment variables or standard config files (like ~/.aws/credentials or ~/.aws/config), you can read those and merge that information in the compute_options_for function.

## Reference format

All machines have a reference hash to find them. These are the keys used by the Fog provisioner:

## Machine options

Machine options (for allocation and readying the machine) include:

Example bootstrap_options for ec2:

:bootstrap_options => {
  :image_id =>'ami-311f2b45',
  :flavor_id =>'t1.micro',
  :key_name => 'key-pair-name'
}

Constants

DEFAULT_OPTIONS
RETRYABLE_ERRORS
RETRYABLE_OPTIONS

Public Class Methods

__new__(driver_url, config)
Alias for: new
canonicalize_url(driver_url, config) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 140
def self.canonicalize_url(driver_url, config)
  _, provider, id = driver_url.split(":", 3)
  config, id = provider_class_for(provider).compute_options_for(provider, id, config)
  ["fog:#{provider}:#{id}", config]
end
from_provider(provider, config) click to toggle source

Passed in a config which is not merged with driver_url (because we don't know what it is yet) but which has the same keys

# File lib/chef/provisioning/fog_driver/driver.rb, line 148
def self.from_provider(provider, config)
  # Figure out the options and merge them into the config
  config, id = provider_class_for(provider).compute_options_for(provider, nil, config)

  driver_url = "fog:#{provider}:#{id}"

  Provisioning.driver_for_url(driver_url, config)
end
from_url(driver_url, config) click to toggle source

Passed in a driver_url, and a config in the format of Driver.config.

# File lib/chef/provisioning/fog_driver/driver.rb, line 136
def self.from_url(driver_url, config)
  Driver.new(driver_url, config)
end
inherited(klass) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 113
def inherited(klass)
  class << klass
    alias_method :new, :__new__
  end
end
new(driver_url, config) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 130
def self.new(driver_url, config)
  provider = driver_url.split(":")[1]
  provider_class_for(provider).new(driver_url, config)
end
Also aliased as: __new__
new(driver_url, config) click to toggle source

Create a new Fog driver.

## Parameters driver_url - URL of driver. “fog:<provider>:<provider_id>” config - configuration. :driver_options, :keys, :key_paths and :log_level are used.

driver_options is a hash with these possible options:
- compute_options: the hash of options to Fog::Compute.new.
- aws_config_file: aws config file (defaults: ~/.aws/credentials, ~/.aws/config)
- aws_csv_file: aws csv credentials file downloaded from EC2 interface
- aws_profile: profile name to use for credentials
- aws_credentials: AWSCredentials object. (will be created for you by default)
- log_level: :debug, :info, :warn, :error
Calls superclass method
# File lib/chef/provisioning/fog_driver/driver.rb, line 169
def initialize(driver_url, config)
  super(driver_url, config)
  if config[:log_level] == :debug
    Fog::Logger[:debug] = ::STDERR
    Excon.defaults[:debug_request] = true
    Excon.defaults[:debug_response] = true
  end
end
provider_class_for(provider) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 125
def self.provider_class_for(provider)
  require "chef/provisioning/fog_driver/providers/#{provider.downcase}"
  @@registered_provider_classes[provider]
end
register_provider_class(name, driver) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 121
def self.register_provider_class(name, driver)
  @@registered_provider_classes[name] = driver
end

Protected Class Methods

compute_options_for(provider, _id, _config) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 724
def self.compute_options_for(provider, _id, _config)
  raise "unsupported Fog provider #{provider}"
end

Public Instance Methods

allocate_machine(action_handler, machine_spec, machine_options) click to toggle source

Acquire a machine, generally by provisioning it. Returns a Machine object pointing at the machine, allowing useful actions like setup, converge, execute, file and directory.

# File lib/chef/provisioning/fog_driver/driver.rb, line 189
def allocate_machine(action_handler, machine_spec, machine_options)
  # If the server does not exist, create it
  create_servers(action_handler, { machine_spec => machine_options }, Chef::ChefFS::Parallelizer.new(0))
  machine_spec
end
allocate_machines(action_handler, specs_and_options, parallelizer) { |machine_spec| ... } click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 195
def allocate_machines(action_handler, specs_and_options, parallelizer)
  create_servers(action_handler, specs_and_options, parallelizer) do |machine_spec, _server|
    yield machine_spec
  end
  specs_and_options.keys
end
compute() click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 264
def compute
  @compute ||= Fog::Compute.new(compute_options)
end
compute_options() click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 178
def compute_options
  JSON.parse(driver_options[:compute_options].to_h.to_json, symbolize_names: true) || {}
end
connect_to_machine(machine_spec, machine_options) click to toggle source

Connect to machine without acquiring it

# File lib/chef/provisioning/fog_driver/driver.rb, line 235
def connect_to_machine(machine_spec, machine_options)
  machine_for(machine_spec, machine_options)
end
create_volume(_action_handler, _volume_spec, _volume_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 282
def create_volume(_action_handler, _volume_spec, _volume_options)
  raise "##create_volume not implemented in in #{self.class}"
end
destroy_machine(action_handler, machine_spec, machine_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 239
def destroy_machine(action_handler, machine_spec, machine_options)
  server = server_for(machine_spec)
  if server
    action_handler.perform_action "destroy machine #{machine_spec.name} (#{machine_spec.reference['server_id']} at #{driver_url})" do
      server.destroy
      machine_spec.reference = nil
    end
  end
  strategy = ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
  strategy.cleanup_convergence(action_handler, machine_spec)
end
destroy_volume(_action_handler, _volume_spec, _volume_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 286
def destroy_volume(_action_handler, _volume_spec, _volume_options)
  raise "##destroy_volume not implemented in in #{self.class}"
end
image_for(image_spec) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 260
def image_for(image_spec)
  compute.images.get(image_spec.reference["image_id"])
end
provider() click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 182
def provider
  compute_options[:provider]
end
ready_machine(action_handler, machine_spec, machine_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 202
def ready_machine(action_handler, machine_spec, machine_options)
  server = server_for(machine_spec)
  if server.nil?
    raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
  end

  # Start the server if needed, and wait for it to start
  start_server(action_handler, machine_spec, server)
  wait_until_ready(action_handler, machine_spec, machine_options, server)

  converge_floating_ips(action_handler, machine_spec, machine_options, server)

  begin
    wait_for_transport(action_handler, machine_spec, machine_options, server)
  rescue Fog::Errors::TimeoutError
    # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
    if machine_spec.reference["started_at"] || remaining_wait_time(machine_spec, machine_options) < -(10 * 60)
      raise
    else
      # Sometimes (on EC2) the machine comes up but gets stuck or has
      # some other problem.  If this is the case, we restart the server
      # to unstick it.  Reboot covers a multitude of sins.
      Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) was started but SSH did not come up.  Rebooting machine in an attempt to unstick it ..."
      restart_server(action_handler, machine_spec, server)
      wait_until_ready(action_handler, machine_spec, machine_options, server)
      wait_for_transport(action_handler, machine_spec, machine_options, server)
    end
  end

  machine_for(machine_spec, machine_options, server)
end
stop_machine(action_handler, machine_spec, _machine_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 251
def stop_machine(action_handler, machine_spec, _machine_options)
  server = server_for(machine_spec)
  if server
    action_handler.perform_action "stop machine #{machine_spec.name} (#{server.id} at #{driver_url})" do
      server.stop
    end
  end
end
transport_for(machine_spec, machine_options, server, action_handler = nil) click to toggle source

Not meant to be part of public interface

# File lib/chef/provisioning/fog_driver/driver.rb, line 269
def transport_for(machine_spec, machine_options, server, action_handler = nil)
  Chef::Log.debug("Creating transport for #{server}")
  Chef::Log.debug("Machine Spec: #{machine_spec}")
  if machine_spec.reference["is_windows"]
    action_handler.report_progress "Waiting for admin password on #{machine_spec.name} to be ready (may take up to 15 minutes)..." if action_handler
    transport = create_winrm_transport(machine_spec, machine_options, server)
    action_handler.report_progress "Admin password available ..." if action_handler
    transport
  else
    create_ssh_transport(machine_spec, machine_options, server)
  end
end

Protected Instance Methods

attach_ip(server, ip) click to toggle source

Attach given IP to machine, assign it as public Code taken from kitchen-openstack driver

https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb
# File lib/chef/provisioning/fog_driver/driver.rb, line 531
def attach_ip(server, ip)
  Chef::Log.info "Attaching floating IP <#{ip}>"
  server.associate_address ip
  server.reload
end
attach_ip_from_pool(server, pool) click to toggle source

Attach IP to machine from IP pool Code taken from kitchen-openstack driver

https://github.com/test-kitchen/kitchen-openstack/blob/master/lib/kitchen/driver/openstack.rb
# File lib/chef/provisioning/fog_driver/driver.rb, line 517
def attach_ip_from_pool(server, pool)
  @@ip_pool_lock.synchronize do
    Chef::Log.info "Attaching floating IP from <#{pool}> pool"
    free_addrs = compute.addresses.map do |i|
      i.ip if i.fixed_ip.nil? && i.instance_id.nil? && i.pool == pool
    end.compact
    raise "No available IPs in pool <#{pool}>" if free_addrs.empty?
    attach_ip(server, free_addrs[0])
  end
end
bootstrap_options_for(machine_spec, machine_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 588
def bootstrap_options_for(machine_spec, machine_options)
  bootstrap_options = symbolize_keys(machine_options[:bootstrap_options] || {})

  bootstrap_options[:tags] = default_tags(machine_spec, bootstrap_options[:tags] || {})

  bootstrap_options[:name] ||= machine_spec.name

  bootstrap_options
end
converge_floating_ips(action_handler, _machine_spec, machine_options, server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 459
def converge_floating_ips(action_handler, _machine_spec, machine_options, server)
  pool = option_for(machine_options, :floating_ip_pool)
  floating_ip = option_for(machine_options, :floating_ip)
  attached_floating_ips = find_floating_ips(server, action_handler)
  if pool

    Chef::Log.debug "Attaching IP from pool #{pool}"
    if !attached_floating_ips.empty?
      Chef::Log.info "Server already assigned attached_floating_ips `#{attached_floating_ips}`"
    elsif
      action_handler.perform_action "Attaching floating IP from pool `#{pool}`" do
        attach_ip_from_pool(server, pool)
      end
    end

  elsif floating_ip

    Chef::Log.debug "Attaching given IP #{floating_ip}"
    if attached_floating_ips.include? floating_ip
      Chef::Log.info "Address <#{floating_ip}> already allocated"
    else
      action_handler.perform_action "Attaching floating IP #{floating_ip}" do
        attach_ip(server, floating_ip)
      end
    end

  elsif !attached_floating_ips.empty?

    # If nothing is assigned, lets remove any floating IPs
    Chef::Log.debug "Missing :floating_ip_pool or :floating_ip, removing attached floating IPs"
    action_handler.perform_action "Removing floating IPs #{attached_floating_ips}" do
      attached_floating_ips.each do |ip|
        server.disassociate_address(ip)
      end
      server.reload
    end
  end
end
convergence_strategy_for(machine_spec, machine_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 626
def convergence_strategy_for(machine_spec, machine_options)
  # Defaults
  unless machine_spec.reference
    return ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
  end

  if machine_spec.reference["is_windows"]
    ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
  elsif machine_options[:cached_installer] == true
    ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
  else
    ConvergenceStrategy::InstallSh.new(machine_options[:convergence_options], config)
  end
end
create_many_servers(num_servers, bootstrap_options, parallelizer) { |server| ... } click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 377
def create_many_servers(num_servers, bootstrap_options, parallelizer)
  parallelizer.parallelize(1.upto(num_servers)) do |_i|
    clean_bootstrap_options = Marshal.load(Marshal.dump(bootstrap_options)) # Prevent destructive operations on bootstrap_options.

    server = compute.servers.create(clean_bootstrap_options)
    yield server if block_given?
    server
  end.to_a
end
create_servers(action_handler, specs_and_options, parallelizer) { |machine_spec, server| ... } click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 300
def create_servers(action_handler, specs_and_options, parallelizer)
  specs_and_servers = servers_for(specs_and_options)

  # Get the list of servers which exist, segmented by their bootstrap options
  # (we will try to create a set of servers for each set of bootstrap options
  # with create_many)
  by_bootstrap_options = {}
  specs_and_options.each do |machine_spec, machine_options|
    server = specs_and_servers[machine_spec]
    if server
      server_state = server.respond_to?(:status) ? server.status : server.state
      if %w{terminated archive DELETED}.include?(server_state.downcase) # Can't come back from that
        Chef::Log.warn "Machine #{machine_spec.name} (#{server.id} on #{driver_url}) is terminated.  Recreating ..."
      else
        yield machine_spec, server if block_given?
        next
      end
    elsif machine_spec.reference
      Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.reference['server_id']} on #{driver_url}) no longer exists.  Recreating ..."
    end

    machine_spec.reference ||= {}
    machine_spec.reference.update(
      "driver_url" => driver_url,
      "driver_version" => FogDriver::VERSION,
      "creator" => creator,
      "allocated_at" => Time.now.to_i
    )

    bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
    machine_spec.reference["key_name"] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
    by_bootstrap_options[bootstrap_options] ||= []
    by_bootstrap_options[bootstrap_options] << machine_spec

    # TODO: 2.0 We no longer support `use_private_ip_for_ssh`, only `transport_address_location
    if machine_options[:use_private_ip_for_ssh]
      unless @transport_address_location_warned
        Chef::Log.warn("The machine option ':use_private_ip_for_ssh' has been deprecated, use ':transport_address_location'")
        @transport_address_location_warned = true
      end
      machine_options = Cheffish::MergedConfig.new(machine_options, transport_address_location: :private_ip)
    end
    %w{is_windows ssh_username sudo transport_address_location ssh_gateway}.each do |key|
      machine_spec.reference[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
    end
  end

  # Create the servers in parallel
  parallelizer.parallelize(by_bootstrap_options) do |bootstrap_options, machine_specs|
    machine_description = if machine_specs.size == 1
                            "machine #{machine_specs.first.name}"
                          else
                            "machines #{machine_specs.map(&:name).join(', ')}"
    end
    description = ["creating #{machine_description} on #{driver_url}"]
    bootstrap_options.each_pair { |key, value| description << "  #{key}: #{value.inspect}" }
    action_handler.report_progress description
    if action_handler.should_perform_actions
      # Actually create the servers
      create_many_servers(machine_specs.size, bootstrap_options, parallelizer) do |server|
        # Assign each one to a machine spec
        machine_spec = machine_specs.pop
        machine_options = specs_and_options[machine_spec]
        machine_spec.reference["server_id"] = server.id

        action_handler.performed_action "machine #{machine_spec.name} created as #{server.id} on #{driver_url}"

        yield machine_spec, server if block_given?
      end

      unless machine_specs.empty?
        raise "Not all machines were created by create_many_servers!"
      end
    end
  end.to_a
end
create_ssh_transport(machine_spec, machine_options, server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 697
def create_ssh_transport(machine_spec, machine_options, server)
  ssh_options = ssh_options_for(machine_spec, machine_options, server)
  username = machine_spec.reference["ssh_username"] || default_ssh_username
  if machine_options.key?(:ssh_username) && machine_options[:ssh_username] != machine_spec.reference["ssh_username"]
    Chef::Log.warn("Server #{machine_spec.name} was created with SSH username #{machine_spec.reference['ssh_username']} and machine_options specifies username #{machine_options[:ssh_username]}.  Using #{machine_spec.reference['ssh_username']}.  Please edit the node and change the chef_provisioning.reference.ssh_username attribute if you want to change it.")
  end
  options = {}
  if machine_spec.reference[:sudo] || (!machine_spec.reference.key?(:sudo) && username != "root")
    options[:prefix] = "sudo "
  end

  remote_host = determine_remote_host(machine_spec, server)
  if remote_host.nil? || remote_host.empty?
    raise "Server #{server.id} has no private or public IP address!"
  end

  # Enable pty by default
  options[:ssh_pty_enable] = true
  if machine_options[:ssh_gateway]
    options[:ssh_gateway] = machine_options[:ssh_gateway]
  elsif machine_spec.reference.key?("ssh_gateway")
    options[:ssh_gateway] = machine_spec.reference["ssh_gateway"]
  end

  Transport::SSH.new(remote_host, username, ssh_options, options, config)
end
create_winrm_transport(_machine_spec, _machine_options, _server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 693
def create_winrm_transport(_machine_spec, _machine_options, _server)
  raise "This provider doesn't know how to do that."
end
creator() click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 296
def creator
  raise "unsupported Fog provider #{provider} (please implement #creator)"
end
default_ssh_username() click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 689
def default_ssh_username
  "root"
end
default_tags(machine_spec, bootstrap_tags = {}) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 598
def default_tags(machine_spec, bootstrap_tags = {})
  tags = {
    "Name" => machine_spec.name,
    "BootstrapId" => machine_spec.id,
    "BootstrapHost" => Socket.gethostname,
    "BootstrapUser" => Etc.getlogin
  }
  # User-defined tags override the ones we set
  tags.merge(bootstrap_tags)
end
determine_remote_host(machine_spec, server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 728
def determine_remote_host(machine_spec, server)
  transport_address_location = (machine_spec.reference["transport_address_location"] || :none).to_sym

  if machine_spec.reference["use_private_ip_for_ssh"]
    # The machine_spec has the old config key, lets update it - a successful chef converge will save the machine_spec
    # TODO in 2.0 get rid of this update
    machine_spec.reference.delete("use_private_ip_for_ssh")
    machine_spec.reference["transport_address_location"] = :private_ip
    server.private_ip_address
  elsif transport_address_location == :ip_addresses
    server.ip_addresses.first
  elsif transport_address_location == :private_ip
    server.private_ip_address
  elsif transport_address_location == :public_ip
    server.public_ip_address
  elsif !server.public_ip_address && server.private_ip_address
    Chef::Log.warn("Server #{machine_spec.name} has no public floating_ip address.  Using private floating_ip '#{server.private_ip_address}'.  Set driver option 'transport_address_location' => :private_ip if this will always be the case ...")
    server.private_ip_address
  elsif server.public_ip_address
    server.public_ip_address
  else
    raise "Server #{server.id} has no private or public IP address!"
    # raise "Invalid 'transport_address_location'.  They can only be 'public_ip', 'private_ip', or 'ip_addresses'."
  end
end
find_floating_ips(server, action_handler) click to toggle source

Find all attached floating IPs from all networks

# File lib/chef/provisioning/fog_driver/driver.rb, line 499
def find_floating_ips(server, action_handler)
  floating_ips = []
  Retryable.retryable(RETRYABLE_OPTIONS) do |retries, _exception|
    action_handler.report_progress "Querying for floating IPs attached to server #{server.id}, API attempt #{retries + 1}/#{RETRYABLE_OPTIONS[:tries]} ..."
    server.addresses.each do |_network, addrs|
      addrs.each do |full_addr|
        if full_addr["OS-EXT-IPS:type"] == "floating"
          floating_ips << full_addr["addr"]
        end
      end
    end
  end
  floating_ips
end
machine_for(machine_spec, machine_options, server = nil) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 609
def machine_for(machine_spec, machine_options, server = nil)
  server ||= server_for(machine_spec)
  unless server
    raise "Server for node #{machine_spec.name} has not been created!"
  end

  if machine_spec.reference["is_windows"]
    Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, server), convergence_strategy_for(machine_spec, machine_options))
  else
    Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, server), convergence_strategy_for(machine_spec, machine_options))
  end
end
option_for(machine_options, key) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 292
def option_for(machine_options, key)
  machine_options[:bootstrap_options][key] || DEFAULT_OPTIONS[key]
end
overwrite_default_key_willy_nilly(action_handler, machine_spec) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 566
def overwrite_default_key_willy_nilly(action_handler, machine_spec)
  if machine_spec.reference &&
      Gem::Version.new(machine_spec.reference["driver_version"]) < Gem::Version.new("0.10")
    return "metal_default"
  end

  driver = self
  updated = @@chef_default_lock.synchronize do
    Provisioning.inline_resource(action_handler) do
      fog_key_pair "chef_default" do
        driver driver
        allow_overwrite true
      end
    end
  end
  if updated
    # Only warn the first time
    Chef::Log.warn("Using chef_default key, which is not shared between machines!  It is recommended to create an AWS key pair with the fog_key_pair resource, and set :bootstrap_options => { :key_name => <key name> }")
  end
  "chef_default"
end
parse_time(value) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 421
def parse_time(value)
  if value.is_a?(String)
    Time.parse(value)
  else
    Time.at(value)
  end
end
private_key_for(machine_spec, machine_options, server) click to toggle source

Get the private key for a machine - prioritize the server data, fall back to the the machine spec data, and if that doesn't work, raise an exception. @param [Hash] machine_spec Machine spec data @param [Hash] machine_options Machine options @param [Chef::Provisioning::Machine] server a Machine representing the server @return [String] PEM-encoded private key

# File lib/chef/provisioning/fog_driver/driver.rb, line 647
def private_key_for(machine_spec, machine_options, server)
  bootstrap_options = machine_options[:bootstrap_options] || {}
  if server.respond_to?(:private_key) && server.private_key
    server.private_key
  elsif server.respond_to?(:key_name) && server.key_name
    key = get_private_key(server.key_name)
    unless key
      raise "Server has key name '#{server.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_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 bootstrap_options[:key_path]
    IO.read(bootstrap_options[:key_path])
  elsif bootstrap_options[:key_name]
    get_private_key(bootstrap_options[:key_name])
  elsif machine_options.key?(:ssh_options) && machine_options[:ssh_options].key?(:keys)
    IO.read(machine_options[:ssh_options][:keys].first)
  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})" \
      " : machine_options -> (#{machine_options.inspect})!"
  end
end
remaining_wait_time(machine_spec, machine_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 412
def remaining_wait_time(machine_spec, machine_options)
  if machine_spec.reference["started_at"]
    timeout = option_for(machine_options, :start_timeout) - (Time.now.utc - parse_time(machine_spec.reference["started_at"]))
  else
    timeout = option_for(machine_options, :create_timeout) - (Time.now.utc - parse_time(machine_spec.reference["allocated_at"]))
  end
  timeout > 0 ? timeout : 0.01
end
restart_server(action_handler, machine_spec, server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 404
def restart_server(action_handler, machine_spec, server)
  action_handler.perform_action "restart machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
    server.reboot
    machine_spec.reference["started_at"] = Time.now.to_i
  end
  machine_spec.save(action_handler)
end
server_for(machine_spec) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 543
def server_for(machine_spec)
  if machine_spec.reference
    compute.servers.get(machine_spec.reference["server_id"])
  end
end
servers_for(specs_and_options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 549
def servers_for(specs_and_options)
  result = {}
  specs_and_options.each do |machine_spec, _machine_options|
    if machine_spec.reference
      if machine_spec.reference["driver_url"] != driver_url
        raise "Switching a machine's driver from #{machine_spec.reference['driver_url']} to #{driver_url} for is not currently supported!  Use machine :destroy and then re-create the machine on the new driver."
      end
      result[machine_spec] = compute.servers.get(machine_spec.reference["server_id"])
    else
      result[machine_spec] = nil
    end
  end
  result
end
ssh_options_for(machine_spec, machine_options, server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 676
def ssh_options_for(machine_spec, machine_options, server)
  result = {
    auth_methods: ["publickey"],
    host_key_alias: "#{server.id}.#{provider}"
  }.merge(machine_options[:ssh_options] || {})
  # Grab key_data from the user's config if not specified
  unless result.key?(:key_data)
    result[:keys_only] = true
    result[:key_data] = [private_key_for(machine_spec, machine_options, server)]
  end
  result
end
start_server(action_handler, machine_spec, server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 387
def start_server(action_handler, machine_spec, server)
  # If it is stopping, wait for it to get out of "stopping" transition status before starting
  server_state = server.respond_to?(:status) ? server.status : server.state
  if server_state == "stopping"
    action_handler.report_progress "wait for #{machine_spec.name} (#{server.id} on #{driver_url}) to finish stopping ..."
    server.wait_for { server_state != "stopping" }
    action_handler.report_progress "#{machine_spec.name} is now stopped"
  end
  if server_state == "stopped"
    action_handler.perform_action "start machine #{machine_spec.name} (#{server.id} on #{driver_url})" do
      server.start
      machine_spec.reference["started_at"] = Time.now.to_i
    end
    machine_spec.save(action_handler)
  end
end
symbolize_keys(options) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 537
def symbolize_keys(options)
  options.each_with_object({}) do |(key, value), result|
    result[key.to_sym] = value
  end
end
volume_for(_volume_spec) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 622
def volume_for(_volume_spec)
  raise "Immplement me in #{self.class.name}"
end
wait_for_transport(action_handler, machine_spec, machine_options, server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 441
def wait_for_transport(action_handler, machine_spec, machine_options, server)
  transport = transport_for(machine_spec, machine_options, server, action_handler)
  unless transport.available?
    if action_handler.should_perform_actions
      Retryable.retryable(RETRYABLE_OPTIONS) do |retries, _exception|
        action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be connectable (transport up and running), API attempt #{retries + 1}/#{RETRYABLE_OPTIONS[:tries]} ..."

        _self = self

        server.wait_for(remaining_wait_time(machine_spec, machine_options)) do
          transport.available?
        end
      end
      action_handler.report_progress "#{machine_spec.name} is now connectable"
    end
  end
end
wait_until_ready(action_handler, machine_spec, machine_options, server) click to toggle source
# File lib/chef/provisioning/fog_driver/driver.rb, line 429
def wait_until_ready(action_handler, machine_spec, machine_options, server)
  unless server.ready?
    if action_handler.should_perform_actions
      Retryable.retryable(RETRYABLE_OPTIONS) do |retries, _exception|
        action_handler.report_progress "waiting for #{machine_spec.name} (#{server.id} on #{driver_url}) to be ready, API attempt #{retries + 1}/#{RETRYABLE_OPTIONS[:tries]} ..."
        server.wait_for(remaining_wait_time(machine_spec, machine_options)) { ready? }
      end
      action_handler.report_progress "#{machine_spec.name} is now ready"
    end
  end
end