kitchen-ec2 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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: