kitchen-ec2 3.22.3 → 3.22.4

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: bc2c40634911bf5f8f89a42e04e2bc9e0ea90442390461f49929179e9edd94e0
4
- data.tar.gz: 825a61bf6c1db2db9a555b7c53bda74c8c2d56ee69142facedea70512f021aee
3
+ metadata.gz: 8c45dbb3160767d4ce660a83da145699c8e29f5fbb93b9ccb052ee8c4b8e5eb1
4
+ data.tar.gz: 8da4c4fdcc43e66e5ae58f722397f8663470b7c34167c3d47ec64219ec99824d
5
5
  SHA512:
6
- metadata.gz: 99e722e2e64ba7fb67e5e274eb24c37e84b3cfeb93a42532c2060db2481e51f84afc339c81eb1f6cb0b6b7881002479db2d1a5eac852bfc443a27d39a0d14966
7
- data.tar.gz: 1ec1e0d25fe8dc48ac3c126b958d7f669257c86e2f0aaa93baf1fb7099ad202e4bcb23ad83e2db630a9997abf09d6717b6a841ae1f1071c6ca546c0f39260f62
6
+ metadata.gz: 2b035d3317d7134b04811773347768ba33dfd3713ea3726ab124e2bfbc11afa54a23f3bd18f38fc7d01db9b9dd7b8c7d49cfdc4f1e0835e41281f1c6af9d1b4d
7
+ data.tar.gz: eb00b8d53f41145b6d2deec25c0a840f45be3d5a605907c8f886d52448f667ebcd6496ad279a0f9e82b56938dd1ffbd9a036eb57161e7c3ebef310f883feab08
@@ -23,11 +23,26 @@ require "aws-sdk-core/instance_profile_credentials"
23
23
 
24
24
  module Kitchen
25
25
  module Driver
26
+ # Namespace for the driver's AWS-facing collaborators: the EC2 client
27
+ # wrapper, the RunInstances payload generator, the platform definitions and
28
+ # the Instance Connect and SSM Session Manager helpers.
26
29
  class Aws
27
30
  # A class for creating and managing the EC2 client connection
28
31
  #
29
32
  # @author Tyler Ball <tball@chef.io>
30
33
  class Client
34
+ # Configure the AWS SDK for this driver's connection settings.
35
+ #
36
+ # These settings are applied to the SDK's process-wide configuration
37
+ # rather than held on this object, so clients built later pick them up.
38
+ # A nil retry limit is omitted rather than written, so that the SDK's
39
+ # own default survives.
40
+ #
41
+ # @param region [String] the AWS region, e.g. "us-west-2"
42
+ # @param profile_name [String] shared credentials profile to use
43
+ # @param http_proxy [String, nil] proxy URL, if any
44
+ # @param retry_limit [Integer, nil] SDK retry limit, or nil for the default
45
+ # @param ssl_verify_peer [Boolean] whether to verify TLS peers
31
46
  def initialize(
32
47
  region,
33
48
  profile_name = "default",
@@ -73,15 +88,22 @@ module Kitchen
73
88
 
74
89
  # check if instance exists, given an id
75
90
  # @param id [String] aws instance id
76
- # @return boolean
91
+ # @return [Boolean]
77
92
  def instance_exists?(id)
78
93
  resource.instance(id).exists?
79
94
  end
80
95
 
96
+ # The low-level EC2 client, for API calls with no resource equivalent.
97
+ #
98
+ # @return [Aws::EC2::Client]
81
99
  def client
82
100
  @client ||= ::Aws::EC2::Client.new
83
101
  end
84
102
 
103
+ # The EC2 resource interface, for working with instances and images as
104
+ # objects rather than raw API responses.
105
+ #
106
+ # @return [Aws::EC2::Resource]
85
107
  def resource
86
108
  @resource ||= ::Aws::EC2::Resource.new
87
109
  end
@@ -1,15 +1,34 @@
1
1
  module Kitchen
2
2
  module Driver
3
+ # Namespace for behavior mixed into {Kitchen::Driver::Ec2}.
3
4
  module Mixins
5
+ # Allocation and release of EC2 Dedicated Hosts.
6
+ #
7
+ # A dedicated host is physical hardware reserved for one account, required
8
+ # for `tenancy: host` and for platforms such as macOS. Hosts are billed
9
+ # from allocation until release regardless of whether an instance is
10
+ # running on them, so both operations are gated behind explicit config and
11
+ # failures are fatal rather than warnings.
12
+ #
13
+ # Only hosts tagged `ManagedBy: Test Kitchen` are ever considered, so a
14
+ # user's own dedicated hosts are never allocated to or released.
15
+ #
16
+ # This module expects its includer to provide `config`, `ec2` and the
17
+ # Test Kitchen logging methods; it is mixed into {Kitchen::Driver::Ec2}.
4
18
  module DedicatedHosts
5
- # check if a suitable dedicated host is available
6
- # @return Boolean
19
+ # Whether any managed host has room for the configured instance type.
20
+ #
21
+ # @return [Boolean]
7
22
  def host_available?
8
23
  !hosts_with_capacity.empty?
9
24
  end
10
25
 
11
- # get dedicated host with capacity for instance type
12
- # @return Aws::EC2::Types::Host
26
+ # Managed hosts with room for the configured instance type.
27
+ #
28
+ # T-family hosts report no capacity information and may be
29
+ # overprovisioned, so a host with no capacity block counts as available.
30
+ #
31
+ # @return [Array<Aws::EC2::Types::Host>]
13
32
  def hosts_with_capacity
14
33
  hosts_managed.select do |host|
15
34
  # T-instance hosts do not report available capacity and can be overprovisioned
@@ -18,27 +37,38 @@ module Kitchen
18
37
  else
19
38
  instance_capacity = host.available_capacity.available_instance_capacity
20
39
  capacity_for_type = instance_capacity.detect { |cap| cap.instance_type == config[:instance_type] }
21
- capacity_for_type.available_capacity > 0
40
+ # A host that lists no capacity for this instance type cannot run
41
+ # one, so treat a missing entry the same as zero rather than
42
+ # raising on it.
43
+ !capacity_for_type.nil? && capacity_for_type.available_capacity > 0
22
44
  end
23
45
  end
24
46
  end
25
47
 
26
- # check if host has no instances running
27
- # @param host_id [Aws::EC2::Types::Host] dedicated host
28
- # @return Boolean
48
+ # Whether a host has no instances running on it.
49
+ #
50
+ # @param host [Aws::EC2::Types::Host] the host to inspect
51
+ # @return [Boolean] true when the host can be released
29
52
  def host_unused?(host)
30
53
  host.instances.empty?
31
54
  end
32
55
 
33
- # get host data for host id
34
- # @param host_id [Aws::EC2::Types::Host] dedicated host
35
- # @return Array(Aws::EC2::Types::Host)
56
+ # Look a dedicated host up by ID.
57
+ #
58
+ # @param host_id [String] the host ID, e.g. "h-0123456789abcdef0"
59
+ # @return [Aws::EC2::Types::Host, nil] the host, or nil when EC2 does
60
+ # not know it
36
61
  def host_for_id(host_id)
37
- ec2.client.describe_hosts(host_ids: [host_id])&.first
62
+ ec2.client.describe_hosts(host_ids: [host_id]).hosts.first
38
63
  end
39
64
 
40
- # get dedicated hosts managed by Test Kitchen
41
- # @return Array(Aws::EC2::Types::Host)
65
+ # Available dedicated hosts that Test Kitchen allocated.
66
+ #
67
+ # Filtered on the `ManagedBy` tag so that hosts belonging to the user are
68
+ # never touched, and on state so that hosts still being provisioned or
69
+ # already released are ignored.
70
+ #
71
+ # @return [Array<Aws::EC2::Types::Host>]
42
72
  def hosts_managed
43
73
  response = ec2.client.describe_hosts(
44
74
  filter: [
@@ -49,8 +79,16 @@ module Kitchen
49
79
  response.hosts.select { |host| host.state == "available" }
50
80
  end
51
81
 
52
- # allocate new dedicated host for requested instance type
53
- # @return String host id
82
+ # Allocate a new dedicated host for the configured instance type.
83
+ #
84
+ # A `.metal` size occupies a whole host, so it is allocated for that
85
+ # exact type; every other size can share a host, so the whole instance
86
+ # family is allocated and EC2 places instances within it.
87
+ #
88
+ # @return [String] the new host's ID
89
+ # @note Terminates the process with `exit!` when allocation is not
90
+ # enabled or no availability zone is configured, since an allocated
91
+ # host costs money whether or not it is used.
54
92
  def allocate_host
55
93
  unless allow_allocate_host?
56
94
  warn "ERROR: Attempted to allocate dedicated host but need environment variable TK_ALLOCATE_DEDICATED_HOST to be set"
@@ -91,9 +129,12 @@ module Kitchen
91
129
  response.host_ids.first
92
130
  end
93
131
 
94
- # deallocate a dedicated host
95
- # @param host_id [String] dedicated host id
96
- # @return Aws::EC2::Types::ReleaseHostsResult
132
+ # Release a dedicated host.
133
+ #
134
+ # @param host_id [String] the host to release
135
+ # @return [nil] when the host was released successfully
136
+ # @note Terminates the process with `exit!` when the release fails, as a
137
+ # host that stays allocated keeps accruing charges silently.
97
138
  def deallocate_host(host_id)
98
139
  info("Deallocating dedicated host #{host_id}")
99
140
 
@@ -104,28 +145,32 @@ module Kitchen
104
145
  exit!
105
146
  end
106
147
 
107
- # return instance family from type
108
- # @param instance_type [String] type in format family.size
109
- # @return String instance family
148
+ # The family part of an instance type.
149
+ #
150
+ # @param instance_type [String] a type in "family.size" form, e.g. "m5.large"
151
+ # @return [String] the family, e.g. "m5"
110
152
  def instance_family_from_type(instance_type)
111
153
  instance_type.split(".").first
112
154
  end
113
155
 
114
- # return instance size from type
115
- # @param instance_type [String] type in format family.size
116
- # @return String instance size
156
+ # The size part of an instance type.
157
+ #
158
+ # @param instance_type [String] a type in "family.size" form, e.g. "m5.large"
159
+ # @return [String] the size, e.g. "large"
117
160
  def instance_size_from_type(instance_type)
118
161
  instance_type.split(".").last
119
162
  end
120
163
 
121
- # check config, if host allocation is enabled
122
- # @return Boolean
164
+ # Whether the user has opted in to allocating dedicated hosts.
165
+ #
166
+ # @return [Boolean]
123
167
  def allow_allocate_host?
124
168
  config[:allocate_dedicated_host]
125
169
  end
126
170
 
127
- # check config, if host deallocation is enabled
128
- # @return Boolean
171
+ # Whether the user has opted in to releasing dedicated hosts.
172
+ #
173
+ # @return [Boolean]
129
174
  def allow_deallocate_host?
130
175
  config[:deallocate_dedicated_host]
131
176
  end
@@ -19,13 +19,29 @@ require "aws-sdk-ec2instanceconnect"
19
19
  module Kitchen
20
20
  module Driver
21
21
  class Aws
22
+ # Pushes short-lived SSH public keys to an instance using EC2 Instance
23
+ # Connect, so that a connection can be made without a long-lived key pair.
24
+ #
25
+ # A pushed key is accepted for roughly sixty seconds, so it is sent again
26
+ # before each connection rather than once at create time.
22
27
  class InstanceConnect
28
+ # @param config [Hash] the driver config
29
+ # @param logger [Kitchen::Logger] the logger to report through
23
30
  def initialize(config, logger)
24
31
  @config = config
25
32
  @logger = logger
26
33
  @client = ::Aws::EC2InstanceConnect::Client.new(region: config[:region])
27
34
  end
28
35
 
36
+ # Push an SSH public key to an instance for a given user.
37
+ #
38
+ # @param instance_id [String] the target instance, e.g. "i-0123abcd"
39
+ # @param username [String] the OS account to authorize the key for
40
+ # @param public_key [String] the OpenSSH-format public key
41
+ # @raise [Aws::EC2InstanceConnect::Errors::ServiceError] when the key is
42
+ # rejected; there is no usable connection in that case, so the error
43
+ # is not swallowed
44
+ # @return [void]
29
45
  def send_ssh_public_key(instance_id, username, public_key)
30
46
  @logger.info("Sending SSH public key to instance #{instance_id} for user #{username}")
31
47
 
@@ -26,18 +26,41 @@ module Kitchen
26
26
  #
27
27
  # @author Tyler Ball <tball@chef.io>
28
28
  class InstanceGenerator
29
- attr_reader :config, :ec2, :logger
29
+ # @return [Hash] the driver config the payload is built from
30
+ attr_reader :config
30
31
 
32
+ # @return [Kitchen::Driver::Aws::Client] the driver's EC2 client wrapper
33
+ attr_reader :ec2
34
+
35
+ # @return [Kitchen::Logger] the logger to report through
36
+ attr_reader :logger
37
+
38
+ # @param config [Hash] the driver config
39
+ # @param ec2 [Kitchen::Driver::Aws::Client] the driver's EC2 client wrapper
40
+ # @param logger [Kitchen::Logger] the logger to report through
31
41
  def initialize(config, ec2, logger)
32
42
  @config = config
33
43
  @ec2 = ec2
34
44
  @logger = logger
35
45
  end
36
46
 
37
- # Transform the provided kitchen config into the hash we'll use to create the aws instance
38
- # can be passed in null, others need to be omitted if they are null
39
- # Some fields can be passed in null, others need to be omitted if they are null
40
- # @return [Hash]
47
+ # Build the RunInstances payload from the driver config.
48
+ #
49
+ # Some EC2 fields accept an explicit nil and others must be omitted
50
+ # entirely, so optional settings are added conditionally rather than
51
+ # always being present with a nil value.
52
+ #
53
+ # Two lookups happen here as a side effect, because both need to resolve
54
+ # before the payload can be built: a subnet is resolved from
55
+ # `subnet_filter` (and written back into the config), and security
56
+ # groups are resolved from `security_group_filter` within that subnet's
57
+ # VPC. Both are skipped when the corresponding ID is already set.
58
+ #
59
+ # @return [Hash] parameters for `Aws::EC2::Resource#create_instances`
60
+ # @raise [RuntimeError] when a subnet or security group filter matches
61
+ # nothing, since launching into an unintended network is worse than
62
+ # failing
63
+ # @see https://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Resource.html#create_instances-instance_method
41
64
  def ec2_instance_data
42
65
  # Support for looking up security group id and subnet id using tags.
43
66
  vpc_id = nil
@@ -130,21 +153,6 @@ module Kitchen
130
153
  i[:tag_specifications] = [instance_tag_spec, volume_tag_spec]
131
154
  end
132
155
 
133
- availability_zone = config[:availability_zone]
134
- if availability_zone
135
- if /^[a-z]$/i.match?(availability_zone)
136
- availability_zone = "#{config[:region]}#{availability_zone}"
137
- end
138
- i[:placement] = { availability_zone: availability_zone.downcase }
139
- end
140
- tenancy = config[:tenancy]
141
- if tenancy
142
- if i.key?(:placement)
143
- i[:placement][:tenancy] = tenancy
144
- else
145
- i[:placement] = { tenancy: }
146
- end
147
- end
148
156
  unless config[:block_device_mappings].nil? || config[:block_device_mappings].empty?
149
157
  i[:block_device_mappings] = config[:block_device_mappings]
150
158
  end
@@ -177,6 +185,8 @@ module Kitchen
177
185
  i[:network_interfaces][0][:ipv_6_address_count] = 1
178
186
  end
179
187
  end
188
+ # A bare zone letter is a shorthand for that zone within the
189
+ # configured region, so "b" in us-west-2 becomes "us-west-2b".
180
190
  availability_zone = config[:availability_zone]
181
191
  if availability_zone
182
192
  if /^[a-z]$/i.match?(availability_zone)
@@ -236,6 +246,18 @@ module Kitchen
236
246
  i
237
247
  end
238
248
 
249
+ # The user data script, base64 encoded as EC2 requires.
250
+ #
251
+ # The configured value is treated as a file path when it names an
252
+ # existing file, and as inline script content otherwise. Content
253
+ # containing a null byte is always treated as inline, both because a
254
+ # path cannot contain one and because `File.file?` would raise on it.
255
+ #
256
+ # The result is memoized: the file is read once per driver, not once per
257
+ # call.
258
+ #
259
+ # @return [String, nil] base64 encoded user data, or nil when none is
260
+ # configured
239
261
  def prepared_user_data
240
262
  # If user_data is a file reference, lets read it as such
241
263
  return nil if config[:user_data].nil?
@@ -23,6 +23,8 @@ module Kitchen
23
23
  class Aws
24
24
  # Manages AWS Systems Manager Session Manager connections for Test Kitchen
25
25
  class SsmSessionManager
26
+ # @param config [Hash] the driver config
27
+ # @param logger [Kitchen::Logger] the logger to report through
26
28
  def initialize(config, logger)
27
29
  @config = config
28
30
  @logger = logger
@@ -32,7 +34,13 @@ module Kitchen
32
34
  )
33
35
  end
34
36
 
35
- # Check if SSM agent is running on the instance
37
+ # Whether the SSM agent on an instance has checked in and is reachable.
38
+ #
39
+ # Polled while an instance boots, so an API error is reported as "not
40
+ # ready" rather than raised.
41
+ #
42
+ # @param instance_id [String] the instance to check
43
+ # @return [Boolean] true when the agent is registered and online
36
44
  def ssm_agent_available?(instance_id)
37
45
  @logger.debug("Checking if SSM agent is available on instance #{instance_id}")
38
46
 
@@ -62,7 +70,12 @@ module Kitchen
62
70
  end
63
71
  end
64
72
 
65
- # Verify that the AWS CLI session manager plugin is installed
73
+ # Whether the AWS CLI Session Manager plugin is installed locally.
74
+ #
75
+ # The plugin is a separate download from the AWS CLI itself and is
76
+ # required to open a session.
77
+ #
78
+ # @return [Boolean] true when the plugin responds to `--version`
66
79
  def session_manager_plugin_installed?
67
80
  _output, status = Open3.capture2e("session-manager-plugin", "--version")
68
81
  installed = status.success?
@@ -75,7 +88,11 @@ module Kitchen
75
88
  end
76
89
 
77
90
  installed
78
- rescue StandardError => e
91
+ # ::StandardError, not StandardError: this file is nested inside
92
+ # `module Kitchen`, which defines Kitchen::StandardError. An unqualified
93
+ # constant resolves to that one, which would let the Errno::ENOENT
94
+ # raised by a missing plugin escape the check meant to detect it.
95
+ rescue ::StandardError => e
79
96
  @logger.warn("Error checking for session-manager-plugin: #{e.message}")
80
97
  false
81
98
  end
@@ -24,12 +24,23 @@ module Kitchen
24
24
  StandardPlatform.platforms["alma"] = self
25
25
  StandardPlatform.platforms["almalinux"] = self
26
26
 
27
- # default username for this platform's ami
28
- # @return [String]
27
+ # The account EC2 creates on this platform's official AMIs.
28
+ #
29
+ # Used as the SSH username when the transport does not specify one.
30
+ #
31
+ # @return [String] the default SSH username
29
32
  def username
30
33
  "ec2-user"
31
34
  end
32
35
 
36
+ # EC2 image filters that select AlmaLinux OS images published by the AlmaLinux project.
37
+ #
38
+ # A filter is added for {StandardPlatform#architecture} only when one was
39
+ # requested, so that an unspecified architecture matches any of them.
40
+ #
41
+ # @return [Hash{String => String, Array<String>}] filter name to the value
42
+ # or values it must match
43
+ # @see StandardPlatform#find_image
33
44
  def image_search
34
45
  search = {
35
46
  "owner-id" => "764336703387",
@@ -39,6 +50,14 @@ module Kitchen
39
50
  search
40
51
  end
41
52
 
53
+ # Detect this platform from an EC2 image.
54
+ #
55
+ # Matching is done on the image name, which is the only reliable signal
56
+ # EC2 exposes about what an AMI actually contains.
57
+ #
58
+ # @param driver [Kitchen::Driver::Ec2] the driver requesting detection
59
+ # @param image [Aws::EC2::Image] the image to inspect
60
+ # @return [Alma, nil] a platform when the image is AlmaLinux, otherwise nil
42
61
  def self.from_image(driver, image)
43
62
  return unless /AlmaLinux OS/i.match?(image.name)
44
63
 
@@ -23,12 +23,23 @@ 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
+ # The account EC2 creates on this platform's official AMIs.
27
+ #
28
+ # Used as the SSH username when the transport does not specify one.
29
+ #
30
+ # @return [String] the default SSH username
28
31
  def username
29
32
  "ec2-user"
30
33
  end
31
34
 
35
+ # EC2 image filters that select the original Amazon Linux AMIs published by AWS.
36
+ #
37
+ # A filter is added for {StandardPlatform#architecture} only when one was
38
+ # requested, so that an unspecified architecture matches any of them.
39
+ #
40
+ # @return [Hash{String => String, Array<String>}] filter name to the value
41
+ # or values it must match
42
+ # @see StandardPlatform#find_image
32
43
  def image_search
33
44
  search = {
34
45
  "owner-id" => "137112412989",
@@ -38,10 +49,22 @@ module Kitchen
38
49
  search
39
50
  end
40
51
 
52
+ # Detect this platform from an EC2 image.
53
+ #
54
+ # Matching is done on the image name, which is the only reliable signal
55
+ # EC2 exposes about what an AMI actually contains.
56
+ #
57
+ # @param driver [Kitchen::Driver::Ec2] the driver requesting detection
58
+ # @param image [Aws::EC2::Image] the image to inspect
59
+ # @return [Amazon, nil] a platform when the image is Amazon Linux, otherwise nil
41
60
  def self.from_image(driver, image)
42
61
  return unless /amzn-ami/i.match?(image.name)
43
62
 
44
- image.name =~ /\b(\d+(\.\d+[\.\d])?)/i
63
+ # `(\.\d+)?`, not `(\.\d+[\.\d])?`: the character class matched the
64
+ # separator that follows the minor version, capturing it into the
65
+ # version string ("2018.03.") and failing outright when the minor
66
+ # version was followed by anything but a dot or digit.
67
+ image.name =~ /\b(\d+(\.\d+)?)/i
45
68
  new(driver, "amazon", (Regexp.last_match || [])[1], image.architecture)
46
69
  end
47
70
  end
@@ -23,12 +23,23 @@ 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
+ # The account EC2 creates on this platform's official AMIs.
27
+ #
28
+ # Used as the SSH username when the transport does not specify one.
29
+ #
30
+ # @return [String] the default SSH username
28
31
  def username
29
32
  "ec2-user"
30
33
  end
31
34
 
35
+ # EC2 image filters that select Amazon Linux 2 AMIs published by AWS.
36
+ #
37
+ # A filter is added for {StandardPlatform#architecture} only when one was
38
+ # requested, so that an unspecified architecture matches any of them.
39
+ #
40
+ # @return [Hash{String => String, Array<String>}] filter name to the value
41
+ # or values it must match
42
+ # @see StandardPlatform#find_image
32
43
  def image_search
33
44
  search = {
34
45
  "owner-id" => "137112412989",
@@ -38,10 +49,22 @@ module Kitchen
38
49
  search
39
50
  end
40
51
 
52
+ # Detect this platform from an EC2 image.
53
+ #
54
+ # Matching is done on the image name, which is the only reliable signal
55
+ # EC2 exposes about what an AMI actually contains.
56
+ #
57
+ # @param driver [Kitchen::Driver::Ec2] the driver requesting detection
58
+ # @param image [Aws::EC2::Image] the image to inspect
59
+ # @return [Amazon2, nil] a platform when the image is Amazon Linux 2, otherwise nil
41
60
  def self.from_image(driver, image)
42
61
  return unless /amzn2-ami/i.match?(image.name)
43
62
 
44
- image.name =~ /\b(\d+(\.\d+[\.\d])?)/i
63
+ # `(\.\d+)?`, not `(\.\d+[\.\d])?`: the character class matched the
64
+ # separator that follows the minor version, capturing it into the
65
+ # version string ("2018.03.") and failing outright when the minor
66
+ # version was followed by anything but a dot or digit.
67
+ image.name =~ /\b(\d+(\.\d+)?)/i
45
68
  new(driver, "amazon2", (Regexp.last_match || [])[1], image.architecture)
46
69
  end
47
70
  end
@@ -23,12 +23,23 @@ module Kitchen
23
23
  class Amazon2023 < StandardPlatform
24
24
  StandardPlatform.platforms["amazon2023"] = self
25
25
 
26
- # default username for this platform's ami
27
- # @return [String]
26
+ # The account EC2 creates on this platform's official AMIs.
27
+ #
28
+ # Used as the SSH username when the transport does not specify one.
29
+ #
30
+ # @return [String] the default SSH username
28
31
  def username
29
32
  "ec2-user"
30
33
  end
31
34
 
35
+ # EC2 image filters that select Amazon Linux 2023 AMIs published by AWS.
36
+ #
37
+ # A filter is added for {StandardPlatform#architecture} only when one was
38
+ # requested, so that an unspecified architecture matches any of them.
39
+ #
40
+ # @return [Hash{String => String, Array<String>}] filter name to the value
41
+ # or values it must match
42
+ # @see StandardPlatform#find_image
32
43
  def image_search
33
44
  search = {
34
45
  "owner-id" => "137112412989",
@@ -38,10 +49,22 @@ module Kitchen
38
49
  search
39
50
  end
40
51
 
52
+ # Detect this platform from an EC2 image.
53
+ #
54
+ # Matching is done on the image name, which is the only reliable signal
55
+ # EC2 exposes about what an AMI actually contains.
56
+ #
57
+ # @param driver [Kitchen::Driver::Ec2] the driver requesting detection
58
+ # @param image [Aws::EC2::Image] the image to inspect
59
+ # @return [Amazon2023, nil] a platform when the image is Amazon Linux 2023, otherwise nil
41
60
  def self.from_image(driver, image)
42
61
  return unless /al2023-ami/i.match?(image.name)
43
62
 
44
- image.name =~ /\b(\d+(\.\d+[\.\d])?)/i
63
+ # `(\.\d+)?`, not `(\.\d+[\.\d])?`: the character class matched the
64
+ # separator that follows the minor version, capturing it into the
65
+ # version string ("2018.03.") and failing outright when the minor
66
+ # version was followed by anything but a dot or digit.
67
+ image.name =~ /\b(\d+(\.\d+)?)/i
45
68
  new(driver, "amazon2023", (Regexp.last_match || [])[1], image.architecture)
46
69
  end
47
70
  end
@@ -23,14 +23,31 @@ module Kitchen
23
23
  class Centos < StandardPlatform
24
24
  StandardPlatform.platforms["centos"] = self
25
25
 
26
+ # The AWS account CentOS publishes its images under. Releases from 8
27
+ # onwards are published directly rather than through the AWS
28
+ # Marketplace, so images are found by owner rather than by product.
29
+ #
30
+ # @return [String]
26
31
  CENTOS_OWNER_ID = "125523088429".freeze
27
32
 
28
- # default username for this platform's ami
29
- # @return [String]
33
+ # The account EC2 creates on this platform's official AMIs.
34
+ #
35
+ # CentOS 8 and earlier ship a "centos" account; Stream 9 moved to the
36
+ # "ec2-user" convention used by the other EL-family distributions.
37
+ #
38
+ # @return [String] the default SSH username
30
39
  def username
31
40
  version && version.to_f < 9.0 ? "centos" : "ec2-user"
32
41
  end
33
42
 
43
+ # EC2 image filters that select CentOS Linux and CentOS Stream images.
44
+ #
45
+ # A filter is added for {StandardPlatform#architecture} only when one was
46
+ # requested, so that an unspecified architecture matches any of them.
47
+ #
48
+ # @return [Hash{String => String, Array<String>}] filter name to the value
49
+ # or values it must match
50
+ # @see StandardPlatform#find_image
34
51
  def image_search
35
52
  # Version 8+ are published directly, not to the AWS marketplace. Use OWNER ID.
36
53
  search = {
@@ -42,6 +59,14 @@ module Kitchen
42
59
  search
43
60
  end
44
61
 
62
+ # Sort images newest release first.
63
+ #
64
+ # CentOS mixes bare majors ("CentOS Stream 9") with dotted releases
65
+ # ("CentOS 7.9"). A bare major is scored as ".999" so that Stream 9
66
+ # ranks above 9.0 while still ranking below 10.
67
+ #
68
+ # @param images [Array<Aws::EC2::Image>] the images to sort
69
+ # @return [Array<Aws::EC2::Image>] the images, newest release first
45
70
  def sort_by_version(images)
46
71
  # 7.1 -> [ img1, img2, img3 ]
47
72
  # 6 -> [ img4, img5 ]
@@ -51,6 +76,14 @@ module Kitchen
51
76
  .reverse.flat_map { |_k, v| v }
52
77
  end
53
78
 
79
+ # Detect this platform from an EC2 image.
80
+ #
81
+ # Matching is done on the image name, which is the only reliable signal
82
+ # EC2 exposes about what an AMI actually contains.
83
+ #
84
+ # @param driver [Kitchen::Driver::Ec2] the driver requesting detection
85
+ # @param image [Aws::EC2::Image] the image to inspect
86
+ # @return [Centos, nil] a platform when the image is CentOS, otherwise nil
54
87
  def self.from_image(driver, image)
55
88
  return unless /centos/i.match?(image.name)
56
89