kitchen-ec2 3.4.0 → 3.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d030685e23e784c93fd5e8c7f76702a4a90f907925a2a67750827ea6457af94f
4
- data.tar.gz: 5268e3ecbfb386c0a62a1f9f6e2867685e3e5d21376d490a1f5408f20d19db96
3
+ metadata.gz: 0f0f97881aef017104ee3af2f9577a1303495d3f29dd46a8cf22c491e179022b
4
+ data.tar.gz: caf442b23b9b8ed51149f9ca5af1aaa41112d47b21ac6e2b00e3167d21506ad2
5
5
  SHA512:
6
- metadata.gz: 71cd818f465de9cf145592c6110481a8544e45a3e37c105149b122bc921a7d8ae2e11cd8b68816db453a2b4c969b87f1115d32b4e00624c5c3dee6f06cd47de7
7
- data.tar.gz: 30021e46d8151aa712224f810ea986ce28cabeffe65baeba885e5b86e261a38417775978b44d9b5cad08d2107b2be8a57e3cd8c23238cba9bc0fb3dbd48082ae
6
+ metadata.gz: 9e017c01b5a5a9ef3c811c92016bd72c83d108dd08836ed55fc4ee689f63813768f9b9c9a70131be64d29e89c0f1eb1c88951c7ff0c3ae01177f9cd56fb30029
7
+ data.tar.gz: f020ac63fb41ee6b51b1829ec2a8b5122876ebfb8c0bca669b1872fc9e5a5108f332b02d3de2b008d079c7833bb6fee68957249fbbebfa7b30ab062c6ce50b12
@@ -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
@@ -56,8 +57,10 @@ module Kitchen
56
57
  ).subnets
57
58
  raise "The subnet tagged '#{config[:subnet_filter][:tag]}:#{config[:subnet_filter][:value]}' does not exist!" unless subnets.any?
58
59
 
59
- vpc_id = subnets[0].vpc_id
60
- config[:subnet_id] = subnets[0].subnet_id
60
+ # => Select the least-populated subnet if we have multiple matches
61
+ subnet = subnets.max_by { |s| s[:available_ip_address_count] }
62
+ vpc_id = subnet.vpc_id
63
+ config[:subnet_id] = subnet.subnet_id
61
64
  end
62
65
 
63
66
  if config[:security_group_ids].nil? && config[:security_group_filter]
@@ -111,11 +114,25 @@ module Kitchen
111
114
  key_name: config[:aws_ssh_key_id],
112
115
  subnet_id: config[:subnet_id],
113
116
  private_ip_address: config[:private_ip_address],
117
+ min_count: 1,
118
+ max_count: 1,
114
119
  }
115
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
+
116
133
  availability_zone = config[:availability_zone]
117
134
  if availability_zone
118
- if availability_zone =~ /^[a-z]$/i
135
+ if /^[a-z]$/i.match?(availability_zone)
119
136
  availability_zone = "#{config[:region]}#{availability_zone}"
120
137
  end
121
138
  i[:placement] = { availability_zone: availability_zone.downcase }
@@ -158,7 +175,7 @@ module Kitchen
158
175
  end
159
176
  availability_zone = config[:availability_zone]
160
177
  if availability_zone
161
- if availability_zone =~ /^[a-z]$/i
178
+ if /^[a-z]$/i.match?(availability_zone)
162
179
  availability_zone = "#{config[:region]}#{availability_zone}"
163
180
  end
164
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,29 +469,10 @@ 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
476
  request_duration = config[:spot_wait]
497
477
  config_spot_price = config[:spot_price].to_s
498
478
  if %w{ondemand on-demand}.include?(config_spot_price)
@@ -500,56 +480,36 @@ module Kitchen
500
480
  else
501
481
  spot_price = config_spot_price
502
482
  end
503
- request_data = {
504
- spot_price: spot_price,
505
- launch_specification: instance_generator.ec2_instance_data,
483
+ spot_options = {
484
+ spot_instance_type: "persistent", # Cannot use one-time with valid_until
506
485
  valid_until: Time.now + request_duration,
486
+ instance_interruption_behavior: "stop",
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
 
@@ -1,8 +1,7 @@
1
- # -*- encoding: utf-8 -*-
2
1
  #
3
2
  # Author:: Fletcher Nichol (<fnichol@nichol.ca>)
4
3
  #
5
- # Copyright:: 2016-2020, Chef Software, Inc.
4
+ # Copyright:: Chef Software, Inc.
6
5
  # Copyright:: 2012-2018, Fletcher Nichol
7
6
  #
8
7
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -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.4.0".freeze
24
+ EC2_VERSION = "3.7.2".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.4.0
4
+ version: 3.7.2
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-03-18 00:00:00.000000000 Z
11
+ date: 2020-09-29 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.14.1
129
+ version: 1.4.0
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.14.1
136
+ version: 1.4.0
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,7 +182,7 @@ 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
  - - ">="
@@ -232,7 +190,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
232
190
  version: '0'
233
191
  requirements: []
234
192
  rubygems_version: 3.1.2
235
- signing_key:
193
+ signing_key:
236
194
  specification_version: 4
237
195
  summary: A Test Kitchen Driver for Amazon EC2
238
196
  test_files: []