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