lib/kitchen/driver/ec2.rb in kitchen-ec2-3.17.0 vs lib/kitchen/driver/ec2.rb in kitchen-ec2-3.17.1
- old
+ new
@@ -43,24 +43,21 @@
require "time" unless defined?(Time)
require "etc" unless defined?(Etc)
require "socket" unless defined?(Socket)
module Kitchen
-
module Driver
-
# Amazon EC2 driver for Test Kitchen.
#
# @author Fletcher Nichol <fnichol@nichol.ca>
class Ec2 < Kitchen::Driver::Base
-
kitchen_driver_api_version 2
plugin_version Kitchen::Driver::EC2_VERSION
default_config :region, ENV["AWS_REGION"] || "us-east-1"
- default_config :shared_credentials_profile, ENV["AWS_PROFILE"]
+ default_config :shared_credentials_profile, ENV.fetch("AWS_PROFILE", nil)
default_config :availability_zone, nil
default_config :instance_type, &:default_instance_type
default_config :ebs_optimized, false
default_config :delete_on_termination, true
default_config :security_group_ids, nil
@@ -80,18 +77,18 @@
default_config :spot_wait, 60
default_config :retryable_sleep, 5
default_config :aws_access_key_id, nil
default_config :aws_secret_access_key, nil
default_config :aws_session_token, nil
- default_config :aws_ssh_key_id, ENV["AWS_SSH_KEY_ID"]
+ default_config :aws_ssh_key_id, ENV.fetch("AWS_SSH_KEY_ID", nil)
default_config :aws_ssh_key_type, "rsa"
default_config :image_id, &:default_ami
default_config :image_search, nil
default_config :username, nil
default_config :associate_public_ip, nil
default_config :interface, nil
- default_config :http_proxy, ENV["HTTPS_PROXY"] || ENV["HTTP_PROXY"]
+ default_config :http_proxy, ENV["HTTPS_PROXY"] || ENV.fetch("HTTP_PROXY", nil)
default_config :retry_limit, 3
default_config :tenancy, "default"
default_config :instance_initiated_shutdown_behavior, nil
default_config :ssl_verify_peer, true
default_config :skip_cost_warning, false
@@ -192,14 +189,14 @@
exit!
end
end
# empty keys cause failures when tagging and they make no sense
- validations[:tags] = lambda do |attr, val, _driver|
+ validations[:tags] = lambda do |_attr, val, _driver|
# if someone puts the tags each on their own line it's an array not a hash
# @todo we should probably just do the right thing and support this format
- if val.class == Array
+ if val.instance_of?(Array)
warn "AWS instance tags must be specified as a single hash, not a tag " \
"on each line. Example: {:foo => 'bar', :bar => 'foo'}"
exit!
end
end
@@ -245,17 +242,17 @@
allocate_host unless host_available?
info("Auto placement on one dedicated host out of: #{hosts_with_capacity.map(&:host_id).join(", ")}")
end
- if config[:spot_price]
- # Spot instance when a price is set
- server = with_request_limit_backoff(state) { submit_spots }
- else
- # On-demand instance
- server = with_request_limit_backoff(state) { submit_server }
- end
+ server = if config[:spot_price]
+ # Spot instance when a price is set
+ with_request_limit_backoff(state) { submit_spots }
+ else
+ # On-demand instance
+ with_request_limit_backoff(state) { submit_server }
+ end
info("Instance <#{server.id}> requested.")
with_request_limit_backoff(state) do
logging_proc = ->(attempts) { info("Polling AWS for existence, attempt #{attempts}...") }
server.wait_until_exists(before_attempt: logging_proc)
end
@@ -267,11 +264,11 @@
# Waiting can fail, so we have to retry on that.
Retryable.retryable(
tries: 10,
sleep: lambda { |n| [2**n, 30].min },
on: ::Aws::EC2::Errors::InvalidInstanceIDNotFound
- ) do |r, _|
+ ) do |_r, _|
wait_until_ready(server, state)
end
info("EC2 instance <#{state[:server_id]}> ready (hostname: #{state[:hostname]}).")
instance.transport.connection(state).wait_until_ready
@@ -318,14 +315,14 @@
# Clean up any auto-created security groups or keys.
delete_security_group(state)
delete_key(state)
# Clean up dedicated hosts matching instance_type and unused (if allowed)
- if config[:tenancy] == "host" && allow_deallocate_host?
- empty_hosts = hosts_with_capacity.select { |host| host_unused?(host) }
- empty_hosts.each { |host| deallocate_host(host.host_id) }
- end
+ return unless config[:tenancy] == "host" && allow_deallocate_host?
+
+ empty_hosts = hosts_with_capacity.select { |host| host_unused?(host) }
+ empty_hosts.each { |host| deallocate_host(host.host_id) }
end
def image
return @image if defined?(@image)
@@ -425,11 +422,11 @@
# Take one config and expand to multiple configs
def expand_config(conf, key)
configs = []
- if conf[key] && conf[key].is_a?(Array)
+ if conf[key].is_a?(Array)
values = conf[key]
values.each do |value|
new_config = conf.clone
new_config[key] = value
configs.push new_config
@@ -497,15 +494,15 @@
def submit_spot
debug("Creating EC2 Spot Instance..")
instance_data = instance_generator.ec2_instance_data
config_spot_price = config[:spot_price].to_s
- if %w{ondemand on-demand}.include?(config_spot_price)
- spot_price = ""
- else
- spot_price = config_spot_price
- end
+ spot_price = if %w{ondemand on-demand}.include?(config_spot_price)
+ ""
+ else
+ config_spot_price
+ end
spot_options = {
# Must use one-time in order to use instance_interruption_behavior=terminate
# spot_instance_type: "one-time", # default
# Must use instance_interruption_behavior=terminate in order to use block_duration_minutes
# instance_interruption_behavior: "terminate", # default
@@ -517,11 +514,11 @@
spot_options[:max_price] = spot_price
end
instance_data[:instance_market_options] = {
market_type: "spot",
- spot_options: spot_options,
+ spot_options:,
}
# The preferred way to create a spot instance is via request_spot_instances()
# However, it does not allow for tagging to occur at creation time.
# create_instances() allows creation of tagged spot instances, but does
@@ -549,11 +546,11 @@
# Euca instances often report ready before they have an IP
ready = aws_instance.exists? &&
aws_instance.state.name == "running" &&
hostname != "0.0.0.0"
- if ready && ( hostname.nil? || hostname == "" )
+ if ready && (hostname.nil? || hostname == "")
debug("Unable to detect hostname using interface_type #{config[:interface]}. Fallback to ordered mapping")
state[:hostname] = hostname(aws_instance, nil)
end
if ready && windows_os?
if instance.transport[:username] =~ /administrator/i &&
@@ -565,11 +562,11 @@
output = server.console_output.output
unless output.nil?
output = Base64.decode64(output)
debug "Console output: --- \n#{output}"
end
- ready = !!(output =~ /Windows is Ready to use/)
+ ready = !!(output.include?("Windows is Ready to use"))
end
end
ready
end
end
@@ -598,11 +595,11 @@
raise
end
end
def fetch_windows_admin_password(server, state)
- wait_with_destroy(server, state, "to fetch windows admin password") do |aws_instance|
+ wait_with_destroy(server, state, "to fetch windows admin password") do |_aws_instance|
enc = server.client.get_password_data(
instance_id: state[:server_id]
).password_data
# Password data is blank until password is available
!enc.nil? && !enc.empty?
@@ -653,11 +650,11 @@
raise Kitchen::UserError, "Invalid interface [#{interface_type}]"
end
server.send(interface_type)
else
potential_hostname = nil
- INTERFACE_TYPES.values.each do |type|
+ INTERFACE_TYPES.each_value do |type|
potential_hostname ||= server.send(type)
# AWS returns an empty string if the dns name isn't populated yet
potential_hostname = nil if potential_hostname == ""
end
potential_hostname
@@ -715,11 +712,11 @@
Set-ItemProperty -Name LocalAccountTokenFilterPolicy -Path HKLM:\\software\\Microsoft\\Windows\\CurrentVersion\\Policies\\system -Value 1
EOH
# Preparing custom static admin user if we defined something other than Administrator
custom_admin_script = ""
- if !(instance.transport[:username] =~ /administrator/i) && instance.transport[:password]
+ if instance.transport[:username] !~ /administrator/i && instance.transport[:password]
custom_admin_script = Kitchen::Util.outdent!(<<-EOH)
"Disabling Complex Passwords" >> $logfile
$seccfg = [IO.Path]::GetTempFileName()
& secedit.exe /export /cfg $seccfg >> $logfile
(Get-Content $seccfg) | Foreach-Object {$_ -replace "PasswordComplexity\\s*=\\s*1", "PasswordComplexity = 0"} | Set-Content $seccfg
@@ -757,11 +754,11 @@
end
def image_info(image)
root_device = image.block_device_mappings
.find { |b| b.device_name == image.root_device_name }
- volume_type = " #{root_device.ebs.volume_type}" if root_device && root_device.ebs
+ volume_type = " #{root_device.ebs.volume_type}" if root_device&.ebs
" Architecture: #{image.architecture}," \
" Virtualization: #{image.virtualization_type}," \
" Storage: #{image.root_device_type}#{volume_type}," \
" Created: #{image.creation_date}"
@@ -811,10 +808,21 @@
end
# Create the SG.
params = {
group_name: "kitchen-#{Array.new(8) { rand(36).to_s(36) }.join}",
description: "Test Kitchen for #{instance.name} by #{Etc.getlogin || "nologin"} on #{Socket.gethostname}",
+ tag_specifications: [
+ {
+ resource_type: "security-group",
+ tags: [
+ {
+ key: "created-by",
+ value: "test-kitchen",
+ },
+ ],
+ },
+ ],
}
params[:vpc_id] = vpc_id if vpc_id
resp = ec2.client.create_security_group(params)
state[:auto_security_group_id] = resp.group_id
info("Created automatic security group #{state[:auto_security_group_id]}")
@@ -827,11 +835,11 @@
{
ip_protocol: "tcp",
from_port: port,
to_port: port,
ip_ranges: Array(config[:security_group_cidr_ip]).map do |cidr_ip|
- { cidr_ip: cidr_ip }
+ { cidr_ip: }
end,
}
end
)
end
@@ -849,18 +857,32 @@
name_parts = [
instance.name.gsub(/\W/, ""),
(Etc.getlogin || "nologin").gsub(/\W/, ""),
Socket.gethostname.gsub(/\W/, "")[0..20],
Time.now.utc.iso8601,
- Array.new(8) { rand(36).to_s(36) }.join(""),
+ Array.new(8) { rand(36).to_s(36) }.join,
]
# In a perfect world this would generate the key locally and use ImportKey
# instead for better security, but given the use case that is very likely
# to rapidly exhaust local entropy by creating a lot of keys. So this is
# probably fine. If you want very high security, probably don't use this
# feature anyway.
- resp = ec2.client.create_key_pair(key_name: "kitchen-#{name_parts.join("-")}", key_type: config[:aws_ssh_key_type])
+ resp = ec2.client.create_key_pair(
+ key_name: "kitchen-#{name_parts.join("-")}",
+ key_type: config[:aws_ssh_key_type],
+ tag_specifications: [
+ {
+ resource_type: "key-pair",
+ tags: [
+ {
+ key: "created-by",
+ value: "test-kitchen",
+ },
+ ],
+ },
+ ]
+ )
state[:auto_key_id] = resp.key_name
info("Created automatic key pair #{state[:auto_key_id]}")
# Write the key out with safe permissions
key_path = "#{config[:kitchen_root]}/.kitchen/#{instance.name}.pem"
File.open(key_path, File::WRONLY | File::CREAT | File::EXCL, 00600) do |f|
@@ -890,11 +912,11 @@
end
else
puts "ENI #{config[:elastic_network_interface_id]} already attached."
end
rescue ::Aws::EC2::Errors::InvalidNetworkInterfaceIDNotFound => e
- warn("#{e}")
+ warn(e.to_s)
end
end
# Clean up a temporary security group for this instance.
#
@@ -920,9 +942,8 @@
info("Removing automatic key pair #{state[:auto_key_id]}")
ec2.client.delete_key_pair(key_name: state[:auto_key_id])
state.delete(:auto_key_id)
File.unlink("#{config[:kitchen_root]}/.kitchen/#{instance.name}.pem")
end
-
end
end
end