lib/kitchen/driver/ec2.rb in kitchen-ec2-2.1.0 vs lib/kitchen/driver/ec2.rb in kitchen-ec2-2.2.0

- old
+ new

@@ -1,10 +1,11 @@ # -*- encoding: utf-8 -*- # # Author:: Fletcher Nichol (<fnichol@nichol.ca>) # -# Copyright (C) 2015, Fletcher Nichol +# Copyright:: 2016-2018, Chef Software, Inc. +# Copyright:: 2015-2018, Fletcher Nichol # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # @@ -44,11 +45,11 @@ module Driver # Amazon EC2 driver for Test Kitchen. # # @author Fletcher Nichol <fnichol@nichol.ca> - class Ec2 < Kitchen::Driver::Base # rubocop:disable Metrics/ClassLength + class Ec2 < Kitchen::Driver::Base kitchen_driver_api_version 2 plugin_version Kitchen::Driver::EC2_VERSION @@ -101,12 +102,13 @@ driver.warn "WARN: The driver[#{driver.class.name}] config key `#{old_key}` " \ "is deprecated, please use `#{new_key}`" end def self.validation_error(driver, old_key, new_key) - raise "ERROR: The driver[#{driver.class.name}] config key `#{old_key}` " \ + warn "ERROR: The driver[#{driver.class.name}] config key `#{old_key}` " \ "has been removed, please use `#{new_key}`" + exit! end # TODO: remove these in 1.1 deprecated_configs = [:ebs_volume_size, :ebs_delete_on_termination, :ebs_device_name] deprecated_configs.each do |d| @@ -140,41 +142,65 @@ unless val.nil? validation_error(driver, attr, "instance_type") end end + # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/dedicated-instance.html + validations[:tenancy] = lambda do |attr, val, _driver| + unless %w{default host dedicated}.include?(val) + warn "'#{val}' is an invalid value for option '#{attr}'. " \ + "Valid values are 'default', 'host', or 'dedicated'." + exit! + end + end + # The access key/secret are now using the priority list AWS uses # Providing these inside the .kitchen.yml is no longer recommended validations[:aws_access_key_id] = lambda do |attr, val, _driver| unless val.nil? - raise "#{attr} is no longer valid, please use " \ - "ENV['AWS_ACCESS_KEY_ID'] or ~/.aws/credentials. See " \ + warn "#{attr} is no longer a valid config option, please use " \ + "ENV['AWS_ACCESS_KEY_ID'] or ~/.aws/credentials. See " \ "the README for more details" + exit! end end validations[:aws_secret_access_key] = lambda do |attr, val, _driver| unless val.nil? - raise "#{attr} is no longer valid, please use " \ - "ENV['AWS_SECRET_ACCESS_KEY'] or ~/.aws/credentials. See " \ + warn "#{attr} is no longer a valid config option, please use " \ + "ENV['AWS_SECRET_ACCESS_KEY'] or ~/.aws/credentials. See " \ "the README for more details" + exit! end end validations[:aws_session_token] = lambda do |attr, val, _driver| unless val.nil? - raise "#{attr} is no longer valid, please use " \ - "ENV['AWS_SESSION_TOKEN'] or ~/.aws/credentials. See " \ + warn "#{attr} is no longer a valid config option, please use " \ + "ENV['AWS_SESSION_TOKEN'] or ~/.aws/credentials. See " \ "the README for more details" + exit! end end validations[:instance_initiated_shutdown_behavior] = lambda do |attr, val, _driver| unless [nil, "stop", "terminate"].include?(val) - raise "'#{val}' is an invalid value for option '#{attr}'. " \ + warn "'#{val}' is an invalid value for option '#{attr}'. " \ "Valid values are 'stop' or 'terminate'" + exit! end end - def create(state) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength + # empty keys cause failures when tagging and they make no sense + 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 + 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 + + def create(state) return if state[:server_id] update_username(state) info(Kitchen::Util.outdent!(<<-END)) unless config[:skip_cost_warning] If you are not using an account that qualifies under the AWS @@ -209,11 +235,11 @@ end end # See https://github.com/aws/aws-sdk-ruby/issues/859 # Tagging can fail with a NotFound error even though we waited until the server exists - # Waiting can also fail, so we have to also retry on that. If it means we re-tag the + # Waiting can also fail, so we have to also retry on that. If it means we re-tag the # instance, so be it. # Tagging an instance is possible before volumes are attached. Tagging the volumes after # instance creation is consistent. Retryable.retryable( :tries => 10, @@ -348,11 +374,11 @@ end def update_username(state) # BUG: With the following equality condition on username, if the user specifies 'root' # as the transport's username then we will overwrite that value with one from the standard - # platform definitions. This seems difficult to handle here as the default username is + # platform definitions. This seems difficult to handle here as the default username is # provided by the underlying transport classes, and is often non-nil (eg; 'root'), leaving # us no way to distinguish a user-set value from the transport's default. # See https://github.com/test-kitchen/kitchen-ec2/pull/273 if actual_platform && instance.transport[:username] == instance.transport.class.defaults[:username] @@ -386,11 +412,11 @@ instance_data[:min_count] = 1 instance_data[:max_count] = 1 ec2.create_instance(instance_data) end - def submit_spot(state) # rubocop:disable Metrics/AbcSize + def submit_spot(state) debug("Creating EC2 Spot Instance..") spot_request_id = create_spot_request # deleting the instance cancels the request, but deleting the request # does not affect the instance @@ -426,20 +452,23 @@ end def tag_server(server) if config[:tags] && !config[:tags].empty? tags = config[:tags].map do |k, v| - { :key => k, :value => v } + # we convert the value to a string because + # nils should be passed as an empty String + # and Integers need to be represented as Strings + { :key => k, :value => v.to_s } end server.create_tags(:tags => tags) end end def tag_volumes(server) if config[:tags] && !config[:tags].empty? tags = config[:tags].map do |k, v| - { :key => k, :value => v } + { :key => k, :value => v.to_s } end server.volumes.each do |volume| volume.create_tags(:tags => tags) end end @@ -485,11 +514,11 @@ end ready end end - # Poll a block, waiting for it to return true. If it does not succeed + # Poll a block, waiting for it to return true. If it does not succeed # within the configured time we destroy the instance to save people money def wait_with_destroy(server, state, status_msg, &block) wait_log = proc do |attempts| c = attempts * config[:retryable_sleep] t = config[:retryable_tries] * config[:retryable_sleep] @@ -510,11 +539,10 @@ destroy(state) raise end end - # rubocop:disable Lint/UnusedBlockArgument def fetch_windows_admin_password(server, state) 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 @@ -525,11 +553,10 @@ server.decrypt_windows_password(File.expand_path(state[:ssh_key] || instance.transport[:ssh_key])) end state[:password] = pass info("Retrieved Windows password for instance <#{state[:server_id]}>.") end - # rubocop:enable Lint/UnusedBlockArgument def with_request_limit_backoff(state) retries = 0 begin yield @@ -542,11 +569,11 @@ retry end end # - # Ordered mapping from config name to Fog name. Ordered by preference + # Ordered mapping from config name to Fog name. Ordered by preference # when looking up hostname. # INTERFACE_TYPES = { "dns" => "public_dns_name", @@ -554,12 +581,12 @@ "private" => "private_ip_address", "private_dns" => "private_dns_name", } # - # Lookup hostname of provided server. If interface_type is provided use - # that interface to lookup hostname. Otherwise, try ordered list of + # Lookup hostname of provided server. If interface_type is provided use + # that interface to lookup hostname. Otherwise, try ordered list of # options. # def hostname(server, interface_type = nil) if interface_type interface_type = INTERFACE_TYPES.fetch(interface_type) do @@ -582,11 +609,10 @@ # def sudo_command instance.provisioner[:sudo] ? instance.provisioner[:sudo_command].to_s : "" end - # rubocop:disable Metrics/MethodLength, Metrics/LineLength def create_ec2_json(state) if windows_os? cmd = "New-Item -Force C:\\chef\\ohai\\hints\\ec2.json -ItemType File" else debug "Using sudo_command='#{sudo_command}' for ohai hints" @@ -647,10 +673,9 @@ Set-ItemProperty -Name LocalAccountTokenFilterPolicy -Path HKLM:\\software\\Microsoft\\Windows\\CurrentVersion\\Policies\\system -Value 1 #{custom_admin_script} </powershell> EOH end - # rubocop:enable Metrics/MethodLength, Metrics/LineLength def show_chosen_image # Print some debug stuff debug("Image for #{instance.name}: #{image.name}. #{image_info(image)}") if actual_platform