class Kitchen::Driver::Softlayer

Softlayer driver for Kitchen. rubocop:disable Metrics/LineLength

Public Instance Methods

create(state) click to toggle source

rubocop:disable Metrics/AbcSize

# File lib/kitchen/driver/softlayer.rb, line 82
def create(state)
  config_server_name
  config[:disable_ssl_validation] && disable_ssl_validation
  config[:fqdn] = "#{config[:server_name]}.#{config[:domain]}"
  debug "fqdn: #{config[:fqdn]}"
  debug "server_name: #{config[:server_name]}"
  debug "server_name_prefix: #{config[:server_name_prefix]}"
  server = create_server if config[:fqdn].include?(config[:default_name]) || !find_server(config[:fqdn])
  state[:server_id] = server.id
  info "Softlayer instance <#{state[:server_id]}> created."
  server.wait_for do
    print '.'
    ready?
  end
  info "\n(server ready)"
  tag_server(server)
  # set state[:hostname] to be able to setup ssh using Fog::SSH
  state[:hostname] = if config[:ssh_via_hostname]
                       config[:server_name]
                     elsif config[:alternate_ip]
                       config[:alternate_ip]
                     else
                       get_ip(server)
                     end
  setup_ssh(server, state)
  wait_for_ssh_key_access(state)
rescue Fog::Errors::Error, Excon::Errors::Error => ex
  raise ActionFailed, ex.message
end
destroy(state) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 112
def destroy(state)
  return if state[:server_id].nil?

  config[:disable_ssl_validation] && disable_ssl_validation
  server = compute.servers.get(state[:server_id])
  server.destroy unless server.nil? || server.id.nil?
  wait_for_server_to_delete(state) if config[:destroy_wait]
  info "Softlayer instance <#{state[:server_id]}> destroyed."
  state.delete(:server_id)
  state.delete(:server_name)
end

Private Instance Methods

compute() click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 168
def compute
  @compute_connection ||= Fog::Compute.new(
    provider: :softlayer,
    softlayer_username: config[:softlayer_username],
    softlayer_api_key: config[:softlayer_api_key],
    softlayer_default_datacenter: config[:softlayer_datacenter],
    softlayer_default_domain: config[:softlayer_domain]
  )
end
config_server_name() click to toggle source

Set the proper server name in the config

# File lib/kitchen/driver/softlayer.rb, line 127
def config_server_name
  return if config[:server_name]

  config[:server_name] = if config[:server_name_prefix]
                           server_name_prefix(config[:server_name_prefix])
                         else
                           config[:default_name] = default_name
                           config[:default_name].to_s
                         end
end
create_server() click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 227
def create_server
  server_def = init_configuration
  #   TODO: figure out network options
  #    if config[:network_ref]
  #      networks = [].concat([config[:network_ref]])
  #      server_def[:nics] = networks.flatten.map do |net|
  #        { 'net_id' => find_network(net).id }
  #      end
  #    end
  %i[
    username
    password
    port
    domain
    fqdn
    cpu
    ram
    disk
    flavor_id
    bare_metal
    os_code
    image_id
    ephemeral_storage
    network_components
    account_id
    single_tenant
    global_identifier
    tags
    user_data
    uid
    vlan
    private_vlan
    provision_script
  ].each do |c|
    server_def[c] = optional_config(c) if config[c]
  end
  debug "server_def: #{server_def}"
  compute.servers.create(server_def)
end
default_name() click to toggle source

Generate what should be a unique server name up to 63 total chars Base name: 15 Username: 15 Hostname: 23 Random string: 7 Separators: 3

Total: 63

# File lib/kitchen/driver/softlayer.rb, line 299
def default_name
  [
    instance.name.gsub(/\W/, '')[0..14],
    (Etc.getlogin || 'nologin').gsub(/\W/, '')[0..14],
    Socket.gethostname.gsub(/\W/, '')[0..22],
    Array.new(7) { rand(36).to_s(36) }.join
  ].join('-')
end
disable_ssl_validation() click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 387
def disable_ssl_validation
  require 'excon'
  Excon.defaults[:ssl_verify_peer] = false
end
find_image(image_name) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 196
def find_image(image_name)
  id = nil
  images = compute.images.all
  images.each do |image|
    if image.name == image_name
      id = image.id
      debug "Found public image #{id} for name #{image_name}"
    end
  end
  images = compute.images.private
  images.each do |image|
    if image.name == image_name
      id = image.id
      debug "Found private image #{id} for name #{image_name}"
    end
  end
  raise "No image found with name #{image_name}" if id.nil?
  id
end
find_network(vlan) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 216
def find_network(vlan)
  debug "Looking for network for vlan number #{vlan}"
  response = network.list_networks
  response.body.each do |r|
    next unless r['vlanNumber'] == vlan
    debug "Found network id #{r['id']} for vlan number #{r['vlanNumber']}"
    return r['id']
  end
  raise "No network found for vlan number #{r['vlanNumber']}"
end
find_server(fqdn) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 186
def find_server(fqdn)
  s = nil
  srvs = compute.servers.all.select { |x| x.fqdn == fqdn }
  unless srvs.empty?
    s = srvs[0]
    info "Server with fqdn #{fqdn} already created"
  end
  s
end
get_ip(server) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 342
def get_ip(server)
  pub, priv = get_public_private_ips(server)
  pub, priv = parse_ips(pub, priv)
  pub[config[:public_ip_order].to_i] ||
    priv[config[:private_ip_order].to_i] ||
    raise(ActionFailed, 'Could not find an IP')
  return priv[0] if config[:use_private_ip_with_public_network] && !config[:private_network_only]
  return priv[0] if config[:private_network_only]
  pub[0]
end
get_public_private_ips(server) click to toggle source

TODO: code has support for multiple ips but not used.

# File lib/kitchen/driver/softlayer.rb, line 336
def get_public_private_ips(server)
  pub = server.public_ip
  priv = server.private_ip
  [pub, priv]
end
init_configuration() click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 267
def init_configuration
  {
    name: config[:server_name],
    key_pairs: [compute.key_pairs.by_label(config[:key_name])],
    datacenter: config[:datacenter],
    hourly_billing_flag: config[:hourly_billing_flag],
    private_network_only: config[:private_network_only]
  }
end
network() click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 178
def network
  @network_connection ||= Fog::Network.new(
    provider: :softlayer,
    softlayer_username: config[:softlayer_username],
    softlayer_api_key: config[:softlayer_api_key]
  )
end
optional_config(c) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 277
def optional_config(c)
  case c
  when :vlan
    find_network(config[c])
  when :private_vlan
    find_network(config[c])
  when :image_id
    return config[c] if config[c].match?('^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
    find_image(config[c])
  else
    config[c]
  end
end
parse_ips(pub, priv) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 353
def parse_ips(pub, priv)
  pub = Array(pub)
  priv = Array(priv)
  if config[:use_ipv6]
    [pub, priv].each { |n| n.select! { |i| IPAddr.new(i).ipv6? } }
  else
    [pub, priv].each { |n| n.select! { |i| IPAddr.new(i).ipv4? } }
  end
  [pub, priv]
end
server_name_prefix(server_name_prefix) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 308
def server_name_prefix(server_name_prefix)
  # Generate what should be a unique server name with given prefix
  # of up to 63 total chars
  #
  # Provided prefix:  variable, max 54
  # Separator:        1
  # Random string:    8
  # ===================
  # Max:              63
  #
  if server_name_prefix.length > 54
    warn 'Server name prefix too long, truncated to 54 characters'
    server_name_prefix = server_name_prefix[0..53]
  end

  server_name_prefix.gsub!(/\W/, '')

  if server_name_prefix.empty?
    warn 'Server name prefix empty or invalid; using fully generated name'
    default_name
  else
    random_suffix = ('a'..'z').to_a.sample(8).join
    server_name_prefix + '-' + random_suffix
  end
end
setup_ssh(server, state) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 364
def setup_ssh(server, state)
  tcp_check(state)
  info "Using Softlayer keypair <#{config[:key_name]}>"
  info "Using private SSH key <#{config[:ssh_key]}>"
  state[:ssh_key] = config[:ssh_key]
  # we don't call this as key_name must be set.
  do_ssh_setup(state, config, server) unless config[:key_name]
end
tag_server(server) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 392
def tag_server(server)
  server.add_tags(config[:tags])
end
tcp_check(state) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 373
def tcp_check(state)
  # allow driver config to bypass SSH tcp check -- because
  # it doesn't respect ssh_config values that might be required
  if config[:no_ssh_tcp_check]
    sleep(config[:no_ssh_tcp_check_sleep])
  else
    debug("wait_for_sshd hostname: #{state[:hostname]},username: #{config[:username]},port: #{config[:port]}")
    wait_for_sshd(state[:hostname],
                  config[:username],
                  port: config[:port])
  end
  info '(ssh ready)'
end
wait_for_server_to_delete(state) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 138
def wait_for_server_to_delete(state)
  ((config[:destroy_timeout].to_i / 15).floor).times do
    info 'Deleting server in softlayer'
    sleep 15
    server = compute.servers.get(state[:server_id])
    return if server.nil? || server.id.nil?
  end
  raise "#{config[:destroy_timeout]} seconds went by and server not deleted by softlayer"
end
wait_for_ssh_key_access(state) click to toggle source
# File lib/kitchen/driver/softlayer.rb, line 148
def wait_for_ssh_key_access(state)
  new_state = build_ssh_args(state)
  new_state[2][:number_of_password_prompts] = 0
  info 'Checking ssh key authentication'

  (config[:ssh_timeout].to_i).times do
    ssh = Fog::SSH.new(*new_state)
    begin
      ssh.run([%(uname -a)])
    rescue StandardError => e
      info "Server not yet accepting SSH key: #{e.message}"
      sleep 1
    else
      info 'SSH key authetication successful'
      return
    end
  end
  raise "#{config[:ssh_timeout]} seconds went by and we couldn't connect, somethings broken"
end