kitchen-ec2 3.22.7 → 3.22.10
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 +4 -4
- data/lib/kitchen/driver/aws/dedicated_hosts.rb +3 -1
- data/lib/kitchen/driver/aws/instance_generator.rb +23 -26
- data/lib/kitchen/driver/aws/standard_platform/alma.rb +17 -0
- data/lib/kitchen/driver/aws/standard_platform/debian.rb +23 -0
- data/lib/kitchen/driver/aws/standard_platform/fedora.rb +31 -1
- data/lib/kitchen/driver/aws/standard_platform/freebsd.rb +22 -0
- data/lib/kitchen/driver/aws/standard_platform/rhel.rb +5 -4
- data/lib/kitchen/driver/aws/standard_platform.rb +14 -0
- data/lib/kitchen/driver/ec2.rb +162 -23
- data/lib/kitchen/driver/ec2_version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: adbbc52b9c77d773ae78ebd08798d629a6781793d0e91ff46fb14eb080c70595
|
|
4
|
+
data.tar.gz: 8df7160b05b65d0187b24d9525ea458593db0c95cf9cef261683229752ca1cf6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 956f2322fd4e6bb42ad179c677159be0acb108b30c651c940e95b3c6e266466a548a982f9bb9bcc6edeea3e3ae9734ddc37d80dad14cce80a78c268d733a97ba
|
|
7
|
+
data.tar.gz: f98fe3846db116988011feefebae241fec7145cbbc49aa45d3e82620c1ba5fa36ae15222125144cf37405d7f373db0c5fbcf32505b62453b9f677e84cfa3c765
|
|
@@ -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
|
|
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
|
|
|
@@ -92,34 +92,29 @@ module Kitchen
|
|
|
92
92
|
security_groups = []
|
|
93
93
|
filters = [config[:security_group_filter]].flatten
|
|
94
94
|
filters.each do |sg_filter|
|
|
95
|
-
|
|
95
|
+
# Built up rather than assigned, so that a filter carrying both a
|
|
96
|
+
# name and a tag searches on both. Assigning meant the tag
|
|
97
|
+
# replaced the name outright, silently widening the search to
|
|
98
|
+
# whatever else carried that tag.
|
|
99
|
+
criteria = []
|
|
96
100
|
if sg_filter[:name]
|
|
97
|
-
|
|
98
|
-
{
|
|
99
|
-
name: "group-name",
|
|
100
|
-
values: [sg_filter[:name]],
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: "vpc-id",
|
|
104
|
-
values: [vpc_id],
|
|
105
|
-
},
|
|
106
|
-
]
|
|
101
|
+
criteria << { name: "group-name", values: [sg_filter[:name]] }
|
|
107
102
|
end
|
|
108
|
-
|
|
109
103
|
if sg_filter[:tag]
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
]
|
|
104
|
+
criteria << { name: "tag:#{sg_filter[:tag]}", values: [sg_filter[:value]] }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Refused rather than sent: describe_security_groups with no
|
|
108
|
+
# filters returns every security group in the region, and all of
|
|
109
|
+
# them were then attached to the instance.
|
|
110
|
+
if criteria.empty?
|
|
111
|
+
raise "A security_group_filter needs a `name` or a `tag`, but " \
|
|
112
|
+
"#{sg_filter.inspect} has neither."
|
|
120
113
|
end
|
|
121
114
|
|
|
122
|
-
|
|
115
|
+
criteria << { name: "vpc-id", values: [vpc_id] }
|
|
116
|
+
|
|
117
|
+
security_group = client.describe_security_groups(filters: criteria).security_groups
|
|
123
118
|
|
|
124
119
|
if security_group.any?
|
|
125
120
|
security_group.each { |sg| security_groups.push(sg.group_id) }
|
|
@@ -232,11 +227,13 @@ module Kitchen
|
|
|
232
227
|
i[:placement][:tenancy] = placement[:tenancy]
|
|
233
228
|
end
|
|
234
229
|
end
|
|
230
|
+
# RunInstances calls this `license_specifications`. The driver option
|
|
231
|
+
# is `licenses`, and the payload key used to match the option rather
|
|
232
|
+
# than the API, which no EC2 API accepts.
|
|
235
233
|
license_specifications = config[:licenses]
|
|
236
234
|
if license_specifications
|
|
237
|
-
i[:
|
|
238
|
-
|
|
239
|
-
i[:licenses].append({ license_configuration_arn: license_configuration_arn[:license_configuration_arn] })
|
|
235
|
+
i[:license_specifications] = license_specifications.map do |license|
|
|
236
|
+
{ license_configuration_arn: license[:license_configuration_arn] }
|
|
240
237
|
end
|
|
241
238
|
end
|
|
242
239
|
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
|
|
@@ -98,6 +98,29 @@ module Kitchen
|
|
|
98
98
|
search
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
# Sort images newest release first, keeping backports images last.
|
|
102
|
+
#
|
|
103
|
+
# Debian publishes a backports image alongside each release, from the
|
|
104
|
+
# same account and under the same "debian-<release>-" prefix,
|
|
105
|
+
# differing only by the word "backports" in the name:
|
|
106
|
+
#
|
|
107
|
+
# debian-12-backports-amd64-20260821-2577
|
|
108
|
+
# debian-12-amd64-20260821-2577
|
|
109
|
+
#
|
|
110
|
+
# It runs the backports kernel rather than the release's own -- 6.12
|
|
111
|
+
# against 6.1 for Debian 12 -- and is often published minutes after
|
|
112
|
+
# its plain counterpart, so a tie broken on creation date handed
|
|
113
|
+
# every Debian platform the backports image.
|
|
114
|
+
#
|
|
115
|
+
# This is a preference rather than a filter, so a release with only
|
|
116
|
+
# backports images published is still selectable.
|
|
117
|
+
#
|
|
118
|
+
# @param images [Array<Aws::EC2::Image>] the images to sort
|
|
119
|
+
# @return [Array<Aws::EC2::Image>] the images, newest release first
|
|
120
|
+
def sort_by_version(images)
|
|
121
|
+
prefer(super) { |image| !image.name.include?("backports") }
|
|
122
|
+
end
|
|
123
|
+
|
|
101
124
|
# Detect this platform from an EC2 image.
|
|
102
125
|
#
|
|
103
126
|
# 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" =>
|
|
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
|
-
#
|
|
94
|
-
|
|
95
|
-
#
|
|
96
|
-
|
|
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)
|
data/lib/kitchen/driver/ec2.rb
CHANGED
|
@@ -16,8 +16,10 @@
|
|
|
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)
|
|
22
|
+
require "open3" unless defined?(Open3)
|
|
21
23
|
require "json" unless defined?(JSON)
|
|
22
24
|
require "kitchen"
|
|
23
25
|
require_relative "ec2_version"
|
|
@@ -228,6 +230,19 @@ module Kitchen
|
|
|
228
230
|
end
|
|
229
231
|
end
|
|
230
232
|
|
|
233
|
+
# A placement group is named either by ID or by name, never both: EC2
|
|
234
|
+
# rejects a RunInstances call carrying the pair. The payload generator
|
|
235
|
+
# therefore only applies each one when the other is absent, which means
|
|
236
|
+
# setting both silently drops both and the instance launches in no
|
|
237
|
+
# placement group at all, with nothing in the output to say so.
|
|
238
|
+
validations[:placement] = lambda do |_attr, val, _driver|
|
|
239
|
+
if val.is_a?(Hash) && val[:group_id] && val[:group_name]
|
|
240
|
+
warn "Cannot set both 'group_id' and 'group_name' under 'placement'. " \
|
|
241
|
+
"A placement group is identified by one or the other, so please set only one."
|
|
242
|
+
exit!
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
231
246
|
# empty keys cause failures when tagging and they make no sense
|
|
232
247
|
validations[:tags] = lambda do |_attr, val, _driver|
|
|
233
248
|
# if someone puts the tags each on their own line it's an array not a hash
|
|
@@ -248,11 +263,13 @@ module Kitchen
|
|
|
248
263
|
#
|
|
249
264
|
# Any failure destroys the instance and everything auto-created alongside
|
|
250
265
|
# it, so that a failed create does not leave billable resources behind.
|
|
266
|
+
# Interrupts are re-raised untouched once that cleanup has run.
|
|
251
267
|
#
|
|
252
268
|
# @param state [Hash] the instance state, updated in place with
|
|
253
269
|
# `:server_id`, `:hostname` and any auto-created credentials
|
|
254
270
|
# @return [void]
|
|
255
|
-
# @raise [
|
|
271
|
+
# @raise [Kitchen::ActionFailed] wrapping whatever went wrong, after
|
|
272
|
+
# cleaning up
|
|
256
273
|
def create(state)
|
|
257
274
|
return if state[:server_id]
|
|
258
275
|
|
|
@@ -292,7 +309,8 @@ module Kitchen
|
|
|
292
309
|
exit!
|
|
293
310
|
end
|
|
294
311
|
|
|
295
|
-
|
|
312
|
+
# Remembered so that destroy releases this host and no other.
|
|
313
|
+
state[:allocated_host_id] = allocate_host unless host_available?
|
|
296
314
|
|
|
297
315
|
info("Auto placement on one dedicated host out of: #{hosts_with_capacity.map(&:host_id).join(", ")}")
|
|
298
316
|
end
|
|
@@ -335,10 +353,60 @@ module Kitchen
|
|
|
335
353
|
attach_network_interface(state) unless config[:elastic_network_interface_id].nil?
|
|
336
354
|
create_ec2_json(state) if /chef/i.match?(instance.provisioner.name)
|
|
337
355
|
debug("ec2:create '#{state[:hostname]}'")
|
|
338
|
-
rescue Exception => e
|
|
339
|
-
#
|
|
356
|
+
rescue ::Exception => e
|
|
357
|
+
# Deliberately ::Exception, and deliberately broad: a create that is
|
|
358
|
+
# interrupted partway through must still clean up, or the user is left
|
|
359
|
+
# paying for an instance Test Kitchen has forgotten about.
|
|
360
|
+
#
|
|
361
|
+
# Root-qualified because this file is nested inside `module Kitchen`.
|
|
362
|
+
# There is no Kitchen::Exception today, but Kitchen::StandardError does
|
|
363
|
+
# exist, and an unqualified constant would silently pick it up if one
|
|
364
|
+
# were ever added.
|
|
340
365
|
destroy(state)
|
|
341
|
-
|
|
366
|
+
|
|
367
|
+
# Signals and exits are re-raised untouched. Wrapping a Ctrl-C in an
|
|
368
|
+
# ActionFailed would report the user's own interrupt as a driver bug.
|
|
369
|
+
raise if e.is_a?(::SignalException) || e.is_a?(::SystemExit)
|
|
370
|
+
|
|
371
|
+
raise Kitchen::ActionFailed, create_failure_message(e), e.backtrace
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
# Describe a failed create without discarding what actually went wrong.
|
|
375
|
+
#
|
|
376
|
+
# This message used to append "in the specified region <region>. Please
|
|
377
|
+
# check this AMI is available in this region" to *every* failure, and to
|
|
378
|
+
# raise a bare RuntimeError, so the original exception class and
|
|
379
|
+
# backtrace were lost. A local OpenSSL fault, an expired credential or a
|
|
380
|
+
# missing subnet all arrived looking like an AMI problem, sending users
|
|
381
|
+
# to check something that was never wrong.
|
|
382
|
+
#
|
|
383
|
+
# The hint is worth keeping -- an AMI that does not exist in the region
|
|
384
|
+
# really is a common mistake -- but only when the failure is about the
|
|
385
|
+
# image.
|
|
386
|
+
#
|
|
387
|
+
# @param error [Exception] the underlying failure
|
|
388
|
+
# @return [String]
|
|
389
|
+
# @see https://github.com/test-kitchen/kitchen-ec2/issues/606
|
|
390
|
+
def create_failure_message(error)
|
|
391
|
+
message = "Failed to create the EC2 instance: #{error.class}: #{error.message}"
|
|
392
|
+
return message unless image_related_error?(error)
|
|
393
|
+
|
|
394
|
+
"#{message} Check that image #{config[:image_id]} exists and is available " \
|
|
395
|
+
"in region #{config[:region]}."
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
# Whether a failure is about the AMI rather than something else entirely.
|
|
399
|
+
#
|
|
400
|
+
# EC2 reports every image problem with a code beginning "InvalidAMI"; the
|
|
401
|
+
# message check catches errors raised by the driver itself, which are
|
|
402
|
+
# plain strings with no code attached.
|
|
403
|
+
#
|
|
404
|
+
# @param error [Exception] the underlying failure
|
|
405
|
+
# @return [Boolean]
|
|
406
|
+
def image_related_error?(error)
|
|
407
|
+
return true if error.respond_to?(:code) && error.code.to_s.start_with?("InvalidAMI")
|
|
408
|
+
|
|
409
|
+
error.message.to_s.match?(/\bAMI\b/i)
|
|
342
410
|
end
|
|
343
411
|
|
|
344
412
|
# Terminate the instance and clean up everything created alongside it.
|
|
@@ -385,11 +453,22 @@ module Kitchen
|
|
|
385
453
|
delete_security_group(state)
|
|
386
454
|
delete_key(state)
|
|
387
455
|
|
|
388
|
-
#
|
|
456
|
+
# Release the dedicated host this instance's create allocated, if it
|
|
457
|
+
# allocated one and nothing else is left running on it.
|
|
458
|
+
#
|
|
459
|
+
# Only that host. Dedicated hosts are a shared pool -- create places
|
|
460
|
+
# onto any managed host with room rather than always allocating, so
|
|
461
|
+
# most runs allocate nothing -- and releasing every empty managed host
|
|
462
|
+
# tore down hosts belonging to other suites, including one allocated
|
|
463
|
+
# seconds earlier by a concurrent run whose instance had not launched
|
|
464
|
+
# onto it yet.
|
|
389
465
|
return unless config[:tenancy] == "host" && allow_deallocate_host?
|
|
390
466
|
|
|
391
|
-
|
|
392
|
-
|
|
467
|
+
host_id = state.delete(:allocated_host_id)
|
|
468
|
+
return unless host_id
|
|
469
|
+
|
|
470
|
+
host = host_for_id(host_id)
|
|
471
|
+
deallocate_host(host_id) if host && host_unused?(host)
|
|
393
472
|
end
|
|
394
473
|
|
|
395
474
|
# The EC2 image this instance will be created from.
|
|
@@ -901,20 +980,35 @@ module Kitchen
|
|
|
901
980
|
# @return [String] a PowerShell script wrapped in `<powershell>` tags
|
|
902
981
|
def default_windows_user_data
|
|
903
982
|
base_script = Kitchen::Util.outdent!(<<-EOH)
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
983
|
+
# Log where the installed launch agent already logs, chosen by looking
|
|
984
|
+
# for it rather than by matching the OS against known release names.
|
|
985
|
+
# Writing into the directory of an agent that is not installed would
|
|
986
|
+
# invent a misleading empty tree.
|
|
987
|
+
$logdir = If (Test-Path 'C:\\ProgramData\\Amazon\\EC2Launch') {
|
|
988
|
+
'C:\\ProgramData\\Amazon\\EC2Launch\\log'
|
|
989
|
+
} ElseIf (Test-Path 'C:\\ProgramData\\Amazon\\EC2-Windows\\Launch') {
|
|
990
|
+
'C:\\ProgramData\\Amazon\\EC2-Windows\\Launch\\Log'
|
|
991
|
+
} ElseIf (Test-Path 'C:\\Program Files\\Amazon\\Ec2ConfigService') {
|
|
992
|
+
'C:\\Program Files\\Amazon\\Ec2ConfigService\\Logs'
|
|
993
|
+
} Else {
|
|
994
|
+
Join-Path $env:ProgramData 'Amazon\\kitchen-ec2'
|
|
995
|
+
}
|
|
996
|
+
New-Item -ItemType Directory -Force -Path $logdir | Out-Null
|
|
997
|
+
$logfile = Join-Path $logdir 'kitchen-ec2.log'
|
|
916
998
|
New-Item $logfile -Type file -Force
|
|
917
999
|
|
|
1000
|
+
# Extra EBS volumes are attached but left uninitialized: no launch
|
|
1001
|
+
# agent partitions them by default, on any release. Done with the
|
|
1002
|
+
# storage cmdlets rather than by calling a particular agent's script,
|
|
1003
|
+
# so it does not matter which agent is installed. Only RAW disks are
|
|
1004
|
+
# touched, so a volume that already carries a filesystem is never
|
|
1005
|
+
# reformatted.
|
|
1006
|
+
"Initializing any uninitialized volumes" >> $logfile
|
|
1007
|
+
Get-Disk | Where-Object PartitionStyle -eq 'RAW' |
|
|
1008
|
+
Initialize-Disk -PartitionStyle MBR -PassThru |
|
|
1009
|
+
New-Partition -AssignDriveLetter -UseMaximumSize |
|
|
1010
|
+
Format-Volume -FileSystem NTFS -Confirm:$false >> $logfile
|
|
1011
|
+
|
|
918
1012
|
# Allow script execution
|
|
919
1013
|
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force
|
|
920
1014
|
#PS Remoting and & winrm.cmd basic config
|
|
@@ -927,7 +1021,6 @@ module Kitchen
|
|
|
927
1021
|
& winrm.cmd set winrm/config '@{MaxTimeoutms="1800000"}' >> $logfile
|
|
928
1022
|
& winrm.cmd set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}' >> $logfile
|
|
929
1023
|
& winrm.cmd set winrm/config/winrs '@{MaxShellsPerUser="50"}' >> $logfile
|
|
930
|
-
& winrm.cmd set winrm/config/winrs '@{MaxMemoryPerShellMB="1024"}' >> $logfile
|
|
931
1024
|
#Firewall Config
|
|
932
1025
|
& netsh advfirewall firewall set rule name="Windows Remote Management (HTTP-In)" profile=public protocol=tcp localport=5985 remoteip=localsubnet new remoteip=any >> $logfile
|
|
933
1026
|
Set-ItemProperty -Name LocalAccountTokenFilterPolicy -Path HKLM:\\software\\Microsoft\\Windows\\CurrentVersion\\Policies\\system -Value 1
|
|
@@ -1179,7 +1272,12 @@ module Kitchen
|
|
|
1179
1272
|
info("Removing automatic key pair #{state[:auto_key_id]}")
|
|
1180
1273
|
ec2.client.delete_key_pair(key_name: state[:auto_key_id])
|
|
1181
1274
|
state.delete(:auto_key_id)
|
|
1182
|
-
|
|
1275
|
+
# The file is not always still there: it may have been cleaned up by
|
|
1276
|
+
# hand, wiped along with .kitchen, or never written at all, since
|
|
1277
|
+
# create records the key pair in the state before writing it. This is
|
|
1278
|
+
# the last thing destroy does, so raising here fails the whole action
|
|
1279
|
+
# after every AWS resource has already been cleaned up successfully.
|
|
1280
|
+
FileUtils.rm_f("#{config[:kitchen_root]}/.kitchen/#{instance.name}.pem")
|
|
1183
1281
|
end
|
|
1184
1282
|
|
|
1185
1283
|
# Finalize the driver config and install transport overrides.
|
|
@@ -1562,6 +1660,9 @@ module Kitchen
|
|
|
1562
1660
|
return File.read(public_key_path).strip
|
|
1563
1661
|
end
|
|
1564
1662
|
|
|
1663
|
+
public_key = instance_connect_public_key_via_ssh_keygen(private_key_path)
|
|
1664
|
+
return public_key if public_key
|
|
1665
|
+
|
|
1565
1666
|
begin
|
|
1566
1667
|
key = SSHKey.new(File.read(private_key_path))
|
|
1567
1668
|
key.ssh_public_key
|
|
@@ -1570,6 +1671,35 @@ module Kitchen
|
|
|
1570
1671
|
end
|
|
1571
1672
|
end
|
|
1572
1673
|
|
|
1674
|
+
# Derive the public half of a private key with ssh-keygen.
|
|
1675
|
+
#
|
|
1676
|
+
# OpenSSH reads every key type EC2 can create. The sshkey gem handles
|
|
1677
|
+
# only RSA and DSA, and raises "Neither PUB key nor PRIV key" on an
|
|
1678
|
+
# ed25519 key -- which is exactly what `aws_ssh_key_type: ed25519`
|
|
1679
|
+
# produces, so that documented setting could not be combined with
|
|
1680
|
+
# Instance Connect at all.
|
|
1681
|
+
#
|
|
1682
|
+
# Falling back to the gem rather than requiring ssh-keygen keeps the RSA
|
|
1683
|
+
# default working where OpenSSH is not installed.
|
|
1684
|
+
#
|
|
1685
|
+
# @param private_key_path [String] path to the private key
|
|
1686
|
+
# @return [String, nil] the public key in OpenSSH format, or nil when
|
|
1687
|
+
# ssh-keygen is unavailable or could not read the key
|
|
1688
|
+
def instance_connect_public_key_via_ssh_keygen(private_key_path)
|
|
1689
|
+
output, status = Open3.capture2e("ssh-keygen", "-y", "-f", private_key_path)
|
|
1690
|
+
return output.strip if status.success?
|
|
1691
|
+
|
|
1692
|
+
debug("ssh-keygen could not read #{private_key_path}: #{output.strip}")
|
|
1693
|
+
nil
|
|
1694
|
+
# ::StandardError, not StandardError: this file is nested inside `module
|
|
1695
|
+
# Kitchen`, which defines Kitchen::StandardError. An unqualified constant
|
|
1696
|
+
# resolves to that one, letting the Errno::ENOENT raised by a missing
|
|
1697
|
+
# ssh-keygen escape the check meant to detect it.
|
|
1698
|
+
rescue ::StandardError => e
|
|
1699
|
+
debug("Could not run ssh-keygen: #{e.message}")
|
|
1700
|
+
nil
|
|
1701
|
+
end
|
|
1702
|
+
|
|
1573
1703
|
# SSM Session Manager Support Methods
|
|
1574
1704
|
|
|
1575
1705
|
# The SSM Session Manager helper.
|
|
@@ -1648,9 +1778,18 @@ module Kitchen
|
|
|
1648
1778
|
"--region", driver_instance.config[:region]
|
|
1649
1779
|
]
|
|
1650
1780
|
|
|
1651
|
-
#
|
|
1781
|
+
# Tunnelling SSH over SSM needs a session document that forwards a
|
|
1782
|
+
# port. Without one, `start-session` opens an interactive shell
|
|
1783
|
+
# session instead, which speaks nothing SSH understands: pointed at
|
|
1784
|
+
# that as a ProxyCommand, SSH waits for a banner that never comes
|
|
1785
|
+
# and the create hangs until it is interrupted. AWS-StartSSHSession
|
|
1786
|
+
# is the document AWS publishes for this, and `%p` is substituted by
|
|
1787
|
+
# Net::SSH::Proxy::Command with the port being connected on, so a
|
|
1788
|
+
# transport using a port other than 22 tunnels to that port.
|
|
1652
1789
|
if driver_instance.config[:ssm_session_manager_document_name]
|
|
1653
1790
|
cmd += ["--document-name", driver_instance.config[:ssm_session_manager_document_name]]
|
|
1791
|
+
else
|
|
1792
|
+
cmd += ["--document-name", "AWS-StartSSHSession", "--parameters", "portNumber=%p"]
|
|
1654
1793
|
end
|
|
1655
1794
|
|
|
1656
1795
|
# Add AWS profile if specified
|