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