kitchen-ec2 3.22.7 → 3.22.9

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: 54005acc701e50833f4592f90156aa8d41f74ceba73c824df2903940afd4232d
4
- data.tar.gz: e1c55129e883dfa0e92f0c2510a47d4719fe7ceb2f2b3c9db4bcb766c05fe18c
3
+ metadata.gz: 54de858935bf91fcad58900283fd45042007c18d546797d098f29b955d177c87
4
+ data.tar.gz: 95fc2485b796d0e87974e17de7b34dd858c08fdb2d2433cbae6975983cea1501
5
5
  SHA512:
6
- metadata.gz: 61ddb4833e791864530709dba1bd080519a7e8e8d18268ee147f6cf8f2bfa212218e81f40990e8fc7e13288abb2d77edfba580a757cb8488fc7738189cfb940c
7
- data.tar.gz: 415028713bf3467ed42152c5249730212d0124e5a0424867cd1580f55f1b23dbaef199bc8aa82cc88fa2e0e0e85e7ae1de30d3f55de7e967588fcabbda333450
6
+ metadata.gz: dad42aca368360dd2d11c58583d13360f34fa77925a0fd40ede0c97da97092361c74ba7b61401918d7c1ae33732806fe8cdd7ddb84b2d23024dc21a20a94a835
7
+ data.tar.gz: 5e3a1ce54adf7425bce0c70816b212b0a2e8be36f35e9a1f8a58ebe971cfda2ace1c004e9dea91dc5d334021ba8df5f7541d1f891b1121021a9321f82d0f311c
@@ -91,7 +91,9 @@ module Kitchen
91
91
  # host costs money whether or not it is used.
92
92
  def allocate_host
93
93
  unless allow_allocate_host?
94
- warn "ERROR: Attempted to allocate dedicated host but need environment variable TK_ALLOCATE_DEDICATED_HOST to be set"
94
+ warn "ERROR: Attempted to allocate a dedicated host, but the driver setting `allocate_dedicated_host` is not enabled. " \
95
+ "Set `allocate_dedicated_host: true` to allow it, remembering that a dedicated host is billed from allocation " \
96
+ "until it is released."
95
97
  exit!
96
98
  end
97
99
 
@@ -232,11 +232,13 @@ module Kitchen
232
232
  i[:placement][:tenancy] = placement[:tenancy]
233
233
  end
234
234
  end
235
+ # RunInstances calls this `license_specifications`. The driver option
236
+ # is `licenses`, and the payload key used to match the option rather
237
+ # than the API, which no EC2 API accepts.
235
238
  license_specifications = config[:licenses]
236
239
  if license_specifications
237
- i[:licenses] = []
238
- license_specifications.each do |license_configuration_arn|
239
- i[:licenses].append({ license_configuration_arn: license_configuration_arn[:license_configuration_arn] })
240
+ i[:license_specifications] = license_specifications.map do |license|
241
+ { license_configuration_arn: license[:license_configuration_arn] }
240
242
  end
241
243
  end
242
244
  unless config[:instance_initiated_shutdown_behavior].nil? ||
@@ -50,6 +50,23 @@ module Kitchen
50
50
  search
51
51
  end
52
52
 
53
+ # Sort images newest release first, keeping Kitten builds last.
54
+ #
55
+ # AlmaLinux Kitten is AlmaLinux's development distribution, published
56
+ # from the same account and under the same "AlmaLinux OS" prefix as
57
+ # the releases. Its name carries no release number, so {.from_image}
58
+ # reads the major straight into the build date: "AlmaLinux OS Kitten
59
+ # 10.20260727.0" yields "10.20260727", a far larger number than the
60
+ # "10.2" of an actual release, which sorts Kitten ahead of
61
+ # everything. A versioned search is unaffected, since "AlmaLinux OS
62
+ # 10*" does not match "AlmaLinux OS Kitten 10*".
63
+ #
64
+ # @param images [Array<Aws::EC2::Image>] the images to sort
65
+ # @return [Array<Aws::EC2::Image>] the images, newest release first
66
+ def sort_by_version(images)
67
+ prefer(super) { |image| !image.name.include?("Kitten") }
68
+ end
69
+
53
70
  # Detect this platform from an EC2 image.
54
71
  #
55
72
  # Matching is done on the image name, which is the only reliable signal
@@ -23,6 +23,12 @@ module Kitchen
23
23
  class Fedora < StandardPlatform
24
24
  StandardPlatform.platforms["fedora"] = self
25
25
 
26
+ # Name fragments marking an image as a development stream rather
27
+ # than a Fedora release: Rawhide (the rolling development branch),
28
+ # ELN (Enterprise Linux Next) and the Prerelease builds published
29
+ # while a release is still stabilising.
30
+ DEVELOPMENT_STREAMS = /-(?:Rawhide|ELN|Prerelease)-/i
31
+
26
32
  # The account EC2 creates on this platform's official AMIs.
27
33
  #
28
34
  # Used as the SSH username when the transport does not specify one.
@@ -43,12 +49,36 @@ module Kitchen
43
49
  def image_search
44
50
  search = {
45
51
  "owner-id" => "125523088429",
46
- "name" => version ? "Fedora-Cloud-Base-#{version}-*" : "Fedora-Cloud-Base-*",
52
+ "name" => if version
53
+ # Both naming schemes are searched because the older
54
+ # one is still present in some regions.
55
+ ["Fedora-Cloud-Base-AmazonEC2.*-#{version}-*",
56
+ "Fedora-Cloud-Base-#{version}-*"]
57
+ else
58
+ "Fedora-Cloud-Base-*"
59
+ end,
47
60
  }
48
61
  search["architecture"] = architecture if architecture
49
62
  search
50
63
  end
51
64
 
65
+ # Sort images newest release first, keeping development streams last.
66
+ #
67
+ # Fedora publishes Rawhide, ELN and Prerelease images from the same
68
+ # account and under the same "Fedora-Cloud-Base-" prefix as its
69
+ # releases. Those names carry a build date where a release carries a
70
+ # release number, so {.from_image} reads a version like "20250820.0"
71
+ # off them -- larger than any real release, which sorts them to the
72
+ # front. They are pushed to the back afterwards, the same way RHEL
73
+ # handles its Beta images.
74
+ #
75
+ # @param images [Array<Aws::EC2::Image>] the images to sort
76
+ # @return [Array<Aws::EC2::Image>] the images, newest release first
77
+ def sort_by_version(images)
78
+ images = super
79
+ prefer(images) { |image| !DEVELOPMENT_STREAMS.match?(image.name) }
80
+ end
81
+
52
82
  # Detect this platform from an EC2 image.
53
83
  #
54
84
  # Matching is done on the image name, which is the only reliable signal
@@ -57,6 +57,28 @@ module Kitchen
57
57
  search
58
58
  end
59
59
 
60
+ # Sort images newest release first, preferring the cloud-init flavor.
61
+ #
62
+ # FreeBSD publishes several flavors of each release, distinguished
63
+ # only by a word at the end of the image name, and only the
64
+ # cloud-init flavor is a general-purpose cloud image. "base" and
65
+ # "small" ship without sudo, so a converge fails on them; "builder"
66
+ # is a developer image that does not boot to a usable state on a
67
+ # small instance -- launched on a t3.micro it reaches "impaired" and
68
+ # never opens port 22. Nothing in the name distinguishes them by
69
+ # date or version, so without a preference the flavor selected is
70
+ # effectively arbitrary.
71
+ #
72
+ # Releases old enough to predate the flavored names publish a single
73
+ # image each. Because this is a stable partition rather than a
74
+ # filter, those are left where the version sort put them.
75
+ #
76
+ # @param images [Array<Aws::EC2::Image>] the images to sort
77
+ # @return [Array<Aws::EC2::Image>] the images, best match first
78
+ def sort_by_version(images)
79
+ prefer(super) { |image| image.name.include?("cloud-init") }
80
+ end
81
+
60
82
  # Detect this platform from an EC2 image.
61
83
  #
62
84
  # Matching is done on the image name, which is the only reliable signal
@@ -90,10 +90,11 @@ module Kitchen
90
90
  # @param images [Array<Aws::EC2::Image>] the images to sort
91
91
  # @return [Array<Aws::EC2::Image>] the images, best match first
92
92
  def sort_by_version(images)
93
- # First do a normal version sort
94
- super(images)
95
- # Now sort again, shunning Beta releases.
96
- prefer(images) { |image| !image.name.match(/_Beta-/i) }
93
+ # `prefer(super)`, not `super(images)` followed by
94
+ # `prefer(images)`: `prefer` returns a new array rather than
95
+ # reordering in place, so applying it to the original argument
96
+ # discarded the version sort entirely.
97
+ prefer(super) { |image| !image.name.match?(/_Beta-/i) }
97
98
  end
98
99
  end
99
100
  end
@@ -153,6 +153,20 @@ module Kitchen
153
153
  #
154
154
  def self.from_platform_string(driver, platform_string)
155
155
  platform, version, architecture = parse_platform_string(platform_string)
156
+
157
+ # A few platform names end in what looks like a version but is
158
+ # really part of the distribution's name: "amazon2023" and "amazon2"
159
+ # are Amazon Linux 2023 and Amazon Linux 2, not releases 2023 and 2
160
+ # of "amazon" (which is the long-EOL Amazon Linux 1). Written with a
161
+ # dash -- the spelling the documentation uses -- the split above
162
+ # lands on the "amazon" platform searching for a release that never
163
+ # existed, and no image matches. Fold the release back into the name
164
+ # whenever the two together name a platform that is registered.
165
+ if version && platforms["#{platform}#{version}"]
166
+ platform = "#{platform}#{version}"
167
+ version = nil
168
+ end
169
+
156
170
  return unless platform && platforms[platform]
157
171
 
158
172
  platforms[platform].new(driver, platform, version, architecture)
@@ -16,6 +16,7 @@
16
16
  # See the License for the specific language governing permissions and
17
17
  # limitations under the License.
18
18
 
19
+ require "fileutils" unless defined?(FileUtils)
19
20
  require "sshkey" unless defined?(SSHKey)
20
21
  require "benchmark" unless defined?(Benchmark)
21
22
  require "json" unless defined?(JSON)
@@ -248,11 +249,13 @@ module Kitchen
248
249
  #
249
250
  # Any failure destroys the instance and everything auto-created alongside
250
251
  # it, so that a failed create does not leave billable resources behind.
252
+ # Interrupts are re-raised untouched once that cleanup has run.
251
253
  #
252
254
  # @param state [Hash] the instance state, updated in place with
253
255
  # `:server_id`, `:hostname` and any auto-created credentials
254
256
  # @return [void]
255
- # @raise [RuntimeError] wrapping whatever went wrong, after cleaning up
257
+ # @raise [Kitchen::ActionFailed] wrapping whatever went wrong, after
258
+ # cleaning up
256
259
  def create(state)
257
260
  return if state[:server_id]
258
261
 
@@ -335,10 +338,60 @@ module Kitchen
335
338
  attach_network_interface(state) unless config[:elastic_network_interface_id].nil?
336
339
  create_ec2_json(state) if /chef/i.match?(instance.provisioner.name)
337
340
  debug("ec2:create '#{state[:hostname]}'")
338
- rescue Exception => e
339
- # Clean up the instance and any auto-created security groups or keys on the way out.
341
+ rescue ::Exception => e
342
+ # Deliberately ::Exception, and deliberately broad: a create that is
343
+ # interrupted partway through must still clean up, or the user is left
344
+ # paying for an instance Test Kitchen has forgotten about.
345
+ #
346
+ # Root-qualified because this file is nested inside `module Kitchen`.
347
+ # There is no Kitchen::Exception today, but Kitchen::StandardError does
348
+ # exist, and an unqualified constant would silently pick it up if one
349
+ # were ever added.
340
350
  destroy(state)
341
- raise "#{e.message} in the specified region #{config[:region]}. Please check this AMI is available in this region."
351
+
352
+ # Signals and exits are re-raised untouched. Wrapping a Ctrl-C in an
353
+ # ActionFailed would report the user's own interrupt as a driver bug.
354
+ raise if e.is_a?(::SignalException) || e.is_a?(::SystemExit)
355
+
356
+ raise Kitchen::ActionFailed, create_failure_message(e), e.backtrace
357
+ end
358
+
359
+ # Describe a failed create without discarding what actually went wrong.
360
+ #
361
+ # This message used to append "in the specified region <region>. Please
362
+ # check this AMI is available in this region" to *every* failure, and to
363
+ # raise a bare RuntimeError, so the original exception class and
364
+ # backtrace were lost. A local OpenSSL fault, an expired credential or a
365
+ # missing subnet all arrived looking like an AMI problem, sending users
366
+ # to check something that was never wrong.
367
+ #
368
+ # The hint is worth keeping -- an AMI that does not exist in the region
369
+ # really is a common mistake -- but only when the failure is about the
370
+ # image.
371
+ #
372
+ # @param error [Exception] the underlying failure
373
+ # @return [String]
374
+ # @see https://github.com/test-kitchen/kitchen-ec2/issues/606
375
+ def create_failure_message(error)
376
+ message = "Failed to create the EC2 instance: #{error.class}: #{error.message}"
377
+ return message unless image_related_error?(error)
378
+
379
+ "#{message} Check that image #{config[:image_id]} exists and is available " \
380
+ "in region #{config[:region]}."
381
+ end
382
+
383
+ # Whether a failure is about the AMI rather than something else entirely.
384
+ #
385
+ # EC2 reports every image problem with a code beginning "InvalidAMI"; the
386
+ # message check catches errors raised by the driver itself, which are
387
+ # plain strings with no code attached.
388
+ #
389
+ # @param error [Exception] the underlying failure
390
+ # @return [Boolean]
391
+ def image_related_error?(error)
392
+ return true if error.respond_to?(:code) && error.code.to_s.start_with?("InvalidAMI")
393
+
394
+ error.message.to_s.match?(/\bAMI\b/i)
342
395
  end
343
396
 
344
397
  # Terminate the instance and clean up everything created alongside it.
@@ -1179,7 +1232,12 @@ module Kitchen
1179
1232
  info("Removing automatic key pair #{state[:auto_key_id]}")
1180
1233
  ec2.client.delete_key_pair(key_name: state[:auto_key_id])
1181
1234
  state.delete(:auto_key_id)
1182
- File.unlink("#{config[:kitchen_root]}/.kitchen/#{instance.name}.pem")
1235
+ # The file is not always still there: it may have been cleaned up by
1236
+ # hand, wiped along with .kitchen, or never written at all, since
1237
+ # create records the key pair in the state before writing it. This is
1238
+ # the last thing destroy does, so raising here fails the whole action
1239
+ # after every AWS resource has already been cleaned up successfully.
1240
+ FileUtils.rm_f("#{config[:kitchen_root]}/.kitchen/#{instance.name}.pem")
1183
1241
  end
1184
1242
 
1185
1243
  # Finalize the driver config and install transport overrides.
@@ -1648,9 +1706,18 @@ module Kitchen
1648
1706
  "--region", driver_instance.config[:region]
1649
1707
  ]
1650
1708
 
1651
- # Add document name if specified
1709
+ # Tunnelling SSH over SSM needs a session document that forwards a
1710
+ # port. Without one, `start-session` opens an interactive shell
1711
+ # session instead, which speaks nothing SSH understands: pointed at
1712
+ # that as a ProxyCommand, SSH waits for a banner that never comes
1713
+ # and the create hangs until it is interrupted. AWS-StartSSHSession
1714
+ # is the document AWS publishes for this, and `%p` is substituted by
1715
+ # Net::SSH::Proxy::Command with the port being connected on, so a
1716
+ # transport using a port other than 22 tunnels to that port.
1652
1717
  if driver_instance.config[:ssm_session_manager_document_name]
1653
1718
  cmd += ["--document-name", driver_instance.config[:ssm_session_manager_document_name]]
1719
+ else
1720
+ cmd += ["--document-name", "AWS-StartSSHSession", "--parameters", "portNumber=%p"]
1654
1721
  end
1655
1722
 
1656
1723
  # Add AWS profile if specified
@@ -19,6 +19,6 @@
19
19
  module Kitchen
20
20
  module Driver
21
21
  # Version string for EC2 Test Kitchen driver
22
- EC2_VERSION = "3.22.7".freeze
22
+ EC2_VERSION = "3.22.9".freeze
23
23
  end
24
24
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-ec2
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.22.7
4
+ version: 3.22.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Test Kitchen Team