kitchen-ec2 3.5.0 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/kitchen/driver/aws/client.rb +10 -1
- data/lib/kitchen/driver/aws/instance_generator.rb +21 -6
- data/lib/kitchen/driver/aws/standard_platform/amazon.rb +3 -1
- data/lib/kitchen/driver/aws/standard_platform/amazon2.rb +3 -1
- data/lib/kitchen/driver/aws/standard_platform/centos.rb +28 -3
- data/lib/kitchen/driver/aws/standard_platform/debian.rb +5 -2
- data/lib/kitchen/driver/aws/standard_platform/fedora.rb +3 -1
- data/lib/kitchen/driver/aws/standard_platform/freebsd.rb +3 -1
- data/lib/kitchen/driver/aws/standard_platform/rhel.rb +10 -1
- data/lib/kitchen/driver/aws/standard_platform/ubuntu.rb +3 -1
- data/lib/kitchen/driver/aws/standard_platform/windows.rb +3 -1
- data/lib/kitchen/driver/ec2.rb +71 -109
- data/lib/kitchen/driver/ec2_version.rb +1 -2
- metadata +9 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 34d9b99c9fe048be513d606f50abb1318d6aa71541fa720308770311d1066a75
|
|
4
|
+
data.tar.gz: abb3e57324a99271ade0ea238ac759e0790934b727729d74ab6cc7f0887f6997
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f829ebf7964c3588642d6cfd20ca78152098bc3ed700ca839d443c934b46b5ee61afcb7ee713ef2b2cc4acc9921d550c03e1de2914be3265852d56b554bbe050
|
|
7
|
+
data.tar.gz: 9600e83fcde5acf5e9e98b91cf9e44033fa1aa405aec192ee5774fc1ea6ec4afc8f03cb485be8766a1f0804a4ab9a349f8353851c92348a53c60f8b5c3f3a2c5
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
1
|
#
|
|
3
2
|
# Author:: Tyler Ball (<tball@chef.io>)
|
|
4
3
|
#
|
|
@@ -49,14 +48,24 @@ module Kitchen
|
|
|
49
48
|
::Aws.config.update(retry_limit: retry_limit) unless retry_limit.nil?
|
|
50
49
|
end
|
|
51
50
|
|
|
51
|
+
# create a new AWS EC2 instance
|
|
52
|
+
# @param options [Hash] has of instance options
|
|
53
|
+
# @see https://docs.aws.amazon.com/sdkforruby/api/Aws/EC2/Resource.html#create_instances-instance_method
|
|
54
|
+
# @return [Aws::EC2::Instance]
|
|
52
55
|
def create_instance(options)
|
|
53
56
|
resource.create_instances(options).first
|
|
54
57
|
end
|
|
55
58
|
|
|
59
|
+
# get an instance object given an id
|
|
60
|
+
# @param id [String] aws instance id
|
|
61
|
+
# @return [Aws::EC2::Instance]
|
|
56
62
|
def get_instance(id)
|
|
57
63
|
resource.instance(id)
|
|
58
64
|
end
|
|
59
65
|
|
|
66
|
+
# get an instance object given a spot request ID
|
|
67
|
+
# @param request_id [String] aws spot instance id
|
|
68
|
+
# @return [Aws::EC2::Instance]
|
|
60
69
|
def get_instance_from_spot_request(request_id)
|
|
61
70
|
resource.instances(
|
|
62
71
|
filters: [{
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
1
|
#
|
|
3
2
|
# Author:: Tyler Ball (<tball@chef.io>)
|
|
4
3
|
#
|
|
@@ -17,7 +16,7 @@
|
|
|
17
16
|
# See the License for the specific language governing permissions and
|
|
18
17
|
# limitations under the License.
|
|
19
18
|
|
|
20
|
-
require "base64"
|
|
19
|
+
require "base64" unless defined?(Base64)
|
|
21
20
|
require "aws-sdk-ec2"
|
|
22
21
|
|
|
23
22
|
module Kitchen
|
|
@@ -39,8 +38,10 @@ module Kitchen
|
|
|
39
38
|
@logger = logger
|
|
40
39
|
end
|
|
41
40
|
|
|
42
|
-
# Transform the provided config into the hash
|
|
41
|
+
# Transform the provided kitchen config into the hash we'll use to create the aws instance
|
|
43
42
|
# can be passed in null, others need to be ommitted if they are null
|
|
43
|
+
# Some fields can be passed in null, others need to be ommitted if they are null
|
|
44
|
+
# @return [Hash]
|
|
44
45
|
def ec2_instance_data # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
45
46
|
# Support for looking up security group id and subnet id using tags.
|
|
46
47
|
vpc_id = nil
|
|
@@ -57,7 +58,7 @@ module Kitchen
|
|
|
57
58
|
raise "The subnet tagged '#{config[:subnet_filter][:tag]}:#{config[:subnet_filter][:value]}' does not exist!" unless subnets.any?
|
|
58
59
|
|
|
59
60
|
# => Select the least-populated subnet if we have multiple matches
|
|
60
|
-
subnet = subnets.
|
|
61
|
+
subnet = subnets.max_by { |s| s[:available_ip_address_count] }
|
|
61
62
|
vpc_id = subnet.vpc_id
|
|
62
63
|
config[:subnet_id] = subnet.subnet_id
|
|
63
64
|
end
|
|
@@ -113,11 +114,25 @@ module Kitchen
|
|
|
113
114
|
key_name: config[:aws_ssh_key_id],
|
|
114
115
|
subnet_id: config[:subnet_id],
|
|
115
116
|
private_ip_address: config[:private_ip_address],
|
|
117
|
+
min_count: 1,
|
|
118
|
+
max_count: 1,
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
if config[:tags] && !config[:tags].empty?
|
|
122
|
+
tags = config[:tags].map do |k, v|
|
|
123
|
+
# we convert the value to a string because
|
|
124
|
+
# nils should be passed as an empty String
|
|
125
|
+
# and Integers need to be represented as Strings
|
|
126
|
+
{ key: k, value: v.to_s }
|
|
127
|
+
end
|
|
128
|
+
instance_tag_spec = { resource_type: "instance", tags: tags }
|
|
129
|
+
volume_tag_spec = { resource_type: "volume", tags: tags }
|
|
130
|
+
i[:tag_specifications] = [instance_tag_spec, volume_tag_spec]
|
|
131
|
+
end
|
|
132
|
+
|
|
118
133
|
availability_zone = config[:availability_zone]
|
|
119
134
|
if availability_zone
|
|
120
|
-
if
|
|
135
|
+
if /^[a-z]$/i.match?(availability_zone)
|
|
121
136
|
availability_zone = "#{config[:region]}#{availability_zone}"
|
|
122
137
|
end
|
|
123
138
|
i[:placement] = { availability_zone: availability_zone.downcase }
|
|
@@ -160,7 +175,7 @@ module Kitchen
|
|
|
160
175
|
end
|
|
161
176
|
availability_zone = config[:availability_zone]
|
|
162
177
|
if availability_zone
|
|
163
|
-
if
|
|
178
|
+
if /^[a-z]$/i.match?(availability_zone)
|
|
164
179
|
availability_zone = "#{config[:region]}#{availability_zone}"
|
|
165
180
|
end
|
|
166
181
|
i[:placement] = { availability_zone: availability_zone.downcase }
|
|
@@ -23,6 +23,8 @@ module Kitchen
|
|
|
23
23
|
class Amazon < StandardPlatform
|
|
24
24
|
StandardPlatform.platforms["amazon"] = self
|
|
25
25
|
|
|
26
|
+
# default username for this platform's ami
|
|
27
|
+
# @return [String]
|
|
26
28
|
def username
|
|
27
29
|
"ec2-user"
|
|
28
30
|
end
|
|
@@ -37,7 +39,7 @@ module Kitchen
|
|
|
37
39
|
end
|
|
38
40
|
|
|
39
41
|
def self.from_image(driver, image)
|
|
40
|
-
if
|
|
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
|
|
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-
|
|
37
|
-
"name" => ["CentOS
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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]
|
data/lib/kitchen/driver/ec2.rb
CHANGED
|
@@ -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
|
|
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
|
-
#
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
423
|
+
def submit_spots
|
|
449
424
|
configs = [config]
|
|
450
425
|
expanded = []
|
|
451
|
-
keys = %i{instance_type
|
|
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
|
|
464
|
+
return submit_spot
|
|
466
465
|
rescue => e
|
|
467
466
|
errs.append(e)
|
|
468
467
|
end
|
|
@@ -470,86 +469,47 @@ module Kitchen
|
|
|
470
469
|
raise ["Could not create a spot instance:", errs].flatten.join("\n")
|
|
471
470
|
end
|
|
472
471
|
|
|
473
|
-
def submit_spot
|
|
472
|
+
def submit_spot
|
|
474
473
|
debug("Creating EC2 Spot Instance..")
|
|
474
|
+
instance_data = instance_generator.ec2_instance_data
|
|
475
475
|
|
|
476
|
-
spot_request_id = create_spot_request
|
|
477
|
-
# deleting the instance cancels the request, but deleting the request
|
|
478
|
-
# does not affect the instance
|
|
479
|
-
state[:spot_request_id] = spot_request_id
|
|
480
|
-
ec2.client.wait_until(
|
|
481
|
-
:spot_instance_request_fulfilled,
|
|
482
|
-
spot_instance_request_ids: [spot_request_id]
|
|
483
|
-
) do |w|
|
|
484
|
-
w.max_attempts = config[:spot_wait] / config[:retryable_sleep]
|
|
485
|
-
w.delay = config[:retryable_sleep]
|
|
486
|
-
w.before_attempt do |attempts|
|
|
487
|
-
c = attempts * config[:retryable_sleep]
|
|
488
|
-
t = config[:spot_wait]
|
|
489
|
-
info "Waited #{c}/#{t}s for spot request <#{spot_request_id}> to become fulfilled."
|
|
490
|
-
end
|
|
491
|
-
end
|
|
492
|
-
ec2.get_instance_from_spot_request(spot_request_id)
|
|
493
|
-
end
|
|
494
|
-
|
|
495
|
-
def create_spot_request
|
|
496
|
-
request_duration = config[:spot_wait]
|
|
497
476
|
config_spot_price = config[:spot_price].to_s
|
|
498
477
|
if %w{ondemand on-demand}.include?(config_spot_price)
|
|
499
478
|
spot_price = ""
|
|
500
479
|
else
|
|
501
480
|
spot_price = config_spot_price
|
|
502
481
|
end
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
482
|
+
spot_options = {
|
|
483
|
+
# Must use one-time in order to use instance_interruption_behavior=terminate
|
|
484
|
+
# spot_instance_type: "one-time", # default
|
|
485
|
+
# Must use instance_interruption_behavior=terminate in order to use block_duration_minutes
|
|
486
|
+
# instance_interruption_behavior: "terminate", # default
|
|
507
487
|
}
|
|
508
488
|
if config[:block_duration_minutes]
|
|
509
|
-
|
|
489
|
+
spot_options[:block_duration_minutes] = config[:block_duration_minutes]
|
|
510
490
|
end
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
(described_volume_count > 0) && (described_volume_count == ready_volume_count)
|
|
500
|
+
# The preferred way to create a spot instance is via request_spot_instances()
|
|
501
|
+
# However, it does not allow for tagging to occur at creation time.
|
|
502
|
+
# create_instances() allows creation of tagged spot instances, but does
|
|
503
|
+
# not retry if the price could not be satisfied immediately.
|
|
504
|
+
Retryable.retryable(
|
|
505
|
+
tries: config[:spot_wait] / config[:retryable_sleep],
|
|
506
|
+
sleep: lambda { |_n| config[:retryable_sleep] },
|
|
507
|
+
on: ::Aws::EC2::Errors::SpotMaxPriceTooLow
|
|
508
|
+
) do |retries|
|
|
509
|
+
c = retries * config[:retryable_sleep]
|
|
510
|
+
t = config[:spot_wait]
|
|
511
|
+
info "Waited #{c}/#{t}s for spot request to become fulfilled."
|
|
512
|
+
ec2.create_instance(instance_data)
|
|
553
513
|
end
|
|
554
514
|
end
|
|
555
515
|
|
|
@@ -825,7 +785,9 @@ module Kitchen
|
|
|
825
785
|
ip_protocol: "tcp",
|
|
826
786
|
from_port: port,
|
|
827
787
|
to_port: port,
|
|
828
|
-
ip_ranges:
|
|
788
|
+
ip_ranges: Array(config[:security_group_cidr_ip]).map do |cidr_ip|
|
|
789
|
+
{ cidr_ip: cidr_ip }
|
|
790
|
+
end,
|
|
829
791
|
}
|
|
830
792
|
end
|
|
831
793
|
)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
|
2
1
|
#
|
|
3
2
|
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
|
4
3
|
#
|
|
@@ -22,6 +21,6 @@ module Kitchen
|
|
|
22
21
|
module Driver
|
|
23
22
|
|
|
24
23
|
# Version string for EC2 Test Kitchen driver
|
|
25
|
-
EC2_VERSION = "3.
|
|
24
|
+
EC2_VERSION = "3.8.0".freeze
|
|
26
25
|
end
|
|
27
26
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-ec2
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fletcher Nichol
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-10-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: test-kitchen
|
|
@@ -30,34 +30,6 @@ dependencies:
|
|
|
30
30
|
- - "<"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '3'
|
|
33
|
-
- !ruby/object:Gem::Dependency
|
|
34
|
-
name: excon
|
|
35
|
-
requirement: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - ">="
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0'
|
|
40
|
-
type: :runtime
|
|
41
|
-
prerelease: false
|
|
42
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - ">="
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0'
|
|
47
|
-
- !ruby/object:Gem::Dependency
|
|
48
|
-
name: multi_json
|
|
49
|
-
requirement: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - ">="
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0'
|
|
54
|
-
type: :runtime
|
|
55
|
-
prerelease: false
|
|
56
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - ">="
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0'
|
|
61
33
|
- !ruby/object:Gem::Dependency
|
|
62
34
|
name: aws-sdk-ec2
|
|
63
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -134,20 +106,6 @@ dependencies:
|
|
|
134
106
|
- - "~>"
|
|
135
107
|
- !ruby/object:Gem::Version
|
|
136
108
|
version: '0.6'
|
|
137
|
-
- !ruby/object:Gem::Dependency
|
|
138
|
-
name: simplecov
|
|
139
|
-
requirement: !ruby/object:Gem::Requirement
|
|
140
|
-
requirements:
|
|
141
|
-
- - "~>"
|
|
142
|
-
- !ruby/object:Gem::Version
|
|
143
|
-
version: '0.7'
|
|
144
|
-
type: :development
|
|
145
|
-
prerelease: false
|
|
146
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
147
|
-
requirements:
|
|
148
|
-
- - "~>"
|
|
149
|
-
- !ruby/object:Gem::Version
|
|
150
|
-
version: '0.7'
|
|
151
109
|
- !ruby/object:Gem::Dependency
|
|
152
110
|
name: yard
|
|
153
111
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -168,14 +126,14 @@ dependencies:
|
|
|
168
126
|
requirements:
|
|
169
127
|
- - '='
|
|
170
128
|
- !ruby/object:Gem::Version
|
|
171
|
-
version:
|
|
129
|
+
version: 1.4.4
|
|
172
130
|
type: :development
|
|
173
131
|
prerelease: false
|
|
174
132
|
version_requirements: !ruby/object:Gem::Requirement
|
|
175
133
|
requirements:
|
|
176
134
|
- - '='
|
|
177
135
|
- !ruby/object:Gem::Version
|
|
178
|
-
version:
|
|
136
|
+
version: 1.4.4
|
|
179
137
|
- !ruby/object:Gem::Dependency
|
|
180
138
|
name: climate_control
|
|
181
139
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -216,7 +174,7 @@ homepage: https://github.com/test-kitchen/kitchen-ec2
|
|
|
216
174
|
licenses:
|
|
217
175
|
- Apache-2.0
|
|
218
176
|
metadata: {}
|
|
219
|
-
post_install_message:
|
|
177
|
+
post_install_message:
|
|
220
178
|
rdoc_options: []
|
|
221
179
|
require_paths:
|
|
222
180
|
- lib
|
|
@@ -224,15 +182,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
224
182
|
requirements:
|
|
225
183
|
- - ">="
|
|
226
184
|
- !ruby/object:Gem::Version
|
|
227
|
-
version: '2.
|
|
185
|
+
version: '2.4'
|
|
228
186
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
229
187
|
requirements:
|
|
230
188
|
- - ">="
|
|
231
189
|
- !ruby/object:Gem::Version
|
|
232
190
|
version: '0'
|
|
233
191
|
requirements: []
|
|
234
|
-
rubygems_version: 3.1.
|
|
235
|
-
signing_key:
|
|
192
|
+
rubygems_version: 3.1.4
|
|
193
|
+
signing_key:
|
|
236
194
|
specification_version: 4
|
|
237
195
|
summary: A Test Kitchen Driver for Amazon EC2
|
|
238
196
|
test_files: []
|