kitchen-ec2 3.5.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4591ccc89a7ed2c996442b609b0ef4fb96fb868e5d3512e4a3d4283d0b883b07
4
- data.tar.gz: 1ba531086886fddc2f27f9e25843c94df565a5cd86f7b97562a026036709e51a
3
+ metadata.gz: 34d9b99c9fe048be513d606f50abb1318d6aa71541fa720308770311d1066a75
4
+ data.tar.gz: abb3e57324a99271ade0ea238ac759e0790934b727729d74ab6cc7f0887f6997
5
5
  SHA512:
6
- metadata.gz: 9703c6b265c6ab38ec111454b1a620cc8e395e7230316d9ee84e3b56c9a8a77aceffc2537cc1a560ea7c4be2599fa23fa0521cb6a781ac54ba7856eb8bd22bbe
7
- data.tar.gz: 5727ade2a7a41f52e530b999c27198a67631925217c303681af2cef952e0a714e933138d4d475c3c224f148c20dd0abc080a9d0d2f43c65d27c583ffc555908a
6
+ metadata.gz: f829ebf7964c3588642d6cfd20ca78152098bc3ed700ca839d443c934b46b5ee61afcb7ee713ef2b2cc4acc9921d550c03e1de2914be3265852d56b554bbe050
7
+ data.tar.gz: 9600e83fcde5acf5e9e98b91cf9e44033fa1aa405aec192ee5774fc1ea6ec4afc8f03cb485be8766a1f0804a4ab9a349f8353851c92348a53c60f8b5c3f3a2c5
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  #
3
2
  # Author:: Tyler Ball (<tball@chef.io>)
4
3
  #
@@ -49,14 +48,24 @@ module Kitchen
49
48
  ::Aws.config.update(retry_limit: retry_limit) unless retry_limit.nil?
50
49
  end
51
50
 
51
+ # create a new AWS EC2 instance
52
+ # @param options [Hash] has of instance options
53
+ # @see https://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Resource.html#create_instances-instance_method
54
+ # @return [Aws::EC2::Instance]
52
55
  def create_instance(options)
53
56
  resource.create_instances(options).first
54
57
  end
55
58
 
59
+ # get an instance object given an id
60
+ # @param id [String] aws instance id
61
+ # @return [Aws::EC2::Instance]
56
62
  def get_instance(id)
57
63
  resource.instance(id)
58
64
  end
59
65
 
66
+ # get an instance object given a spot request ID
67
+ # @param request_id [String] aws spot instance id
68
+ # @return [Aws::EC2::Instance]
60
69
  def get_instance_from_spot_request(request_id)
61
70
  resource.instances(
62
71
  filters: [{
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  #
3
2
  # Author:: Tyler Ball (<tball@chef.io>)
4
3
  #
@@ -17,7 +16,7 @@
17
16
  # See the License for the specific language governing permissions and
18
17
  # limitations under the License.
19
18
 
20
- require "base64"
19
+ require "base64" unless defined?(Base64)
21
20
  require "aws-sdk-ec2"
22
21
 
23
22
  module Kitchen
@@ -39,8 +38,10 @@ module Kitchen
39
38
  @logger = logger
40
39
  end
41
40
 
42
- # Transform the provided config into the hash to send to AWS. Some fields
41
+ # Transform the provided kitchen config into the hash we'll use to create the aws instance
43
42
  # can be passed in null, others need to be ommitted if they are null
43
+ # Some fields can be passed in null, others need to be ommitted if they are null
44
+ # @return [Hash]
44
45
  def ec2_instance_data # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
45
46
  # Support for looking up security group id and subnet id using tags.
46
47
  vpc_id = nil
@@ -57,7 +58,7 @@ module Kitchen
57
58
  raise "The subnet tagged '#{config[:subnet_filter][:tag]}:#{config[:subnet_filter][:value]}' does not exist!" unless subnets.any?
58
59
 
59
60
  # => Select the least-populated subnet if we have multiple matches
60
- subnet = subnets.sort_by { |s| s[:available_ip_address_count] }.last
61
+ subnet = subnets.max_by { |s| s[:available_ip_address_count] }
61
62
  vpc_id = subnet.vpc_id
62
63
  config[:subnet_id] = subnet.subnet_id
63
64
  end
@@ -113,11 +114,25 @@ module Kitchen
113
114
  key_name: config[:aws_ssh_key_id],
114
115
  subnet_id: config[:subnet_id],
115
116
  private_ip_address: config[:private_ip_address],
117
+ min_count: 1,
118
+ max_count: 1,
116
119
  }
117
120
 
121
+ if config[:tags] && !config[:tags].empty?
122
+ tags = config[:tags].map do |k, v|
123
+ # we convert the value to a string because
124
+ # nils should be passed as an empty String
125
+ # and Integers need to be represented as Strings
126
+ { key: k, value: v.to_s }
127
+ end
128
+ instance_tag_spec = { resource_type: "instance", tags: tags }
129
+ volume_tag_spec = { resource_type: "volume", tags: tags }
130
+ i[:tag_specifications] = [instance_tag_spec, volume_tag_spec]
131
+ end
132
+
118
133
  availability_zone = config[:availability_zone]
119
134
  if availability_zone
120
- if availability_zone =~ /^[a-z]$/i
135
+ if /^[a-z]$/i.match?(availability_zone)
121
136
  availability_zone = "#{config[:region]}#{availability_zone}"
122
137
  end
123
138
  i[:placement] = { availability_zone: availability_zone.downcase }
@@ -160,7 +175,7 @@ module Kitchen
160
175
  end
161
176
  availability_zone = config[:availability_zone]
162
177
  if availability_zone
163
- if availability_zone =~ /^[a-z]$/i
178
+ if /^[a-z]$/i.match?(availability_zone)
164
179
  availability_zone = "#{config[:region]}#{availability_zone}"
165
180
  end
166
181
  i[:placement] = { availability_zone: availability_zone.downcase }
@@ -23,6 +23,8 @@ module Kitchen
23
23
  class Amazon < StandardPlatform
24
24
  StandardPlatform.platforms["amazon"] = self
25
25
 
26
+ # default username for this platform's ami
27
+ # @return [String]
26
28
  def username
27
29
  "ec2-user"
28
30
  end
@@ -37,7 +39,7 @@ module Kitchen
37
39
  end
38
40
 
39
41
  def self.from_image(driver, image)
40
- if image.name =~ /amzn-ami/i
42
+ if /amzn-ami/i.match?(image.name)
41
43
  image.name =~ /\b(\d+(\.\d+[\.\d])?)/i
42
44
  new(driver, "amazon", (Regexp.last_match || [])[1], image.architecture)
43
45
  end
@@ -23,6 +23,8 @@ module Kitchen
23
23
  class Amazon2 < StandardPlatform
24
24
  StandardPlatform.platforms["amazon2"] = self
25
25
 
26
+ # default username for this platform's ami
27
+ # @return [String]
26
28
  def username
27
29
  "ec2-user"
28
30
  end
@@ -37,7 +39,7 @@ module Kitchen
37
39
  end
38
40
 
39
41
  def self.from_image(driver, image)
40
- if image.name =~ /amzn2-ami/i
42
+ if /amzn2-ami/i.match?(image.name)
41
43
  image.name =~ /\b(\d+(\.\d+[\.\d])?)/i
42
44
  new(driver, "amazon2", (Regexp.last_match || [])[1], image.architecture)
43
45
  end
@@ -23,6 +23,16 @@ module Kitchen
23
23
  class Centos < StandardPlatform
24
24
  StandardPlatform.platforms["centos"] = self
25
25
 
26
+ CENTOS_OWNER_ID = "125523088429".freeze
27
+ PRODUCT_CODES = {
28
+ "6" => "6x5jmcajty9edm3f211pqjfn2",
29
+ "7" => "aw0evgkw8e5c1q413zgy5pjce",
30
+ # It appears that v8 is not published to the
31
+ # AWS marketplace and hence does not have a product code
32
+ }.freeze
33
+
34
+ # default username for this platform's ami
35
+ # @return [String]
26
36
  def username
27
37
  # Centos 6.x images use root as the username (but the "centos 6"
28
38
  # updateable image uses "centos")
@@ -32,10 +42,25 @@ module Kitchen
32
42
  end
33
43
 
34
44
  def image_search
45
+ # Version 8+ are published directly, not to the AWS marketplace. Use OWNER ID.
35
46
  search = {
36
- "owner-alias" => "aws-marketplace",
37
- "name" => ["CentOS Linux #{version}*", "CentOS-#{version}*-GA-*"],
47
+ "owner-id" => CENTOS_OWNER_ID,
48
+ "name" => ["CentOS #{version}*", "CentOS-#{version}*-GA-*"],
38
49
  }
50
+
51
+ if version && version.split(".").first.to_i < 8
52
+ # Versions <8 are published to the AWS marketplace and use a different naming convention
53
+ search = {
54
+ "owner-alias" => "aws-marketplace",
55
+ "name" => ["CentOS Linux #{version}*", "CentOS-#{version}*-GA-*"],
56
+ }
57
+ # For versions published to aws-marketplace, additionally filter on product code to
58
+ # avoid non-official AMIs. Can't use CentOS owner ID here, as the owner ID is that of aws marketplace.
59
+ # https://github.com/test-kitchen/kitchen-ec2/issues/456
60
+ PRODUCT_CODES.keys.each do |major_version|
61
+ search["product-code"] = PRODUCT_CODES[major_version] if version.start_with?(major_version)
62
+ end
63
+ end
39
64
  search["architecture"] = architecture if architecture
40
65
  search
41
66
  end
@@ -50,7 +75,7 @@ module Kitchen
50
75
  end
51
76
 
52
77
  def self.from_image(driver, image)
53
- if image.name =~ /centos/i
78
+ if /centos/i.match?(image.name)
54
79
  image.name =~ /\b(\d+(\.\d+)?)\b/i
55
80
  new(driver, "centos", (Regexp.last_match || [])[1], image.architecture)
56
81
  end
@@ -26,14 +26,17 @@ module Kitchen
26
26
  # 10/11 are listed last since we default to the first item in the hash
27
27
  # and 10/11 are not released yet. When they're released move them up
28
28
  DEBIAN_CODENAMES = {
29
+ 10 => "buster",
29
30
  9 => "stretch",
30
31
  8 => "jessie",
31
32
  7 => "wheezy",
32
33
  6 => "squeeze",
33
34
  11 => "bullseye",
34
- 10 => "buster",
35
+ 12 => "bookworm",
35
36
  }.freeze
36
37
 
38
+ # default username for this platform's ami
39
+ # @return [String]
37
40
  def username
38
41
  "admin"
39
42
  end
@@ -57,7 +60,7 @@ module Kitchen
57
60
  end
58
61
 
59
62
  def self.from_image(driver, image)
60
- if image.name =~ /debian/i
63
+ if /debian/i.match?(image.name)
61
64
  image.name =~ /\b(\d+|#{DEBIAN_CODENAMES.values.join("|")})\b/i
62
65
  version = (Regexp.last_match || [])[1]
63
66
  if version && version.to_i == 0
@@ -23,6 +23,8 @@ module Kitchen
23
23
  class Fedora < StandardPlatform
24
24
  StandardPlatform.platforms["fedora"] = self
25
25
 
26
+ # default username for this platform's ami
27
+ # @return [String]
26
28
  def username
27
29
  "fedora"
28
30
  end
@@ -37,7 +39,7 @@ module Kitchen
37
39
  end
38
40
 
39
41
  def self.from_image(driver, image)
40
- if image.name =~ /fedora/i
42
+ if /fedora/i.match?(image.name)
41
43
  image.name =~ /\b(\d+(\.\d+)?)\b/i
42
44
  new(driver, "fedora", (Regexp.last_match || [])[1], image.architecture)
43
45
  end
@@ -23,6 +23,8 @@ module Kitchen
23
23
  class Freebsd < StandardPlatform
24
24
  StandardPlatform.platforms["freebsd"] = self
25
25
 
26
+ # default username for this platform's ami
27
+ # @return [String]
26
28
  def username
27
29
  "ec2-user"
28
30
  end
@@ -39,7 +41,7 @@ module Kitchen
39
41
  end
40
42
 
41
43
  def self.from_image(driver, image)
42
- if image.name =~ /freebsd/i
44
+ if /freebsd/i.match?(image.name)
43
45
  image.name =~ /\b(\d+(\.\d+)?)\b/i
44
46
  new(driver, "freebsd", (Regexp.last_match || [])[1], image.architecture)
45
47
  end
@@ -29,6 +29,8 @@ module Kitchen
29
29
  super(driver, "rhel", version, architecture)
30
30
  end
31
31
 
32
+ # default username for this platform's ami
33
+ # @return [String]
32
34
  def username
33
35
  (version && version.to_f < 6.4) ? "root" : "ec2-user"
34
36
  end
@@ -43,11 +45,18 @@ module Kitchen
43
45
  end
44
46
 
45
47
  def self.from_image(driver, image)
46
- if image.name =~ /rhel/i
48
+ if /rhel/i.match?(image.name)
47
49
  image.name =~ /\b(\d+(\.\d+)?)/i
48
50
  new(driver, "rhel", (Regexp.last_match || [])[1], image.architecture)
49
51
  end
50
52
  end
53
+
54
+ def sort_by_version(images)
55
+ # First do a normal version sort
56
+ super(images)
57
+ # Now sort again, shunning Beta releases.
58
+ prefer(images) { |image| !image.name.match(/_Beta-/i) }
59
+ end
51
60
  end
52
61
  end
53
62
  end
@@ -23,6 +23,8 @@ module Kitchen
23
23
  class Ubuntu < StandardPlatform
24
24
  StandardPlatform.platforms["ubuntu"] = self
25
25
 
26
+ # default username for this platform's ami
27
+ # @return [String]
26
28
  def username
27
29
  "ubuntu"
28
30
  end
@@ -37,7 +39,7 @@ module Kitchen
37
39
  end
38
40
 
39
41
  def self.from_image(driver, image)
40
- if image.name =~ /ubuntu/i
42
+ if /ubuntu/i.match?(image.name)
41
43
  image.name =~ /\b(\d+(\.\d+)?)\b/i
42
44
  new(driver, "ubuntu", (Regexp.last_match || [])[1], image.architecture)
43
45
  end
@@ -23,6 +23,8 @@ module Kitchen
23
23
  class Windows < StandardPlatform
24
24
  StandardPlatform.platforms["windows"] = self
25
25
 
26
+ # default username for this platform's ami
27
+ # @return [String]
26
28
  def username
27
29
  "administrator"
28
30
  end
@@ -68,7 +70,7 @@ module Kitchen
68
70
  end
69
71
 
70
72
  def self.from_image(driver, image)
71
- if image.name =~ /Windows/i
73
+ if /Windows/i.match?(image.name)
72
74
  # 2008 R2 SP2
73
75
  if image.name =~ /(\b\d+)\W*(r\d+)?/i
74
76
  major, revision = (Regexp.last_match || [])[1], (Regexp.last_match || [])[2]
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  #
3
2
  # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
3
  #
@@ -17,8 +16,8 @@
17
16
  # See the License for the specific language governing permissions and
18
17
  # limitations under the License.
19
18
 
20
- require "benchmark"
21
- require "json"
19
+ require "benchmark" unless defined?(Benchmark)
20
+ require "json" unless defined?(JSON)
22
21
  require "kitchen"
23
22
  require_relative "ec2_version"
24
23
  require_relative "aws/client"
@@ -35,10 +34,10 @@ require_relative "aws/standard_platform/ubuntu"
35
34
  require_relative "aws/standard_platform/windows"
36
35
  require "aws-sdk-ec2"
37
36
  require "aws-sdk-core/waiters/errors"
38
- require "retryable"
39
- require "time"
40
- require "etc"
41
- require "socket"
37
+ require "retryable" unless defined?(Retryable)
38
+ require "time" unless defined?(Time)
39
+ require "etc" unless defined?(Etc)
40
+ require "socket" unless defined?(Socket)
42
41
 
43
42
  module Kitchen
44
43
 
@@ -227,7 +226,7 @@ module Kitchen
227
226
 
228
227
  if config[:spot_price]
229
228
  # Spot instance when a price is set
230
- server = with_request_limit_backoff(state) { submit_spots(state) }
229
+ server = with_request_limit_backoff(state) { submit_spots }
231
230
  else
232
231
  # On-demand instance
233
232
  server = with_request_limit_backoff(state) { submit_server }
@@ -238,32 +237,16 @@ module Kitchen
238
237
  server.wait_until_exists(before_attempt: logging_proc)
239
238
  end
240
239
 
240
+ state[:server_id] = server.id
241
+ info("EC2 instance <#{state[:server_id]}> created.")
242
+
241
243
  # See https://github.com/aws/aws-sdk-ruby/issues/859
242
- # Tagging can fail with a NotFound error even though we waited until the server exists
243
- # Waiting can also fail, so we have to also retry on that. If it means we re-tag the
244
- # instance, so be it.
245
- # Tagging an instance is possible before volumes are attached. Tagging the volumes after
246
- # instance creation is consistent.
244
+ # Waiting can fail, so we have to retry on that.
247
245
  Retryable.retryable(
248
246
  tries: 10,
249
247
  sleep: lambda { |n| [2**n, 30].min },
250
248
  on: ::Aws::EC2::Errors::InvalidInstanceIDNotFound
251
249
  ) do |r, _|
252
- info("Attempting to tag the instance, #{r} retries")
253
- tag_server(server)
254
-
255
- # Get information about the AMI (image) used to create the image.
256
- image_data = ec2.client.describe_images({ image_ids: [server.image_id] })[0][0]
257
-
258
- state[:server_id] = server.id
259
- info("EC2 instance <#{state[:server_id]}> created.")
260
-
261
- # instance-store backed images do not have attached volumes, so only
262
- # wait for the volumes to be ready if the instance EBS-backed.
263
- if image_data.root_device_type == "ebs"
264
- wait_until_volumes_ready(server, state)
265
- tag_volumes(server)
266
- end
267
250
  wait_until_ready(server, state)
268
251
  end
269
252
 
@@ -277,7 +260,7 @@ module Kitchen
277
260
 
278
261
  info("EC2 instance <#{state[:server_id]}> ready (hostname: #{state[:hostname]}).")
279
262
  instance.transport.connection(state).wait_until_ready
280
- create_ec2_json(state) if instance.provisioner.name =~ /chef/i
263
+ create_ec2_json(state) if /chef/i.match?(instance.provisioner.name)
281
264
  debug("ec2:create '#{state[:hostname]}'")
282
265
  rescue Exception
283
266
  # Clean up any auto-created security groups or keys on the way out.
@@ -297,13 +280,6 @@ module Kitchen
297
280
  warn("Received #{e}, instance was probably already destroyed. Ignoring")
298
281
  end
299
282
  end
300
- if state[:spot_request_id]
301
- debug("Deleting spot request <#{state[:server_id]}>")
302
- ec2.client.cancel_spot_instance_requests(
303
- spot_instance_request_ids: [state[:spot_request_id]]
304
- )
305
- state.delete(:spot_request_id)
306
- end
307
283
  # If we are going to clean up an automatic security group, we need
308
284
  # to wait for the instance to shut down. This slightly breaks the
309
285
  # subsystem encapsulation, sorry not sorry.
@@ -409,15 +385,14 @@ module Kitchen
409
385
  @instance_generator = Aws::InstanceGenerator.new(config, ec2, instance.logger)
410
386
  end
411
387
 
412
- # Fog AWS helper for creating the instance
388
+ # AWS helper for creating the instance
413
389
  def submit_server
414
390
  instance_data = instance_generator.ec2_instance_data
415
391
  debug("Creating EC2 instance in region #{config[:region]} with properties:")
416
392
  instance_data.each do |key, value|
417
393
  debug("- #{key} = #{value.inspect}")
418
394
  end
419
- instance_data[:min_count] = 1
420
- instance_data[:max_count] = 1
395
+
421
396
  ec2.create_instance(instance_data)
422
397
  end
423
398
 
@@ -445,10 +420,34 @@ module Kitchen
445
420
  configs
446
421
  end
447
422
 
448
- def submit_spots(state)
423
+ def submit_spots
449
424
  configs = [config]
450
425
  expanded = []
451
- keys = %i{instance_type subnet_id}
426
+ keys = %i{instance_type}
427
+
428
+ unless config[:subnet_filter]
429
+ # => Use explicitly specified subnets
430
+ keys << :subnet_id
431
+ else
432
+ # => Enable cascading through matching subnets
433
+ client = ::Aws::EC2::Client.new(region: config[:region])
434
+ subnets = client.describe_subnets(
435
+ filters: [
436
+ {
437
+ name: "tag:#{config[:subnet_filter][:tag]}",
438
+ values: [config[:subnet_filter][:value]],
439
+ },
440
+ ]
441
+ ).subnets
442
+ raise "A subnet matching '#{config[:subnet_filter][:tag]}:#{config[:subnet_filter][:value]}' does not exist!" unless subnets.any?
443
+
444
+ configs = subnets.map do |subnet|
445
+ new_config = config.clone
446
+ new_config[:subnet_id] = subnet.subnet_id
447
+ new_config[:subnet_filter] = nil
448
+ new_config
449
+ end
450
+ end
452
451
 
453
452
  keys.each do |key|
454
453
  configs.each do |conf|
@@ -462,7 +461,7 @@ module Kitchen
462
461
  configs.each do |conf|
463
462
  begin
464
463
  @config = conf
465
- return submit_spot(state)
464
+ return submit_spot
466
465
  rescue => e
467
466
  errs.append(e)
468
467
  end
@@ -470,86 +469,47 @@ module Kitchen
470
469
  raise ["Could not create a spot instance:", errs].flatten.join("\n")
471
470
  end
472
471
 
473
- def submit_spot(state)
472
+ def submit_spot
474
473
  debug("Creating EC2 Spot Instance..")
474
+ instance_data = instance_generator.ec2_instance_data
475
475
 
476
- spot_request_id = create_spot_request
477
- # deleting the instance cancels the request, but deleting the request
478
- # does not affect the instance
479
- state[:spot_request_id] = spot_request_id
480
- ec2.client.wait_until(
481
- :spot_instance_request_fulfilled,
482
- spot_instance_request_ids: [spot_request_id]
483
- ) do |w|
484
- w.max_attempts = config[:spot_wait] / config[:retryable_sleep]
485
- w.delay = config[:retryable_sleep]
486
- w.before_attempt do |attempts|
487
- c = attempts * config[:retryable_sleep]
488
- t = config[:spot_wait]
489
- info "Waited #{c}/#{t}s for spot request <#{spot_request_id}> to become fulfilled."
490
- end
491
- end
492
- ec2.get_instance_from_spot_request(spot_request_id)
493
- end
494
-
495
- def create_spot_request
496
- request_duration = config[:spot_wait]
497
476
  config_spot_price = config[:spot_price].to_s
498
477
  if %w{ondemand on-demand}.include?(config_spot_price)
499
478
  spot_price = ""
500
479
  else
501
480
  spot_price = config_spot_price
502
481
  end
503
- request_data = {
504
- spot_price: spot_price,
505
- launch_specification: instance_generator.ec2_instance_data,
506
- valid_until: Time.now + request_duration,
482
+ spot_options = {
483
+ # Must use one-time in order to use instance_interruption_behavior=terminate
484
+ # spot_instance_type: "one-time", # default
485
+ # Must use instance_interruption_behavior=terminate in order to use block_duration_minutes
486
+ # instance_interruption_behavior: "terminate", # default
507
487
  }
508
488
  if config[:block_duration_minutes]
509
- request_data[:block_duration_minutes] = config[:block_duration_minutes]
489
+ spot_options[:block_duration_minutes] = config[:block_duration_minutes]
510
490
  end
511
-
512
- response = ec2.client.request_spot_instances(request_data)
513
- response[:spot_instance_requests][0][:spot_instance_request_id]
514
- end
515
-
516
- def tag_server(server)
517
- if config[:tags] && !config[:tags].empty?
518
- tags = config[:tags].map do |k, v|
519
- # we convert the value to a string because
520
- # nils should be passed as an empty String
521
- # and Integers need to be represented as Strings
522
- { key: k.to_s, value: v.to_s }
523
- end
524
- server.create_tags(tags: tags)
491
+ unless spot_price == "" # i.e. on-demand
492
+ spot_options[:max_price] = spot_price
525
493
  end
526
- end
527
494
 
528
- def tag_volumes(server)
529
- if config[:tags] && !config[:tags].empty?
530
- tags = config[:tags].map do |k, v|
531
- { key: k.to_s, value: v.to_s }
532
- end
533
- server.volumes.each do |volume|
534
- volume.create_tags(tags: tags)
535
- end
536
- end
537
- end
495
+ instance_data[:instance_market_options] = {
496
+ market_type: "spot",
497
+ spot_options: spot_options,
498
+ }
538
499
 
539
- # Compares the requested volume count vs what has actually been set to be
540
- # attached to the instance. The information requested through
541
- # ec2.client.described_volumes is updated before the instance volume
542
- # information.
543
- def wait_until_volumes_ready(server, state)
544
- wait_with_destroy(server, state, "volumes to be ready") do |aws_instance|
545
- described_volume_count = 0
546
- ready_volume_count = 0
547
- if aws_instance.exists?
548
- described_volume_count = ec2.client.describe_volumes(filters: [
549
- { name: "attachment.instance-id", values: ["#{state[:server_id]}"] }]).volumes.length
550
- aws_instance.volumes.each { ready_volume_count += 1 }
551
- end
552
- (described_volume_count > 0) && (described_volume_count == ready_volume_count)
500
+ # The preferred way to create a spot instance is via request_spot_instances()
501
+ # However, it does not allow for tagging to occur at creation time.
502
+ # create_instances() allows creation of tagged spot instances, but does
503
+ # not retry if the price could not be satisfied immediately.
504
+ Retryable.retryable(
505
+ tries: config[:spot_wait] / config[:retryable_sleep],
506
+ sleep: lambda { |_n| config[:retryable_sleep] },
507
+ on: ::Aws::EC2::Errors::SpotMaxPriceTooLow
508
+ ) do |retries|
509
+ c = retries * config[:retryable_sleep]
510
+ t = config[:spot_wait]
511
+ info "Waited #{c}/#{t}s for spot request to become fulfilled."
512
+ ec2.create_instance(instance_data)
553
513
  end
554
514
  end
555
515
 
@@ -825,7 +785,9 @@ module Kitchen
825
785
  ip_protocol: "tcp",
826
786
  from_port: port,
827
787
  to_port: port,
828
- ip_ranges: [{ cidr_ip: config[:security_group_cidr_ip] }],
788
+ ip_ranges: Array(config[:security_group_cidr_ip]).map do |cidr_ip|
789
+ { cidr_ip: cidr_ip }
790
+ end,
829
791
  }
830
792
  end
831
793
  )
@@ -1,4 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
1
  #
3
2
  # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
3
  #
@@ -22,6 +21,6 @@ module Kitchen
22
21
  module Driver
23
22
 
24
23
  # Version string for EC2 Test Kitchen driver
25
- EC2_VERSION = "3.5.0".freeze
24
+ EC2_VERSION = "3.8.0".freeze
26
25
  end
27
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-ec2
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.5.0
4
+ version: 3.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fletcher Nichol
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-06 00:00:00.000000000 Z
11
+ date: 2020-10-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-kitchen
@@ -30,34 +30,6 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '3'
33
- - !ruby/object:Gem::Dependency
34
- name: excon
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: '0'
40
- type: :runtime
41
- prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: '0'
47
- - !ruby/object:Gem::Dependency
48
- name: multi_json
49
- requirement: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: '0'
54
- type: :runtime
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - ">="
59
- - !ruby/object:Gem::Version
60
- version: '0'
61
33
  - !ruby/object:Gem::Dependency
62
34
  name: aws-sdk-ec2
63
35
  requirement: !ruby/object:Gem::Requirement
@@ -134,20 +106,6 @@ dependencies:
134
106
  - - "~>"
135
107
  - !ruby/object:Gem::Version
136
108
  version: '0.6'
137
- - !ruby/object:Gem::Dependency
138
- name: simplecov
139
- requirement: !ruby/object:Gem::Requirement
140
- requirements:
141
- - - "~>"
142
- - !ruby/object:Gem::Version
143
- version: '0.7'
144
- type: :development
145
- prerelease: false
146
- version_requirements: !ruby/object:Gem::Requirement
147
- requirements:
148
- - - "~>"
149
- - !ruby/object:Gem::Version
150
- version: '0.7'
151
109
  - !ruby/object:Gem::Dependency
152
110
  name: yard
153
111
  requirement: !ruby/object:Gem::Requirement
@@ -168,14 +126,14 @@ dependencies:
168
126
  requirements:
169
127
  - - '='
170
128
  - !ruby/object:Gem::Version
171
- version: 0.15.1
129
+ version: 1.4.4
172
130
  type: :development
173
131
  prerelease: false
174
132
  version_requirements: !ruby/object:Gem::Requirement
175
133
  requirements:
176
134
  - - '='
177
135
  - !ruby/object:Gem::Version
178
- version: 0.15.1
136
+ version: 1.4.4
179
137
  - !ruby/object:Gem::Dependency
180
138
  name: climate_control
181
139
  requirement: !ruby/object:Gem::Requirement
@@ -216,7 +174,7 @@ homepage: https://github.com/test-kitchen/kitchen-ec2
216
174
  licenses:
217
175
  - Apache-2.0
218
176
  metadata: {}
219
- post_install_message:
177
+ post_install_message:
220
178
  rdoc_options: []
221
179
  require_paths:
222
180
  - lib
@@ -224,15 +182,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
224
182
  requirements:
225
183
  - - ">="
226
184
  - !ruby/object:Gem::Version
227
- version: '2.3'
185
+ version: '2.4'
228
186
  required_rubygems_version: !ruby/object:Gem::Requirement
229
187
  requirements:
230
188
  - - ">="
231
189
  - !ruby/object:Gem::Version
232
190
  version: '0'
233
191
  requirements: []
234
- rubygems_version: 3.1.2
235
- signing_key:
192
+ rubygems_version: 3.1.4
193
+ signing_key:
236
194
  specification_version: 4
237
195
  summary: A Test Kitchen Driver for Amazon EC2
238
196
  test_files: []