kitchen-ec2 1.2.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8f4428e6497e1981ef132bd6c518ca4f347ed0aa
4
- data.tar.gz: 8f2014042f18b8f1ab66e8b694141535ff365696
3
+ metadata.gz: b0d06c5717fea6db5e691129f2e7b2c29126ad83
4
+ data.tar.gz: 9fe52faf85c23d960c618c26fdd28145a2a1f761
5
5
  SHA512:
6
- metadata.gz: c6f81b68882cb0d853f9b111543373e7c545fa5f57f3c8bf60de9a38aaea237ea2955e6768a632823722e0d482f56444e317c7f670940f2d6db19ebb32946334
7
- data.tar.gz: a6e0a708723aef725301b6690b83eb5b7807cbb2fd1aff89e2ab86f5f460ce09d59c0a380ff0ecce585178e41cf6caf0dfb6c39d20a1b58013105268ba571dde
6
+ metadata.gz: acdc60d833d8c93b17eb73914e207101c8d5cd22945857155e70ad91f99586418f434929039c61fdba6252a73f60f179733b2a67d66f31eff934a9904b143e4f
7
+ data.tar.gz: be0377db0b71c4e76ebecde7310c572d4081530eee5256d4d6c0c17a8eab2e2d76413797a3f293b5580230c4779928f19f2c808bd15c3062390e7fbc53d56657
data/.travis.yml CHANGED
@@ -2,6 +2,6 @@ language: ruby
2
2
  cache: bundler
3
3
  sudo: false
4
4
  rvm:
5
- - 2.0.0
6
- - 2.1
7
- - 2.2
5
+ - 2.2.6
6
+ - 2.3.1
7
+ - ruby-head
data/CHANGELOG.md CHANGED
@@ -1,7 +1,22 @@
1
1
  # Change Log
2
2
 
3
- ## [1.2.0](https://github.com/test-kitchen/kitchen-ec2/tree/1.2.0) (2016-09-12)
4
- [Full Changelog](https://github.com/test-kitchen/kitchen-ec2/compare/v1.1.0...1.2.0)
3
+ ## [v1.3.0](https://github.com/test-kitchen/kitchen-ec2/tree/v1.3.0) (2017-02-10)
4
+ [Full Changelog](https://github.com/test-kitchen/kitchen-ec2/compare/v1.2.0...v1.3.0)
5
+
6
+ **Implemented Enhancements:**
7
+
8
+ - Support Windows 2016 [\#291](https://github.com/test-kitchen/kitchen-ec2/pull/291) ([gdavison](https://github.com/gdavison))
9
+ - Add expiration to spot requests [\#285](https://github.com/test-kitchen/kitchen-ec2/pull/285) ([alanbrent](https://github.com/alanbrent))
10
+ - Don't break if we're using a custom "platform" AMI [\#273](https://github.com/test-kitchen/kitchen-ec2/pull/273) ([hynd](https://github.com/hynd))
11
+ - Propagate tags to volumes [\#260](https://github.com/test-kitchen/kitchen-ec2/pull/260) ([mrbobbytables](https://github.com/mrbobbytables))
12
+ - In the client, only source creds from the shared file when necessary [\#259](https://github.com/test-kitchen/kitchen-ec2/pull/259) ([davidcpell](https://github.com/davidcpell))
13
+ - Add notes for AMI image name requirements [\#252](https://github.com/test-kitchen/kitchen-ec2/pull/252) ([freimer](https://github.com/freimer))
14
+ - Provide the option to set ssl\_peer\_verify to false [\#251](https://github.com/test-kitchen/kitchen-ec2/pull/251) ([mwrock](https://github.com/mwrock))
15
+ - Adding support for tenancy parameter in placement config. [\#235](https://github.com/test-kitchen/kitchen-ec2/pull/235) ([jcastillocano](https://github.com/jcastillocano))
16
+ - Lookup ID from tag [\#232](https://github.com/test-kitchen/kitchen-ec2/pull/232) ([dlukman](https://github.com/dlukman))
17
+
18
+ ## [v1.2.0](https://github.com/test-kitchen/kitchen-ec2/tree/v1.2.0) (2016-09-12)
19
+ [Full Changelog](https://github.com/test-kitchen/kitchen-ec2/compare/v1.1.0...v1.2.0)
5
20
 
6
21
  **Fixed bugs:**
7
22
 
data/Gemfile CHANGED
@@ -6,9 +6,10 @@ gem "test-kitchen"
6
6
  gem "winrm-transport"
7
7
  gem "winrm-fs"
8
8
  gem "activesupport", "~> 4.0"
9
+ gem "faraday-http-cache", "~> 1.3"
9
10
 
10
11
  group :test do
11
- gem "rake"
12
+ gem "rake", "< 12"
12
13
  gem "pry"
13
14
  end
14
15
 
data/README.md CHANGED
@@ -79,9 +79,10 @@ You can learn more about the available filters in the AWS CLI doc under `--filte
79
79
  ```yaml
80
80
  platforms:
81
81
  - name: ubuntu-14.04
82
- image_search:
83
- owner-id: "099720109477"
84
- name: ubuntu/images/*/ubuntu-*-14.04*
82
+ driver:
83
+ image_search:
84
+ owner-id: "099720109477"
85
+ name: ubuntu/images/*/ubuntu-*-14.04*
85
86
  ```
86
87
 
87
88
  In the event that there are multiple matches (as sometimes happens), we sort to
@@ -92,6 +93,16 @@ get the best results. In order of priority from greatest to least, we prefer:
92
93
  - 64-bit over 32-bit
93
94
  - The most recently created image (to pick up patch releases)
94
95
 
96
+ Note that the image_search method *requires* that the AMI image names be in a specific format.
97
+ Some examples are:
98
+
99
+ - Windows-2012
100
+ - Windows-2012r2
101
+ - Windows-2012r2sp1
102
+ - RHEL-7.2
103
+
104
+ It is safest to use the same naming convention as used by the public images published by the OS vendors on the AWS marketplace.
105
+
95
106
  #### `platform.name`
96
107
 
97
108
  The third way to specify the image is by leaving `image_id` and `image_search`
@@ -247,6 +258,20 @@ instance.
247
258
 
248
259
  The default is `["default"]`.
249
260
 
261
+ ### `security_group_filter`
262
+
263
+ The EC2 [security group][group_docs] which will be applied to the instance,
264
+ specified by tag. Only one group can be specified this way.
265
+
266
+ The default is unset, or `nil`.
267
+
268
+ An example of usage:
269
+ ```yaml
270
+ security_group_filter:
271
+ tag: 'Name'
272
+ value: 'example-group-name'
273
+ ```
274
+
250
275
  ### `region`
251
276
 
252
277
  **Required** The AWS [region][region_docs] to use.
@@ -260,6 +285,19 @@ The EC2 [subnet][subnet_docs] to use.
260
285
 
261
286
  The default is unset, or `nil`.
262
287
 
288
+ ### `subnet_filter`
289
+
290
+ The EC2 [subnet][subnet_docs] to use, specified by tag.
291
+
292
+ The default is unset, or `nil`.
293
+
294
+ An example of usage:
295
+ ```yaml
296
+ subnet_filter:
297
+ tag: 'Name'
298
+ value: 'example-subnet-name'
299
+ ```
300
+
263
301
  ### `tags`
264
302
 
265
303
  The Hash of EC tag name/value pairs which will be applied to the instance.
@@ -312,6 +350,10 @@ The default is `ENV["HTTPS_PROXY"] || ENV["HTTP_PROXY"]`. If you have these env
312
350
 
313
351
  **Note** - The AWS command line utility allow you to specify [two proxies](http://docs.aws.amazon.com/cli/latest/userguide/cli-http-proxy.html), one for HTTP and one for HTTPS. The AWS Ruby SDK only allows you to specify 1 proxy and because all requests are `https://` this proxy needs to support HTTPS.
314
352
 
353
+ ### `ssl_verify_peer`
354
+
355
+ If you need to turn off ssl certificate verification for HTTP calls made to AWS, set `ssl_verify_peer: false`.
356
+
315
357
  ### Disk Configuration
316
358
 
317
359
  #### <a name="config-block_device_mappings"></a> `block_device_mappings`
@@ -39,15 +39,17 @@ module Kitchen
39
39
  secret_access_key = nil,
40
40
  session_token = nil,
41
41
  http_proxy = nil,
42
- retry_limit = nil
42
+ retry_limit = nil,
43
+ ssl_verify_peer = true
43
44
  )
44
45
  creds = self.class.get_credentials(
45
- profile_name, access_key_id, secret_access_key, session_token
46
+ profile_name, access_key_id, secret_access_key, session_token, region
46
47
  )
47
48
  ::Aws.config.update(
48
49
  :region => region,
49
50
  :credentials => creds,
50
- :http_proxy => http_proxy
51
+ :http_proxy => http_proxy,
52
+ :ssl_verify_peer => ssl_verify_peer
51
53
  )
52
54
  ::Aws.config.update(:retry_limit => retry_limit) unless retry_limit.nil?
53
55
  end
@@ -55,8 +57,10 @@ module Kitchen
55
57
  # Try and get the credentials from an ordered list of locations
56
58
  # http://docs.aws.amazon.com/sdkforruby/api/index.html#Configuration
57
59
  # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
58
- def self.get_credentials(profile_name, access_key_id, secret_access_key, session_token)
59
- shared_creds = ::Aws::SharedCredentials.new(:profile_name => profile_name)
60
+ # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
61
+ def self.get_credentials(profile_name, access_key_id, secret_access_key, session_token,
62
+ region, options = {})
63
+ source_creds =
60
64
  if access_key_id && secret_access_key
61
65
  ::Aws::Credentials.new(access_key_id, secret_access_key, session_token)
62
66
  elsif ENV["AWS_ACCESS_KEY_ID"] && ENV["AWS_SECRET_ACCESS_KEY"]
@@ -65,14 +69,34 @@ module Kitchen
65
69
  ENV["AWS_SECRET_ACCESS_KEY"],
66
70
  ENV["AWS_SESSION_TOKEN"]
67
71
  )
68
- elsif shared_creds.loadable?
69
- shared_creds
72
+ elsif profile_name
73
+ ::Aws::SharedCredentials.new(:profile_name => profile_name)
70
74
  else
71
75
  ::Aws::InstanceProfileCredentials.new(:retries => 1)
72
76
  end
77
+
78
+ if options[:assume_role_arn] && options[:assume_role_session_name]
79
+ sts = ::Aws::STS::Client.new(:credentials => source_creds, :region => region)
80
+
81
+ assume_role_options = (options[:assume_role_options] || {}).merge(
82
+ :client => sts,
83
+ :role_arn => options[:assume_role_arn],
84
+ :role_session_name => options[:assume_role_session_name]
85
+ )
86
+
87
+ ::Aws::AssumeRoleCredentials.new(assume_role_options)
88
+ else
89
+ source_creds
90
+ end
73
91
  end
74
92
  # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
75
93
 
94
+ def self.get_shared_creds(profile_name)
95
+ ::Aws::SharedCredentials.new(:profile_name => profile_name)
96
+ rescue ::Aws::Errors::NoSuchProfileError
97
+ false
98
+ end
99
+
76
100
  def create_instance(options)
77
101
  resource.create_instances(options)[0]
78
102
  end
@@ -17,6 +17,7 @@
17
17
  # limitations under the License.
18
18
 
19
19
  require "base64"
20
+ require "aws-sdk"
20
21
 
21
22
  module Kitchen
22
23
 
@@ -40,6 +41,42 @@ module Kitchen
40
41
  # Transform the provided config into the hash to send to AWS. Some fields
41
42
  # can be passed in null, others need to be ommitted if they are null
42
43
  def ec2_instance_data # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
44
+ # Support for looking up security group id and subnet id using tags.
45
+
46
+ if config[:subnet_id].nil? && config[:subnet_filter]
47
+ config[:subnet_id] = ::Aws::EC2::Client.
48
+ new(:region => config[:region]).describe_subnets(
49
+ :filters => [
50
+ {
51
+ :name => "tag:#{config[:subnet_filter][:tag]}",
52
+ :values => [config[:subnet_filter][:value]]
53
+ }
54
+ ]
55
+ )[0][0].subnet_id
56
+
57
+ if config[:subnet_id].nil?
58
+ fail "The subnet tagged '#{config[:subnet_filter][:tag]}\
59
+ #{config[:subnet_filter][:value]}' does not exist!"
60
+ end
61
+ end
62
+
63
+ if config[:security_group_ids].nil? && config[:security_group_filter]
64
+ config[:security_group_ids] = [::Aws::EC2::Client.
65
+ new(:region => config[:region]).describe_security_groups(
66
+ :filters => [
67
+ {
68
+ :name => "tag:#{config[:security_group_filter][:tag]}",
69
+ :values => [config[:security_group_filter][:value]]
70
+ }
71
+ ]
72
+ )[0][0].group_id]
73
+
74
+ if config[:security_group_ids].nil?
75
+ fail "The group tagged '#{config[:security_group_filter][:tag]}\
76
+ #{config[:security_group_filter][:value]}' does not exist!"
77
+ end
78
+ end
79
+
43
80
  i = {
44
81
  :instance_type => config[:instance_type],
45
82
  :ebs_optimized => config[:ebs_optimized],
@@ -56,6 +93,14 @@ module Kitchen
56
93
  end
57
94
  i[:placement] = { :availability_zone => availability_zone.downcase }
58
95
  end
96
+ tenancy = config[:tenancy]
97
+ if tenancy && %w[default dedicated].include?(tenancy)
98
+ if i.key?(:placement)
99
+ i[:placement][:tenancy] = tenancy
100
+ else
101
+ i[:placement] = { :tenancy => tenancy }
102
+ end
103
+ end
59
104
  unless config[:block_device_mappings].nil? || config[:block_device_mappings].empty?
60
105
  i[:block_device_mappings] = config[:block_device_mappings]
61
106
  end
@@ -84,6 +129,21 @@ module Kitchen
84
129
  i[:network_interfaces][0][:groups] = i.delete(:security_group_ids)
85
130
  end
86
131
  end
132
+ availability_zone = config[:availability_zone]
133
+ if availability_zone
134
+ if availability_zone =~ /^[a-z]$/i
135
+ availability_zone = "#{config[:region]}#{availability_zone}"
136
+ end
137
+ i[:placement] = { :availability_zone => availability_zone.downcase }
138
+ end
139
+ tenancy = config[:tenancy]
140
+ if tenancy && %w[default dedicated].include?(tenancy)
141
+ if i.key?(:placement)
142
+ i[:placement][:tenancy] = tenancy
143
+ else
144
+ i[:placement] = { :tenancy => tenancy }
145
+ end
146
+ end
87
147
  unless config[:instance_initiated_shutdown_behavior].nil? ||
88
148
  config[:instance_initiated_shutdown_behavior].empty?
89
149
  i[:instance_initiated_shutdown_behavior] = config[:instance_initiated_shutdown_behavior]
@@ -16,7 +16,8 @@ module Kitchen
16
16
  #
17
17
  # "windows" -> [nil, nil, nil]
18
18
  # Windows_Server-*-R*_RTM-, Windows_Server-*-R*_SP*-,
19
- # Windows_Server-*-RTM-, Windows_Server-*-SP*-
19
+ # Windows_Server-*-RTM-, Windows_Server-*-SP*-,
20
+ # Windows_Server-*-
20
21
  # "windows-2012" -> [2012, 0, nil]
21
22
  # Windows_Server-2012-RTM-, Windows_Server-2012-SP*-
22
23
  # "windows-2012r2" -> [2012, 2, nil]
@@ -29,6 +30,8 @@ module Kitchen
29
30
  # Windows_Server-2012-R2_SP1-
30
31
  # "windows-2012r2rtm" -> [2012, 2, 0]
31
32
  # Windows_Server-2012-R2_RTM-
33
+ # "windows-2016" -> [2016, 0, nil]
34
+ # Windows_Server-2016-
32
35
  def image_search
33
36
  search = {
34
37
  "owner-alias" => "amazon",
@@ -75,6 +78,7 @@ module Kitchen
75
78
  # 2012r2sp4 -> [ 2012, 2, 4 ]
76
79
  # 2012sp4 -> [ 2012, 0, 4 ]
77
80
  # 2012rtm -> [ 2012, 0, 0 ]
81
+ # 2016 -> [ 2016, 0, nil ]
78
82
  def windows_version_parts
79
83
  version = self.version
80
84
  if version
@@ -106,29 +110,35 @@ module Kitchen
106
110
 
107
111
  private
108
112
 
109
- def windows_name_filter
113
+ def windows_name_filter # rubocop:disable Metrics/MethodLength
110
114
  major, revision, service_pack = windows_version_parts
111
115
 
112
- case revision
113
- when nil
114
- revision_strings = ["", "R*_"]
115
- when 0
116
- revision_strings = [""]
116
+ if major == 2016
117
+ "Windows_Server-2016-English-Full-Base-*"
117
118
  else
118
- revision_strings = ["R#{revision}_"]
119
- end
119
+ case revision
120
+ when nil
121
+ revision_strings = ["", "R*_"]
122
+ when 0
123
+ revision_strings = [""]
124
+ else
125
+ revision_strings = ["R#{revision}_"]
126
+ end
120
127
 
121
- case service_pack
122
- when nil
123
- revision_strings = revision_strings.flat_map { |r| ["#{r}RTM", "#{r}SP*"] }
124
- when 0
125
- revision_strings = revision_strings.map { |r| "#{r}RTM" }
126
- else
127
- revision_strings = revision_strings.map { |r| "#{r}SP#{service_pack}" }
128
- end
128
+ case service_pack
129
+ when nil
130
+ revision_strings = revision_strings.flat_map { |r| ["#{r}RTM", "#{r}SP*"] }
131
+ when 0
132
+ revision_strings = revision_strings.map { |r| "#{r}RTM" }
133
+ else
134
+ revision_strings = revision_strings.map { |r| "#{r}SP#{service_pack}" }
135
+ end
129
136
 
130
- revision_strings.map do |r|
131
- "Windows_Server-#{major || "*"}-#{r}-English-*-Base-*"
137
+ name_filter = revision_strings.map do |r|
138
+ "Windows_Server-#{major || "*"}-#{r}-English-*-Base-*"
139
+ end
140
+ name_filter << "Windows_Server-*-English-Full-Base-*" if major.nil?
141
+ name_filter
132
142
  end
133
143
  end
134
144
  end
@@ -81,7 +81,9 @@ module Kitchen
81
81
  default_config :interface, nil
82
82
  default_config :http_proxy, ENV["HTTPS_PROXY"] || ENV["HTTP_PROXY"]
83
83
  default_config :retry_limit, 3
84
+ default_config :tenancy, "default"
84
85
  default_config :instance_initiated_shutdown_behavior, nil
86
+ default_config :ssl_verify_peer, true
85
87
 
86
88
  def initialize(*args, &block)
87
89
  super
@@ -194,6 +196,8 @@ module Kitchen
194
196
  # Tagging can fail with a NotFound error even though we waited until the server exists
195
197
  # Waiting can also fail, so we have to also retry on that. If it means we re-tag the
196
198
  # instance, so be it.
199
+ # Tagging an instance is possible before volumes are attached. Tagging the volumes after
200
+ # instance creation is consistent.
197
201
  Retryable.retryable(
198
202
  :tries => 10,
199
203
  :sleep => lambda { |n| [2**n, 30].min },
@@ -204,6 +208,8 @@ module Kitchen
204
208
 
205
209
  state[:server_id] = server.id
206
210
  info("EC2 instance <#{state[:server_id]}> created.")
211
+ wait_until_volumes_ready(server, state)
212
+ tag_volumes(server)
207
213
  wait_until_ready(server, state)
208
214
  end
209
215
 
@@ -296,9 +302,14 @@ module Kitchen
296
302
  end
297
303
 
298
304
  def update_username(state)
299
- # TODO: if the user explicitly specified the transport's default username,
300
- # do NOT overwrite it!
301
- if instance.transport[:username] == instance.transport.class.defaults[:username]
305
+ # BUG: With the following equality condition on username, if the user specifies 'root'
306
+ # as the transport's username then we will overwrite that value with one from the standard
307
+ # platform definitions. This seems difficult to handle here as the default username is
308
+ # provided by the underlying transport classes, and is often non-nil (eg; 'root'), leaving
309
+ # us no way to distinguish a user-set value from the transport's default.
310
+ # See https://github.com/test-kitchen/kitchen-ec2/pull/273
311
+ if actual_platform &&
312
+ instance.transport[:username] == instance.transport.class.defaults[:username]
302
313
  debug("No SSH username specified: using default username #{actual_platform.username} " \
303
314
  " for image #{config[:image_id]}, which we detected as #{actual_platform}.")
304
315
  state[:username] = actual_platform.username
@@ -313,7 +324,8 @@ module Kitchen
313
324
  config[:aws_secret_access_key],
314
325
  config[:aws_session_token],
315
326
  config[:http_proxy],
316
- config[:retry_limit]
327
+ config[:retry_limit],
328
+ config[:ssl_verify_peer]
317
329
  )
318
330
  end
319
331
 
@@ -356,9 +368,11 @@ module Kitchen
356
368
  end
357
369
 
358
370
  def create_spot_request
371
+ request_duration = config[:retryable_tries] * config[:retryable_sleep]
359
372
  request_data = {
360
373
  :spot_price => config[:spot_price].to_s,
361
- :launch_specification => instance_generator.ec2_instance_data
374
+ :launch_specification => instance_generator.ec2_instance_data,
375
+ :valid_until => Time.now + request_duration
362
376
  }
363
377
  if config[:block_duration_minutes]
364
378
  request_data[:block_duration_minutes] = config[:block_duration_minutes]
@@ -378,6 +392,34 @@ module Kitchen
378
392
  end
379
393
  end
380
394
 
395
+ def tag_volumes(server)
396
+ tags = []
397
+ config[:tags].each do |k, v|
398
+ tags << { :key => k, :value => v }
399
+ end
400
+ server.volumes.each do |volume|
401
+ volume.create_tags(:tags => tags)
402
+ end
403
+ end
404
+
405
+ # Compares the requested volume count vs what has actually been set to be
406
+ # attached to the instance. The information requested through
407
+ # ec2.client.described_volumes is updated before the instance volume
408
+ # information.
409
+ def wait_until_volumes_ready(server, state)
410
+ wait_with_destroy(server, state, "volumes to be ready") do |aws_instance|
411
+ described_volume_count = 0
412
+ ready_volume_count = 0
413
+ if aws_instance.exists?
414
+ described_volume_count = ec2.client.describe_volumes(:filters => [
415
+ { :name => "attachment.instance-id", :values => ["#{state[:server_id]}"] }]
416
+ ).volumes.length
417
+ aws_instance.volumes.each { ready_volume_count += 1 }
418
+ end
419
+ (described_volume_count > 0) && (described_volume_count == ready_volume_count)
420
+ end
421
+ end
422
+
381
423
  # Normally we could use `server.wait_until_running` but we actually need
382
424
  # to check more than just the instance state
383
425
  def wait_until_ready(server, state)
@@ -513,10 +555,15 @@ module Kitchen
513
555
  EOH
514
556
  end
515
557
 
558
+ if actual_platform.version =~ /2016/
559
+ logfile_name = 'C:\\ProgramData\\Amazon\\EC2-Windows\\Launch\\Log\\kitchen-ec2.log'
560
+ else
561
+ logfile_name = 'C:\\Program Files\\Amazon\\Ec2ConfigService\\Logs\\kitchen-ec2.log'
562
+ end
516
563
  # Returning the fully constructed PowerShell script to user_data
517
564
  Kitchen::Util.outdent!(<<-EOH)
518
565
  <powershell>
519
- $logfile="C:\\Program Files\\Amazon\\Ec2ConfigService\\Logs\\kitchen-ec2.log"
566
+ $logfile=#{logfile_name}
520
567
  # Allow script execution
521
568
  Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force
522
569
  #PS Remoting and & winrm.cmd basic config
@@ -21,6 +21,6 @@ module Kitchen
21
21
  module Driver
22
22
 
23
23
  # Version string for EC2 Test Kitchen driver
24
- EC2_VERSION = "1.2.0"
24
+ EC2_VERSION = "1.3.0"
25
25
  end
26
26
  end
@@ -21,45 +21,48 @@ require "climate_control"
21
21
 
22
22
  describe Kitchen::Driver::Aws::Client do
23
23
  describe "::get_credentials" do
24
- let(:shared) { instance_double(Aws::SharedCredentials) }
25
- let(:iam) { instance_double(Aws::InstanceProfileCredentials) }
26
-
27
- before do
28
- expect(Aws::SharedCredentials).to \
29
- receive(:new).with(:profile_name => "profile").and_return(shared)
30
- end
31
-
32
24
  # nothing else is set, so we default to this
33
25
  it "loads IAM credentials last" do
34
- expect(shared).to receive(:loadable?).and_return(false)
35
- expect(Aws::InstanceProfileCredentials).to receive(:new).and_return(iam)
36
- expect(Kitchen::Driver::Aws::Client.get_credentials("profile", nil, nil, nil)).to eq(iam)
26
+ iam = instance_double(Aws::InstanceProfileCredentials)
27
+
28
+ allow(Kitchen::Driver::Aws::Client).to receive(:get_shared_creds).and_return(false)
29
+ allow(Aws::InstanceProfileCredentials).to receive(:new).and_return(iam)
30
+
31
+ env_creds(nil, nil) do
32
+ expect(Kitchen::Driver::Aws::Client.get_credentials(nil, nil, nil, nil, nil)).to eq(iam)
33
+ end
37
34
  end
38
35
 
39
- it "loads shared credentials second to last" do
40
- expect(shared).to receive(:loadable?).and_return(true)
41
- expect(Kitchen::Driver::Aws::Client.get_credentials("profile", nil, nil, nil)).to eq(shared)
36
+ it "loads the shared credentials file second to last" do
37
+ shared = instance_double(Aws::SharedCredentials)
38
+
39
+ allow(Aws::SharedCredentials).to \
40
+ receive(:new).with(:profile_name => "profile").and_return(shared)
41
+
42
+ env_creds(nil, nil) do
43
+ expect(Kitchen::Driver::Aws::Client.get_credentials("profile", nil, nil, nil, nil)).to \
44
+ eq(shared)
45
+ end
42
46
  end
43
47
 
44
- it "loads shared credentials third to last" do
45
- expect(shared).to_not receive(:loadable?)
46
- ClimateControl.modify(
47
- "AWS_ACCESS_KEY_ID" => "key1",
48
- "AWS_SECRET_ACCESS_KEY" => "value1",
49
- "AWS_SESSION_TOKEN" => "token1"
50
- ) do
51
- expect(Kitchen::Driver::Aws::Client.get_credentials("profile", nil, nil, nil)).to \
48
+ it "loads credentials from the environment third to last" do
49
+ env_creds("key_id", "secret") do
50
+ expect(Kitchen::Driver::Aws::Client.get_credentials(nil, nil, nil, nil, nil)).to \
52
51
  be_a(Aws::Credentials).and have_attributes(
53
- :access_key_id => "key1",
54
- :secret_access_key => "value1",
55
- :session_token => "token1"
52
+ :access_key_id => "key_id",
53
+ :secret_access_key => "secret"
56
54
  )
57
55
  end
58
56
  end
59
57
 
60
58
  it "loads provided credentials first" do
61
- expect(shared).to_not receive(:loadable?)
62
- expect(Kitchen::Driver::Aws::Client.get_credentials("profile", "key3", "value3", nil)).to \
59
+ expect(Kitchen::Driver::Aws::Client.get_credentials(
60
+ "profile",
61
+ "key3",
62
+ "value3",
63
+ nil,
64
+ "us-west-1"
65
+ )).to \
63
66
  be_a(Aws::Credentials).and have_attributes(
64
67
  :access_key_id => "key3",
65
68
  :secret_access_key => "value3",
@@ -68,8 +71,13 @@ describe Kitchen::Driver::Aws::Client do
68
71
  end
69
72
 
70
73
  it "uses a session token if provided" do
71
- expect(shared).to_not receive(:loadable?)
72
- expect(Kitchen::Driver::Aws::Client.get_credentials("profile", "key3", "value3", "t")).to \
74
+ expect(Kitchen::Driver::Aws::Client.get_credentials(
75
+ "profile",
76
+ "key3",
77
+ "value3",
78
+ "t",
79
+ "us-west-1"
80
+ )).to \
73
81
  be_a(Aws::Credentials).and have_attributes(
74
82
  :access_key_id => "key3",
75
83
  :secret_access_key => "value3",
@@ -78,6 +86,55 @@ describe Kitchen::Driver::Aws::Client do
78
86
  end
79
87
  end
80
88
 
89
+ describe "::get_credentials + STS AssumeRole" do
90
+ let(:shared) { instance_double(Aws::SharedCredentials) }
91
+ let(:iam) { instance_double(Aws::InstanceProfileCredentials) }
92
+ let(:assume_role) { instance_double(Aws::AssumeRoleCredentials) }
93
+ let(:sts_client) { instance_double(Aws::STS::Client) }
94
+
95
+ before do
96
+ expect(Aws::AssumeRoleCredentials).to \
97
+ receive(:new).with(
98
+ :client => sts_client,
99
+ :role_arn => "role_arn",
100
+ :role_session_name => "role_session_name"
101
+ ).and_return(assume_role)
102
+ end
103
+
104
+ # nothing else is set, so we default to this
105
+ it "loads an Instance Profile last" do
106
+ expect(Aws::InstanceProfileCredentials).to \
107
+ receive(:new).and_return(iam)
108
+ expect(Aws::STS::Client).to \
109
+ receive(:new).with(:credentials => iam, :region => "us-west-1").and_return(sts_client)
110
+
111
+ expect(Kitchen::Driver::Aws::Client.get_credentials(
112
+ nil,
113
+ nil,
114
+ nil,
115
+ nil,
116
+ "us-west-1",
117
+ :assume_role_arn => "role_arn", :assume_role_session_name => "role_session_name"
118
+ )).to eq(assume_role)
119
+ end
120
+
121
+ it "loads shared credentials second to last" do
122
+ expect(::Aws::SharedCredentials).to \
123
+ receive(:new).with(:profile_name => "profile").and_return(shared)
124
+ expect(Aws::STS::Client).to \
125
+ receive(:new).with(:credentials => shared, :region => "us-west-1").and_return(sts_client)
126
+
127
+ expect(Kitchen::Driver::Aws::Client.get_credentials(
128
+ "profile",
129
+ nil,
130
+ nil,
131
+ nil,
132
+ "us-west-1",
133
+ :assume_role_arn => "role_arn", :assume_role_session_name => "role_session_name"
134
+ )).to eq(assume_role)
135
+ end
136
+ end
137
+
81
138
  let(:client) { Kitchen::Driver::Aws::Client.new("us-west-1") }
82
139
 
83
140
  describe "#initialize" do
@@ -100,7 +157,8 @@ describe Kitchen::Driver::Aws::Client do
100
157
  "secret_access_key",
101
158
  "session_token",
102
159
  "http_proxy",
103
- 999
160
+ 999,
161
+ false
104
162
  )
105
163
  }
106
164
  let(:creds) { double("creds") }
@@ -111,6 +169,7 @@ describe Kitchen::Driver::Aws::Client do
111
169
  expect(Aws.config[:credentials]).to eq(creds)
112
170
  expect(Aws.config[:http_proxy]).to eq("http_proxy")
113
171
  expect(Aws.config[:retry_limit]).to eq(999)
172
+ expect(Aws.config[:ssl_verify_peer]).to eq(false)
114
173
  end
115
174
  end
116
175
  end
@@ -123,4 +182,12 @@ describe Kitchen::Driver::Aws::Client do
123
182
  expect(client.resource).to be_a(Aws::EC2::Resource)
124
183
  end
125
184
 
185
+ def env_creds(key_id, secret, &block)
186
+ ClimateControl.modify(
187
+ "AWS_ACCESS_KEY_ID" => key_id,
188
+ "AWS_SECRET_ACCESS_KEY" => secret
189
+ ) do
190
+ block.call
191
+ end
192
+ end
126
193
  end
@@ -221,7 +221,8 @@ describe "Default images for various platforms" do
221
221
  Windows_Server-*-RTM-English-*-Base-*
222
222
  Windows_Server-*-SP*-English-*-Base-*
223
223
  Windows_Server-*-R*_RTM-English-*-Base-*
224
- Windows_Server-*-R*_SP*-English-*-Base-*] }
224
+ Windows_Server-*-R*_SP*-English-*-Base-*
225
+ Windows_Server-*-English-Full-Base-*] }
225
226
  ],
226
227
  "windows-2008" => [
227
228
  { :name => "owner-alias", :values => %w[amazon] },
@@ -267,7 +268,8 @@ describe "Default images for various platforms" do
267
268
  Windows_Server-*-RTM-English-*-Base-*
268
269
  Windows_Server-*-SP*-English-*-Base-*
269
270
  Windows_Server-*-R*_RTM-English-*-Base-*
270
- Windows_Server-*-R*_SP*-English-*-Base-*] },
271
+ Windows_Server-*-R*_SP*-English-*-Base-*
272
+ Windows_Server-*-English-Full-Base-*] },
271
273
  { :name => "architecture", :values => %w[x86_64] }
272
274
  ],
273
275
  "windows-2012r2-x86_64" => [
@@ -283,7 +285,8 @@ describe "Default images for various platforms" do
283
285
  Windows_Server-*-RTM-English-*-Base-*
284
286
  Windows_Server-*-SP*-English-*-Base-*
285
287
  Windows_Server-*-R*_RTM-English-*-Base-*
286
- Windows_Server-*-R*_SP*-English-*-Base-*] }
288
+ Windows_Server-*-R*_SP*-English-*-Base-*
289
+ Windows_Server-*-English-Full-Base-*] }
287
290
  ],
288
291
  "windows-server-2012r2-x86_64" => [
289
292
  { :name => "owner-alias", :values => %w[amazon] },
@@ -291,6 +294,11 @@ describe "Default images for various platforms" do
291
294
  Windows_Server-2012-R2_RTM-English-*-Base-*
292
295
  Windows_Server-2012-R2_SP*-English-*-Base-*] },
293
296
  { :name => "architecture", :values => %w[x86_64] }
297
+ ],
298
+ "windows-2016" => [
299
+ { :name => "owner-alias", :values => %w[amazon] },
300
+ { :name => "name", :values => %w[
301
+ Windows_Server-2016-English-Full-Base-*] }
294
302
  ]
295
303
  }
296
304
 
@@ -20,6 +20,7 @@ require "kitchen/driver/aws/instance_generator"
20
20
  require "kitchen/driver/aws/client"
21
21
  require "tempfile"
22
22
  require "base64"
23
+ require "aws-sdk"
23
24
 
24
25
  describe Kitchen::Driver::Aws::InstanceGenerator do
25
26
 
@@ -59,6 +60,28 @@ describe Kitchen::Driver::Aws::InstanceGenerator do
59
60
  end
60
61
 
61
62
  describe "#ec2_instance_data" do
63
+ ec2_stub = Aws::EC2::Client.new(:stub_responses => true)
64
+
65
+ ec2_stub.stub_responses(
66
+ :describe_subnets,
67
+ :subnets => [
68
+ {
69
+ :subnet_id => "s-123",
70
+ :tags => [{ :key => "foo", :value => "bar" }]
71
+ }
72
+ ]
73
+ )
74
+
75
+ ec2_stub.stub_responses(
76
+ :describe_security_groups,
77
+ :security_groups => [
78
+ {
79
+ :group_id => "sg-123",
80
+ :tags => [{ :key => "foo", :value => "bar" }]
81
+ }
82
+ ]
83
+ )
84
+
62
85
  it "returns empty on nil" do
63
86
  expect(generator.ec2_instance_data).to eq(
64
87
  :instance_type => nil,
@@ -117,6 +140,69 @@ describe Kitchen::Driver::Aws::InstanceGenerator do
117
140
  end
118
141
  end
119
142
 
143
+ context "when provided subnet tag instead of id" do
144
+ let(:config) do
145
+ {
146
+ :instance_type => "micro",
147
+ :ebs_optimized => true,
148
+ :image_id => "ami-123",
149
+ :aws_ssh_key_id => "key",
150
+ :subnet_id => nil,
151
+ :region => "us-west-2",
152
+ :subnet_filter =>
153
+ {
154
+ :tag => "foo",
155
+ :value => "bar"
156
+ }
157
+ }
158
+ end
159
+
160
+ it "generates id from the provided tag" do
161
+ allow(::Aws::EC2::Client).to receive(:new).and_return(ec2_stub)
162
+ expect(ec2_stub).to receive(:describe_subnets).with(
163
+ :filters => [
164
+ {
165
+ :name => "tag:foo",
166
+ :values => ["bar"]
167
+ }
168
+ ]
169
+ ).and_return(ec2_stub.describe_subnets)
170
+ expect(generator.ec2_instance_data[:subnet_id]).to eq("s-123")
171
+ end
172
+ end
173
+
174
+ context "when provided security_group tag instead of id" do
175
+ let(:config) do
176
+ {
177
+ :instance_type => "micro",
178
+ :ebs_optimized => true,
179
+ :image_id => "ami-123",
180
+ :aws_ssh_key_id => "key",
181
+ :subnet_id => "s-123",
182
+ :security_group_ids => nil,
183
+ :region => "us-west-2",
184
+ :security_group_filter =>
185
+ {
186
+ :tag => "foo",
187
+ :value => "bar"
188
+ }
189
+ }
190
+ end
191
+
192
+ it "generates id from the provided tag" do
193
+ allow(::Aws::EC2::Client).to receive(:new).and_return(ec2_stub)
194
+ expect(ec2_stub).to receive(:describe_security_groups).with(
195
+ :filters => [
196
+ {
197
+ :name => "tag:foo",
198
+ :values => ["bar"]
199
+ }
200
+ ]
201
+ ).and_return(ec2_stub.describe_security_groups)
202
+ expect(generator.ec2_instance_data[:security_group_ids]).to eq(["sg-123"])
203
+ end
204
+ end
205
+
120
206
  context "when passed an empty block_device_mappings" do
121
207
  let(:config) do
122
208
  {
@@ -200,6 +286,128 @@ describe Kitchen::Driver::Aws::InstanceGenerator do
200
286
  end
201
287
  end
202
288
 
289
+ context "when availability_zone and tenancy are provided" do
290
+ let(:config) do
291
+ {
292
+ :region => "eu-east-1",
293
+ :availability_zone => "c",
294
+ :tenancy => "dedicated"
295
+ }
296
+ end
297
+ it "adds the region to it in the instance data" do
298
+ expect(generator.ec2_instance_data).to eq(
299
+ :instance_type => nil,
300
+ :ebs_optimized => nil,
301
+ :image_id => nil,
302
+ :key_name => nil,
303
+ :subnet_id => nil,
304
+ :private_ip_address => nil,
305
+ :placement => { :availability_zone => "eu-east-1c",
306
+ :tenancy => "dedicated" }
307
+ )
308
+ end
309
+ end
310
+
311
+ context "when tenancy is provided but availability_zone isn't" do
312
+ let(:config) do
313
+ {
314
+ :region => "eu-east-1",
315
+ :tenancy => "default"
316
+ }
317
+ end
318
+ it "is not added to the instance data" do
319
+ expect(generator.ec2_instance_data).to eq(
320
+ :instance_type => nil,
321
+ :ebs_optimized => nil,
322
+ :image_id => nil,
323
+ :key_name => nil,
324
+ :subnet_id => nil,
325
+ :private_ip_address => nil,
326
+ :placement => { :tenancy => "default" }
327
+ )
328
+ end
329
+ end
330
+
331
+ context "when tenancy is not supported" do
332
+ let(:config) do
333
+ {
334
+ :region => "eu-east-1",
335
+ :tenancy => "ephemeral"
336
+ }
337
+ end
338
+ it "is not added to the instance data" do
339
+ expect(generator.ec2_instance_data).to eq(
340
+ :instance_type => nil,
341
+ :ebs_optimized => nil,
342
+ :image_id => nil,
343
+ :key_name => nil,
344
+ :subnet_id => nil,
345
+ :private_ip_address => nil
346
+ )
347
+ end
348
+ end
349
+
350
+ context "when availability_zone and tenancy are provided" do
351
+ let(:config) do
352
+ {
353
+ :region => "eu-east-1",
354
+ :availability_zone => "c",
355
+ :tenancy => "dedicated"
356
+ }
357
+ end
358
+ it "adds the region to it in the instance data" do
359
+ expect(generator.ec2_instance_data).to eq(
360
+ :instance_type => nil,
361
+ :ebs_optimized => nil,
362
+ :image_id => nil,
363
+ :key_name => nil,
364
+ :subnet_id => nil,
365
+ :private_ip_address => nil,
366
+ :placement => { :availability_zone => "eu-east-1c",
367
+ :tenancy => "dedicated" }
368
+ )
369
+ end
370
+ end
371
+
372
+ context "when tenancy is provided but availability_zone isn't" do
373
+ let(:config) do
374
+ {
375
+ :region => "eu-east-1",
376
+ :tenancy => "default"
377
+ }
378
+ end
379
+ it "is not added to the instance data" do
380
+ expect(generator.ec2_instance_data).to eq(
381
+ :instance_type => nil,
382
+ :ebs_optimized => nil,
383
+ :image_id => nil,
384
+ :key_name => nil,
385
+ :subnet_id => nil,
386
+ :private_ip_address => nil,
387
+ :placement => { :tenancy => "default" }
388
+ )
389
+ end
390
+ end
391
+
392
+ context "when tenancy is not supported" do
393
+ let(:config) do
394
+ {
395
+ :region => "eu-east-1",
396
+ :tenancy => "ephemeral"
397
+ }
398
+ end
399
+ it "is not added to the instance data" do
400
+ expect(generator.ec2_instance_data).to eq(
401
+ :instance_type => nil,
402
+ :ebs_optimized => nil,
403
+ :image_id => nil,
404
+ :key_name => nil,
405
+ :subnet_id => nil,
406
+ :private_ip_address => nil
407
+ )
408
+ end
409
+ end
410
+
203
411
  context "when subnet_id is provided" do
204
412
  let(:config) do
205
413
  {
@@ -70,6 +70,35 @@ describe Kitchen::Driver::Ec2 do
70
70
  Kitchen::Driver::EC2_VERSION)
71
71
  end
72
72
 
73
+ describe "default_config" do
74
+ context "Windows" do
75
+ let(:resource) { instance_double(::Aws::EC2::Resource, :image => image) }
76
+ before do
77
+ allow(driver).to receive(:windows_os?).and_return(true)
78
+ allow(client).to receive(:resource).and_return(resource)
79
+ allow(instance).to receive(:name).and_return("instance_name")
80
+ end
81
+ context "Windows 2016" do
82
+ let(:image) {
83
+ FakeImage.new(:name => "Windows_Server-2016-English-Full-Base-2017.01.11")
84
+ }
85
+ it "sets :user_data to something" do
86
+ expect(driver[:user_data]).to include
87
+ '$logfile=C:\\ProgramData\\Amazon\\EC2-Windows\\Launch\\Log\\kitchen-ec2.log'
88
+ end
89
+ end
90
+ context "Windows 2012R2" do
91
+ let(:image) {
92
+ FakeImage.new(:name => "Windows_Server-2012-R2_RTM-English-64Bit-Base-2017.01.11")
93
+ }
94
+ it "sets :user_data to something" do
95
+ expect(driver[:user_data]).to include
96
+ '$logfile=C:\\Program Files\\Amazon\\Ec2ConfigService\\Logs\\kitchen-ec2.log'
97
+ end
98
+ end
99
+ end
100
+ end
101
+
73
102
  describe "#hostname" do
74
103
  let(:public_dns_name) { nil }
75
104
  let(:private_dns_name) { nil }
@@ -212,12 +241,16 @@ describe Kitchen::Driver::Ec2 do
212
241
 
213
242
  before do
214
243
  expect(driver).to receive(:instance).at_least(:once).and_return(instance)
244
+ allow(Time).to receive(:now).and_return(Time.now)
215
245
  end
216
246
 
217
247
  it "submits the server request" do
218
248
  expect(generator).to receive(:ec2_instance_data).and_return({})
219
249
  expect(actual_client).to receive(:request_spot_instances).with(
220
- :spot_price => "", :launch_specification => {}, :block_duration_minutes => 60
250
+ :spot_price => "",
251
+ :launch_specification => {},
252
+ :valid_until => Time.now + (config[:retryable_tries] * config[:retryable_sleep]),
253
+ :block_duration_minutes => 60
221
254
  ).and_return(response)
222
255
  expect(actual_client).to receive(:wait_until)
223
256
  expect(client).to receive(:get_instance_from_spot_request).with("id")
@@ -243,6 +276,23 @@ describe Kitchen::Driver::Ec2 do
243
276
  end
244
277
  end
245
278
 
279
+ describe "#tag_volumes" do
280
+ let(:volume) { double("aws volume resource") }
281
+ before do
282
+ allow(server).to receive(:volumes).and_return([volume])
283
+ end
284
+ it "tags the instance volumes" do
285
+ config[:tags] = { :key1 => :value1, :key2 => :value2 }
286
+ expect(volume).to receive(:create_tags).with(
287
+ :tags => [
288
+ { :key => :key1, :value => :value1 },
289
+ { :key => :key2, :value => :value2 }
290
+ ]
291
+ )
292
+ driver.tag_volumes(server)
293
+ end
294
+ end
295
+
246
296
  describe "#wait_until_ready" do
247
297
  let(:hostname) { "0.0.0.0" }
248
298
  let(:msg) { "to become ready" }
@@ -286,6 +336,43 @@ describe Kitchen::Driver::Ec2 do
286
336
  end
287
337
  end
288
338
 
339
+ describe "#wait_until_volumes_ready" do
340
+ let(:aws_instance) { double("aws instance") }
341
+ let(:msg) { "volumes to be ready" }
342
+ let(:volume) { double("aws volume resource") }
343
+
344
+ before do
345
+ expect(driver).to receive(:wait_with_destroy).with(server, state, msg).and_yield(aws_instance)
346
+ end
347
+ it "first checks instance existence" do
348
+ expect(aws_instance).to receive(:exists?).and_return(false)
349
+ expect(driver.wait_until_volumes_ready(server, state)).to eq(false)
350
+ end
351
+ it "second, it checks for prescence of described volumes" do
352
+ expect(aws_instance).to receive(:exists?).and_return(true)
353
+ expect(actual_client).to receive_message_chain(:describe_volumes, :volumes, :length
354
+ ).and_return(0)
355
+ expect(aws_instance).to receive(:volumes).and_return([])
356
+ expect(driver.wait_until_volumes_ready(server, state)).to eq(false)
357
+ end
358
+ it "third, it compares the described volumes and instance volumes" do
359
+ expect(aws_instance).to receive(:exists?).and_return(true)
360
+ expect(actual_client).to receive_message_chain(:describe_volumes, :volumes, :length
361
+ ).and_return(2)
362
+ expect(aws_instance).to receive(:volumes).and_return([volume])
363
+ expect(driver.wait_until_volumes_ready(server, state)).to eq(false)
364
+ end
365
+ context "when it exists, and both client and instance agree on volumes" do
366
+ it "returns true" do
367
+ expect(aws_instance).to receive(:exists?).and_return(true)
368
+ expect(actual_client).to receive_message_chain(:describe_volumes, :volumes, :length
369
+ ).and_return(1)
370
+ expect(aws_instance).to receive(:volumes).and_return([volume])
371
+ expect(driver.wait_until_volumes_ready(server, state)).to eq(true)
372
+ end
373
+ end
374
+ end
375
+
289
376
  describe "#fetch_windows_admin_password" do
290
377
  let(:msg) { "to fetch windows admin password" }
291
378
  let(:aws_instance) { double("aws instance") }
@@ -362,6 +449,8 @@ describe Kitchen::Driver::Ec2 do
362
449
  expect(server).to receive(:wait_until_exists)
363
450
  expect(driver).to receive(:update_username)
364
451
  expect(driver).to receive(:tag_server).with(server)
452
+ expect(driver).to receive(:tag_volumes).with(server)
453
+ expect(driver).to receive(:wait_until_volumes_ready).with(server, state)
365
454
  expect(driver).to receive(:wait_until_ready).with(server, state)
366
455
  expect(transport).to receive_message_chain("connection.wait_until_ready")
367
456
  expect(driver).to receive(:create_ec2_json).with(state)
@@ -399,6 +488,18 @@ describe Kitchen::Driver::Ec2 do
399
488
  include_examples "common create"
400
489
  end
401
490
 
491
+ context "instance is not a standard platform" do
492
+ let(:state) { {} }
493
+ before do
494
+ expect(driver).to receive(:actual_platform).and_return(nil)
495
+ end
496
+
497
+ it "doesn't set the state username" do
498
+ driver.update_username(state)
499
+ expect(state).to eq({})
500
+ end
501
+ end
502
+
402
503
  end
403
504
 
404
505
  describe "#destroy" do
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: 1.2.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Fletcher Nichol
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-12 00:00:00.000000000 Z
11
+ date: 2017-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-kitchen
@@ -253,7 +253,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
253
253
  version: '0'
254
254
  requirements: []
255
255
  rubyforge_project:
256
- rubygems_version: 2.5.1
256
+ rubygems_version: 2.6.10
257
257
  signing_key:
258
258
  specification_version: 4
259
259
  summary: A Test Kitchen Driver for Amazon EC2
@@ -263,4 +263,3 @@ test_files:
263
263
  - spec/kitchen/driver/ec2/instance_generator_spec.rb
264
264
  - spec/kitchen/driver/ec2_spec.rb
265
265
  - spec/spec_helper.rb
266
- has_rdoc: