kitchen-ec2 2.0.0 → 2.1.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: d121c852a28622ac97b1d1ecd53d8925b8d69807
4
- data.tar.gz: e3099c025db1fefaadc834cc3d0af54b18a1da7a
3
+ metadata.gz: 959a1ab9d5bc68f378d275e54b2582173dd1b639
4
+ data.tar.gz: e1860a9b7dd6edc37eb04af7ef451d0b0e05575b
5
5
  SHA512:
6
- metadata.gz: 0a761d862424e0fa29e2d5f1304ef5a65d66f6da5e5a38fa6ac437cfff0bd1619bcb8ece93066fd8c16036c62875028f9bdbd8b2572f6f825bde7d9c410aea59
7
- data.tar.gz: 3716edf1589f8952d7aa7460f5e1056d0e5c78c1a0dfb6096ae1db5bf527ec31c33c0e3aada585164c0c07b6e9400c26c347b9c894604fad3b2a74226397799c
6
+ metadata.gz: 3d8825aab45d7c154f9e4f718fad006ff0e3b469f51da94ba531a7de42e99c05595d41f0195ac4ac05998d6e2e26f30f89f52da93401e40b689b2b21c8d525ed
7
+ data.tar.gz: fb078de3ae30c8eac13bf7b24b7db968c5083f281aeae3f425230d941aace2f2405e6a7eedfb3c3fed34a7d8cdce43a078ff360df0c2e921fccbe07e72415cce
data/.travis.yml CHANGED
@@ -5,7 +5,18 @@ branches:
5
5
  only:
6
6
  - master
7
7
  rvm:
8
- - 2.2.8
9
- - 2.3.5
10
- - 2.4.2
11
- - ruby-head
8
+ - 2.3.6
9
+ - 2.4.3
10
+
11
+ # https://github.com/travis-ci/travis-ci/issues/8978
12
+ matrix:
13
+ include:
14
+ - rvm: 2.5.0
15
+ before_install:
16
+ - gem update --system
17
+ - rvm: ruby-head
18
+ before_install:
19
+ - gem update --system
20
+
21
+ allow_failures:
22
+ - rvm: ruby-head
data/CHANGELOG.md CHANGED
@@ -1,11 +1,19 @@
1
1
  # Change Log
2
2
 
3
+ ## [v2.1.0](https://github.com/test-kitchen/kitchen-ec2/tree/v2.1.0) (2018-01-27)
4
+ [Full Changelog](https://github.com/test-kitchen/kitchen-ec2/compare/v2.0.0...v2.1.0)
5
+
6
+ **Merged pull requests:**
7
+
8
+ - Only create Ohai hint when provisioner is `/chef/` [\#366](https://github.com/test-kitchen/kitchen-ec2/pull/366) ([cheeseplus](https://github.com/cheeseplus))
9
+ - Automatically create a security group and key pair if needed. [\#362](https://github.com/test-kitchen/kitchen-ec2/pull/362) ([coderanger](https://github.com/coderanger))
10
+
3
11
  ## [v2.0.0](https://github.com/test-kitchen/kitchen-ec2/tree/v2.0.0) (2017-12-08)
4
12
  [Full Changelog](https://github.com/test-kitchen/kitchen-ec2/compare/v1.4.0...v2.0.0)
5
13
 
6
14
  **Improvements**
7
15
 
8
- - Clean up original Authentication; Rely on SDK for Chain. [\#353](https://github.com/test-kitchen/kitchen-ec2/pull/320) ([rhyas](https://github.com/rhyas))
16
+ - Clean up original Authentication; Rely on SDK for Chain. [\#353](https://github.com/test-kitchen/kitchen-ec2/pull/353) ([rhyas](https://github.com/rhyas))
9
17
  - Use quadratic backoff when encountering RequestLimit errors [\#320](https://github.com/test-kitchen/kitchen-ec2/pull/320) ([kamaradclimber](https://github.com/kamaradclimber))
10
18
 
11
19
  ## [v1.4.0](https://github.com/test-kitchen/kitchen-ec2/tree/v1.4.0) (2017-11-29)
data/README.md CHANGED
@@ -9,39 +9,21 @@ A [Test Kitchen][kitchenci] Driver for Amazon EC2.
9
9
  This driver uses the [aws sdk gem][aws_sdk_gem] to provision and destroy EC2
10
10
  instances. Use Amazon's cloud for your infrastructure testing!
11
11
 
12
- ## Initial Setup
13
-
14
- To get started, you need to install the software and set up your credentials and SSH key. Some of these steps you have probably already done, but we include them here for completeness.
15
-
16
- 1. Install the latest test-kitchen or ChefDK and put it in your path.
17
- 2. From this repository, type `bundle install; bundle exec rake install` to install the latest version of the driver.
18
- 3. Install the [AWS command line tools](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html).
19
- 4. Run `aws configure` to place your AWS credentials on the drive at ~/.aws/credentials.
20
- 5. Create your AWS SSH key. We recommend naming it with your username, but you can use any name:
21
-
22
- ```
23
- aws ec2 create-key-pair --key-name $USER | ruby -e "require 'json'; puts JSON.parse(STDIN.read)['KeyMaterial']" > ~/.ssh/$USER
24
- ```
25
- 6. `export AWS_SSH_KEY_ID=<your key name>`
26
-
27
12
  ## Quick Start
28
13
 
29
- Once
30
- that is done, create your kitchen file in your cookbook directory (or an empty
31
- directory if you just want to get a feel for it):
32
-
33
- 1. `kitchen init -D kitchen-ec2`
34
- 2. Edit `.kitchen.yml` and add the aws_ssh_key_id to driver and a transport with
35
- an ssh_key:
14
+ 1. Install [ChefDK](https://downloads.chef.io/chefdk). If testing things other
15
+ than Chef cookbooks, please consult your driver's documentation for information
16
+ on what to install.
17
+ 2. Install the [AWS command line tools](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-set-up.html).
18
+ 3. Run `aws configure`. This will set up your AWS credentials for both the AWS
19
+ CLI tools and kitchen-ec2.
20
+ 4. Add or exit the `driver` section of your `.kitchen.yml`:
36
21
 
37
22
  ```yaml
38
- transport:
39
- ssh_key: ~/.ssh/your_private_key_file
23
+ driver:
24
+ name: ec2
40
25
  ```
41
- 3. While you are in there, modify `centos-7.1` to `centos-7`.
42
- 3. `kitchen test`
43
-
44
- It's that easy! This will set up and run centos and ubuntu flavored instances.
26
+ 5. Run `kitchen test`.
45
27
 
46
28
  ## Requirements
47
29
 
@@ -54,8 +36,51 @@ By automatically applying reasonable defaults wherever possible, kitchen-ec2 doe
54
36
 
55
37
  ### Specifying the Image
56
38
 
57
- There are three ways to specify the image you use for the instance: `image_id`,
58
- `image_search` and `platform.name`
39
+ There are three ways to specify the image you use for the instance: the `platform`
40
+ name, `image_id`, and `image_search`.
41
+
42
+ #### `platform` Name
43
+
44
+ The third way to specify the image is by leaving `image_id` and `image_search`
45
+ blank, and specifying a standard platform name.
46
+
47
+ ```yaml
48
+ platforms:
49
+ - name: ubuntu-14.04
50
+ ```
51
+
52
+ If you use the platform name `ubuntu`, `windows`, `rhel`, `debian`, `centos`, `freebsd` or `fedora`, kitchen-ec2 will search for the latest matching official image of
53
+ the given OS in your region. You may leave versions off, specify partial versions,
54
+ and you may specify architecture to distinguish 32- and 64-bit. Some examples:
55
+
56
+ ```yaml
57
+ platforms:
58
+ # The latest stable minor+patch release of rhel 6
59
+ - name: rhel-6
60
+ # The latest patch release of CentOS 6.3
61
+ - name: centos-6.3
62
+ # The latest patch release of Amazon Linux 2017.03
63
+ - name: amazon-2017.03
64
+ # 32-bit version of latest major+minor+patch release of Ubuntu
65
+ - name: ubuntu-i386
66
+ # 32-bit version of Debian 6
67
+ - name: debian-6-i386
68
+ # Latest 32-bit stable minor release of freebsd 10
69
+ - name: freebsd-10-i386
70
+ # The latest stable major+minor+patch release of Fedora
71
+ - name: fedora
72
+ # The most recent service-pack for Windows 2012 (not R2)
73
+ - name: windows-2012
74
+ # The most recent service-pack for Windows 2012R2
75
+ - name: windows-2012r2
76
+ # Windows 2008 RTM (not R2, no service pack)
77
+ - name: windows-2008rtm
78
+ # Windows 2008R2 SP1
79
+ - name: windows-2008r2sp1
80
+ ```
81
+
82
+ We always pick the highest released stable version that matches your regex, and
83
+ follow the other `image_search` rules for preference.
59
84
 
60
85
  #### `image_id`
61
86
 
@@ -104,121 +129,49 @@ Some examples are:
104
129
 
105
130
  It is safest to use the same naming convention as used by the public images published by the OS vendors on the AWS marketplace.
106
131
 
107
- #### `platform.name`
108
-
109
- The third way to specify the image is by leaving `image_id` and `image_search`
110
- blank, and specifying a standard platform name.
111
-
112
- ```yaml
113
- platforms:
114
- - name: ubuntu-14.04
115
- ```
116
-
117
- If you use the platform name `ubuntu`, `windows`, `rhel`, `debian`, `centos`, `freebsd` or `fedora`, kitchen-ec2 will search for the latest matching official image of
118
- the given OS in your region. You may leave versions off, specify partial versions,
119
- and you may specify architecture to distinguish 32- and 64-bit. Some examples:
120
-
121
- ```yaml
122
- platforms:
123
- # The latest stable minor+patch release of rhel 6
124
- - name: rhel-6
125
- # The latest patch release of CentOS 6.3
126
- - name: centos-6.3
127
- # The latest patch release of Amazon Linux 2017.03
128
- - name: amazon-2017.03
129
- # 32-bit version of latest major+minor+patch release of Ubuntu
130
- - name: ubuntu-i386
131
- # 32-bit version of Debian 6
132
- - name: debian-6-i386
133
- # Latest 32-bit stable minor release of freebsd 10
134
- - name: freebsd-10-i386
135
- # The latest stable major+minor+patch release of Fedora
136
- - name: fedora
137
- # The most recent service-pack for Windows 2012 (not R2)
138
- - name: windows-2012
139
- # The most recent service-pack for Windows 2012R2
140
- - name: windows-2012r2
141
- # Windows 2008 RTM (not R2, no service pack)
142
- - name: windows-2008rtm
143
- # Windows 2008R2 SP1
144
- - name: windows-2008r2sp1
145
- ```
146
-
147
- We always pick the highest released stable version that matches your regex, and
148
- follow the other `image_search` rules for preference.
149
-
150
132
  ### AWS Authentication
151
133
 
152
134
  In order to connect to AWS, you must specify AWS credentials. We rely on the SDK
153
- to find credentials in the standard way, documented here:
135
+ to find credentials in the standard way, documented here:
154
136
  https://github.com/aws/aws-sdk-ruby/#configuration
155
137
 
156
138
  The SDK Chain will search environment variables, then config files, then IAM role
157
- data from the instance profile, in that order. In the case config files being
158
- present, the 'default' profile will be used unless `shared_credentials_profile`
159
- is defined to point to another profile.
139
+ data from the instance profile, in that order. In the case config files being
140
+ present, the 'default' profile will be used unless `shared_credentials_profile`
141
+ is defined to point to another profile.
160
142
 
161
143
  Because the Test Kitchen test should be checked into source control and ran
162
- through CI we no longer recommend storing the AWS credentials in the
144
+ through CI we no longer support storing the AWS credentials in the
163
145
  `.kitchen.yml` file.
164
146
 
165
147
  ### Instance Login Configuration
166
148
 
167
149
  The instances you create use credentials you specify which are *separate* from
168
150
  the AWS credentials. Generally, SSH and WinRM use an AWS key pair which you
169
- specify. You probably set this up in the Initial Setup.
170
-
171
- #### `aws_ssh_key_id`
172
-
173
- The ID of the AWS key pair you want to use.
174
-
175
- The default will be read from the `AWS_SSH_KEY_ID` environment variable if set,
176
- or `nil` otherwise.
151
+ specify.
177
152
 
178
- If `aws_ssh_key_id` is specified, it must be one of the KeyName values shown by the AWS CLI: `aws ec2 describe-key-pairs`.
179
- Otherwise, if not specified, you must either have a user pre-provisioned on the AMI, or provision the user using `user_data`.
180
-
181
- #### `transport.ssh_key`
182
-
183
- The private key file for the AWS key pair you want to use.
184
-
185
- #### `transport.username`
186
-
187
- This is not strictly a `driver` thing, but the username is a crucial component
188
- of logging in to an instance. Different AMIs tend to provide different usernames.
189
-
190
- If you use an official AMI (or create an image with the platform name in the
191
- image name), we will use the default username for official AMIs for that platform.
192
-
193
- #### `ebs_optimized`
153
+ #### SSH
194
154
 
195
- Option to launch EC2 instance with optimized EBS volume. See
196
- [Amazon EC2 Instance Types](http://aws.amazon.com/ec2/instance-types/) to find
197
- out more about instance types that can be launched as EBS-optimized instances.
155
+ The `aws_ssh_key_id` value is the name of the AWS key pair you want to use. The default will be read from the `AWS_SSH_KEY_ID` environment variable if set. If a key ID is not specified, a temporary key will be created for you.
198
156
 
199
- The default is `false`.
157
+ To see a list of existing key pair IDs in a region, run `aws ec2 describe-key-pairs --region us-east-1`.
200
158
 
201
- #### Password
159
+ When using an existing key, ensure that the private key is configured in your
160
+ Test Kitchen `transport`, either directly or made available via `ssh-agent`:
202
161
 
203
- For Windows instances the generated Administrator password is fetched
204
- automatically from Amazon EC2 with the same private key as we use for
205
- SSH logins to Linux.
162
+ ```yaml
163
+ transport:
164
+ ssh_key: ~/.ssh/mykey.pem
165
+ ```
206
166
 
207
- ### Windows Configuration
167
+ For standard platforms we automatically provide the SSH username, but when
168
+ specifying your own AMI you may need to configure that as well.
208
169
 
209
- If you specify a platform name starting with `windows`, Test Kitchen will pull a
210
- default AMI out of `amis.json` if one is not specified.
170
+ #### WinRM
211
171
 
212
- The default user_data will add any `username` with its associated `password`
213
- from the transport options to the Aministrator group. If no `username` is
214
- specified then the default `administrator` is available.
172
+ For Windows instances the generated Administrator password is fetched automatically from Amazon EC2 with the same private key as we use for SSH.
215
173
 
216
- AWS automatically generates an `administrator` password in the default
217
- Windows AMIs. Test Kitchen fetches this and stores it in the
218
- `.kitchen/#{platform}.json` file. If you need to `kitchen login` to the instance
219
- and you have not specified your own `username` and `password` you can use
220
- the `administrator` user and the password from this file. Unfortunately
221
- we cannot auto-fill the RDP password at this point.
174
+ Unfortunately the RDP file format does not allow including login credentials, so `kitchen login` with WinRM cannot automatically log in for you.
222
175
 
223
176
  ### Other Configuration
224
177
 
@@ -230,7 +183,7 @@ the letter designation - will attach this to the region used.
230
183
  If not specified, your instances will be placed in an AZ of AWS's choice in your
231
184
  region.
232
185
 
233
- #### <a name="config-instance_type"></a> `instance_type`
186
+ #### `instance_type`
234
187
 
235
188
  The EC2 [instance type][instance_docs] (also known as size) to use.
236
189
 
@@ -240,9 +193,8 @@ or `paravirtual`. (`paravirtual` images are incompatible with `t2.micro`.)
240
193
  #### `security_group_ids`
241
194
 
242
195
  An Array of EC2 [security groups][group_docs] which will be applied to the
243
- instance.
244
-
245
- The default is `["default"]`.
196
+ instance. If no security group is specified, a temporary group will be created
197
+ automatically which allows SSH and WinRM.
246
198
 
247
199
  #### `security_group_filter`
248
200
 
@@ -363,14 +315,6 @@ The default is `ENV["HTTPS_PROXY"] || ENV["HTTP_PROXY"]`. If you have these env
363
315
 
364
316
  If you need to turn off ssl certificate verification for HTTP calls made to AWS, set `ssl_verify_peer: false`.
365
317
 
366
- #### `vpc_mode`
367
-
368
- Can be used to place ec2 instance into vpc. Requires `vpc_id` and `subnet_id` to be set.
369
-
370
- #### `vpc_id`
371
-
372
- Needs `vpc_mode` to be set to true. Represents the ID of the vpc in which the instance should be placed.
373
-
374
318
  ### Disk Configuration
375
319
 
376
320
  #### <a name="config-block_device_mappings"></a> `block_device_mappings`
@@ -527,10 +471,6 @@ example:
527
471
  4. Push to the branch (`git push origin my-new-feature`)
528
472
  5. Create new Pull Request
529
473
 
530
- ## <a name="authors"></a> Authors
531
-
532
- Created and maintained by [Fletcher Nichol][author] (<fnichol@nichol.ca>)
533
-
534
474
  ## <a name="license"></a> License
535
475
 
536
476
  Apache 2.0 (see [LICENSE][license])
@@ -35,9 +35,6 @@ module Kitchen
35
35
  def initialize( # rubocop:disable Metrics/ParameterLists
36
36
  region,
37
37
  profile_name = "default",
38
- access_key_id = nil,
39
- secret_access_key = nil,
40
- session_token = nil,
41
38
  http_proxy = nil,
42
39
  retry_limit = nil,
43
40
  ssl_verify_peer = true
@@ -33,6 +33,9 @@ require_relative "aws/standard_platform/ubuntu"
33
33
  require_relative "aws/standard_platform/windows"
34
34
  require "aws-sdk-core/waiters/errors"
35
35
  require "retryable"
36
+ require "time"
37
+ require "etc"
38
+ require "socket"
36
39
 
37
40
  Aws.eager_autoload!
38
41
 
@@ -85,6 +88,7 @@ module Kitchen
85
88
  default_config :tenancy, "default"
86
89
  default_config :instance_initiated_shutdown_behavior, nil
87
90
  default_config :ssl_verify_peer, true
91
+ default_config :skip_cost_warning, false
88
92
 
89
93
  def initialize(*args, &block)
90
94
  super
@@ -172,13 +176,25 @@ module Kitchen
172
176
  return if state[:server_id]
173
177
  update_username(state)
174
178
 
175
- info(Kitchen::Util.outdent!(<<-END))
179
+ info(Kitchen::Util.outdent!(<<-END)) unless config[:skip_cost_warning]
176
180
  If you are not using an account that qualifies under the AWS
177
181
  free-tier, you may be charged to run these suites. The charge
178
182
  should be minimal, but neither Test Kitchen nor its maintainers
179
183
  are responsible for your incurred costs.
180
184
  END
181
185
 
186
+ # If no security group IDs are specified, create one automatically.
187
+ unless config[:security_group_ids]
188
+ create_security_group(state)
189
+ config[:security_group_ids] = [state[:auto_security_group_id]]
190
+ end
191
+
192
+ # If no SSH key pair name is specified, create one automatically.
193
+ unless config[:aws_ssh_key_id]
194
+ create_key(state)
195
+ config[:aws_ssh_key_id] = state[:auto_key_id]
196
+ end
197
+
182
198
  if config[:spot_price]
183
199
  # Spot instance when a price is set
184
200
  server = submit_spot(state)
@@ -232,28 +248,49 @@ module Kitchen
232
248
 
233
249
  info("EC2 instance <#{state[:server_id]}> ready (hostname: #{state[:hostname]}).")
234
250
  instance.transport.connection(state).wait_until_ready
235
- create_ec2_json(state)
251
+ create_ec2_json(state) if instance.provisioner.name =~ /chef/
236
252
  debug("ec2:create '#{state[:hostname]}'")
253
+ rescue Exception
254
+ # Clean up any auto-created security groups or keys on the way out.
255
+ delete_security_group(state)
256
+ delete_key(state)
257
+ raise
237
258
  end
238
259
 
239
260
  def destroy(state)
240
- return if state[:server_id].nil?
241
-
242
- server = ec2.get_instance(state[:server_id])
243
- unless server.nil?
244
- instance.transport.connection(state).close
245
- server.terminate
246
- end
247
- if state[:spot_request_id]
248
- debug("Deleting spot request <#{state[:server_id]}>")
249
- ec2.client.cancel_spot_instance_requests(
250
- :spot_instance_request_ids => [state[:spot_request_id]]
251
- )
252
- state.delete(:spot_request_id)
261
+ if state[:server_id]
262
+ server = ec2.get_instance(state[:server_id])
263
+ unless server.nil?
264
+ instance.transport.connection(state).close
265
+ server.terminate
266
+ end
267
+ if state[:spot_request_id]
268
+ debug("Deleting spot request <#{state[:server_id]}>")
269
+ ec2.client.cancel_spot_instance_requests(
270
+ :spot_instance_request_ids => [state[:spot_request_id]]
271
+ )
272
+ state.delete(:spot_request_id)
273
+ end
274
+ # If we are going to clean up an automatic security group, we need
275
+ # to wait for the instance to shut down. This slightly breaks the
276
+ # subsystem encapsulation, sorry not sorry.
277
+ if state[:auto_security_group_id] && server
278
+ server.wait_until_terminated do |waiter|
279
+ waiter.max_attempts = config[:retryable_tries]
280
+ waiter.delay = config[:retryable_sleep]
281
+ waiter.before_attempt do |attempts|
282
+ info "Waited #{attempts * waiter.delay}/#{waiter.delay * waiter.max_attempts}s for instance <#{server.id}> to terminate."
283
+ end
284
+ end
285
+ end
286
+ info("EC2 instance <#{state[:server_id]}> destroyed.")
287
+ state.delete(:server_id)
288
+ state.delete(:hostname)
253
289
  end
254
- info("EC2 instance <#{state[:server_id]}> destroyed.")
255
- state.delete(:server_id)
256
- state.delete(:hostname)
290
+
291
+ # Clean up any auto-created security groups or keys.
292
+ delete_security_group(state)
293
+ delete_key(state)
257
294
  end
258
295
 
259
296
  def image
@@ -329,9 +366,6 @@ module Kitchen
329
366
  @ec2 ||= Aws::Client.new(
330
367
  config[:region],
331
368
  config[:shared_credentials_profile],
332
- config[:aws_access_key_id],
333
- config[:aws_secret_access_key],
334
- config[:aws_session_token],
335
369
  config[:http_proxy],
336
370
  config[:retry_limit],
337
371
  config[:ssl_verify_peer]
@@ -488,7 +522,7 @@ module Kitchen
488
522
  !enc.nil? && !enc.empty?
489
523
  end
490
524
  pass = with_request_limit_backoff(state) do
491
- server.decrypt_windows_password(File.expand_path(instance.transport[:ssh_key]))
525
+ server.decrypt_windows_password(File.expand_path(state[:ssh_key] || instance.transport[:ssh_key]))
492
526
  end
493
527
  state[:password] = pass
494
528
  info("Retrieved Windows password for instance <#{state[:server_id]}>.")
@@ -640,6 +674,110 @@ module Kitchen
640
674
  " Created: #{image.creation_date}"
641
675
  end
642
676
 
677
+ # Create a temporary security group for this instance.
678
+ #
679
+ # @api private
680
+ # @param state [Hash] Instance state hash.
681
+ # @return [void]
682
+ def create_security_group(state)
683
+ return if state[:auto_security_group_id]
684
+ # Work out which VPC, if any, we are creating in.
685
+ vpc_id = if config[:subnet_id]
686
+ # Get the VPC ID for the subnet.
687
+ subnets = ec2.client.describe_subnets(filters: [{ name: "subnet-id", values: [config[:subnet_id]] }]).subnets
688
+ raise "Subnet #{config[:subnet_id]} not found during security group creation" if subnets.empty?
689
+ subnets.first.vpc_id
690
+ else
691
+ # Try to check for a default VPC.
692
+ vpcs = ec2.client.describe_vpcs(filters: [{ name: "isDefault", values: ["true"] }]).vpcs
693
+ if vpcs.empty?
694
+ # No default VPC so assume EC2-Classic ¯\_(ツ)_/¯
695
+ nil
696
+ else
697
+ # I don't actually know if you can have more than one default VPC?
698
+ vpcs.first.vpc_id
699
+ end
700
+ end
701
+ # Create the SG.
702
+ params = {
703
+ group_name: "kitchen-#{Array.new(8) { rand(36).to_s(36) }.join}",
704
+ description: "Test Kitchen for #{instance.name} by #{Etc.getlogin || 'nologin'} on #{Socket.gethostname}",
705
+ }
706
+ params[:vpc_id] = vpc_id if vpc_id
707
+ resp = ec2.client.create_security_group(params)
708
+ state[:auto_security_group_id] = resp.group_id
709
+ info("Created automatic security group #{state[:auto_security_group_id]}")
710
+ debug(" in VPC #{vpc_id || 'none'}")
711
+ # Set up SG rules.
712
+ ec2.client.authorize_security_group_ingress(
713
+ group_id: state[:auto_security_group_id],
714
+ # Allow SSH and WinRM (both plain and TLS).
715
+ ip_permissions: [22, 5985, 5986].map do |port|
716
+ {
717
+ ip_protocol: "tcp",
718
+ from_port: port,
719
+ to_port: port,
720
+ ip_ranges: [{ cidr_ip: "0.0.0.0/0" }],
721
+ }
722
+ end
723
+ )
724
+ end
725
+
726
+ # Create a temporary SSH key pair for this instance.
727
+ #
728
+ # @api private
729
+ # @param state [Hash] Instance state hash.
730
+ # @return [void]
731
+ def create_key(state)
732
+ return if state[:auto_key_id]
733
+ # Encode a bunch of metadata into the name because that's all we can
734
+ # set for a key pair.
735
+ name_parts = [
736
+ instance.name.gsub(/\W/, ""),
737
+ (Etc.getlogin || "nologin").gsub(/\W/, ""),
738
+ Socket.gethostname.gsub(/\W/, "")[0..20],
739
+ Time.now.utc.iso8601,
740
+ Array.new(8) { rand(36).to_s(36) }.join(""),
741
+ ]
742
+ resp = ec2.client.create_key_pair(key_name: "kitchen-#{name_parts.join('-')}")
743
+ state[:auto_key_id] = resp.key_name
744
+ info("Created automatic key pair #{state[:auto_key_id]}")
745
+ # Write the key out, but safely hence the weird sysopen.
746
+ key_path = "#{config[:kitchen_root]}/.kitchen/#{instance.name}.pem"
747
+ key_fd = File.sysopen(key_path, File::WRONLY | File::CREAT | File::EXCL, 00600)
748
+ File.open(key_fd) do |f|
749
+ f.write(resp.key_material)
750
+ end
751
+ # Inject the key into the state to be used by the SSH transport, or for
752
+ # the Windows password decrypt above in {#fetch_windows_admin_password}.
753
+ state[:ssh_key] = key_path
754
+ end
755
+
756
+ # Clean up a temporary security group for this instance.
757
+ #
758
+ # @api private
759
+ # @param state [Hash] Instance state hash.
760
+ # @return [void]
761
+ def delete_security_group(state)
762
+ return unless state[:auto_security_group_id]
763
+ info("Removing automatic security group #{state[:auto_security_group_id]}")
764
+ ec2.client.delete_security_group(group_id: state[:auto_security_group_id])
765
+ state.delete(:auto_security_group_id)
766
+ end
767
+
768
+ # Clean up a temporary SSH key pair for this instance.
769
+ #
770
+ # @api private
771
+ # @param state [Hash] Instance state hash.
772
+ # @return [void]
773
+ def delete_key(state)
774
+ return unless state[:auto_key_id]
775
+ info("Removing automatic key pair #{state[:auto_key_id]}")
776
+ ec2.client.delete_key_pair(key_name: state[:auto_key_id])
777
+ state.delete(:auto_key_id)
778
+ File.unlink("#{config[:kitchen_root]}/.kitchen/#{instance.name}.pem")
779
+ end
780
+
643
781
  end
644
782
  end
645
783
  end
@@ -21,6 +21,6 @@ module Kitchen
21
21
  module Driver
22
22
 
23
23
  # Version string for EC2 Test Kitchen driver
24
- EC2_VERSION = "2.0.0"
24
+ EC2_VERSION = "2.1.0"
25
25
  end
26
26
  end
@@ -39,9 +39,6 @@ describe Kitchen::Driver::Aws::Client do
39
39
  Kitchen::Driver::Aws::Client.new(
40
40
  "us-west-1",
41
41
  "test-profile",
42
- "access_key_id",
43
- "secret_access_key",
44
- "session_token",
45
42
  "http_proxy",
46
43
  999,
47
44
  false
@@ -30,10 +30,13 @@ describe Kitchen::Driver::Ec2 do
30
30
  :aws_ssh_key_id => "key",
31
31
  :image_id => "ami-1234567",
32
32
  :block_duration_minutes => 60,
33
+ :subnet_id => "subnet-1234",
34
+ :security_group_ids => ["sg-56789"],
33
35
  }
34
36
  end
35
37
  let(:platform) { Kitchen::Platform.new(:name => "fooos-99") }
36
38
  let(:transport) { Kitchen::Transport::Dummy.new }
39
+ let(:provisioner) { Kitchen::Provisioner::Dummy.new }
37
40
  let(:generator) { instance_double(Kitchen::Driver::Aws::InstanceGenerator) }
38
41
  # There is too much name overlap I let creep in - my `client` is actually
39
42
  # a wrapper around the actual ec2 client
@@ -49,6 +52,7 @@ describe Kitchen::Driver::Ec2 do
49
52
  Kitchen::Instance,
50
53
  :logger => logger,
51
54
  :transport => transport,
55
+ :provisioner => provisioner,
52
56
  :platform => platform,
53
57
  :to_str => "str"
54
58
  )
@@ -458,12 +462,22 @@ describe Kitchen::Driver::Ec2 do
458
462
  expect(driver).to receive(:wait_until_ready).with(server, state)
459
463
  allow(actual_client).to receive(:describe_images).with({ :image_ids => [server.image_id] }).and_return(ec2_stub)
460
464
  expect(transport).to receive_message_chain("connection.wait_until_ready")
461
- expect(driver).to receive(:create_ec2_json).with(state)
462
465
  driver.create(state)
463
466
  expect(state[:server_id]).to eq(id)
464
467
  end
465
468
  end
466
469
 
470
+ context "chef provisioner" do
471
+ let(:provisioner) { double("chef provisioner", :name => "chef_solo") }
472
+
473
+ before do
474
+ expect(driver).to receive(:create_ec2_json).with(state)
475
+ expect(driver).to receive(:submit_server).and_return(server)
476
+ end
477
+
478
+ include_examples "common create"
479
+ end
480
+
467
481
  context "non-windows on-demand instance" do
468
482
  before do
469
483
  expect(driver).to receive(:submit_server).and_return(server)
@@ -520,6 +534,78 @@ describe Kitchen::Driver::Ec2 do
520
534
  end
521
535
  end
522
536
 
537
+ context "with no security group specified" do
538
+ before do
539
+ config.delete(:security_group_ids)
540
+ expect(driver).to receive(:submit_server).and_return(server)
541
+ allow(instance).to receive(:name).and_return("instance_name")
542
+ end
543
+
544
+ context "with a subnet configured" do
545
+ before do
546
+ expect(actual_client).to receive(:describe_subnets).with(filters: [{ name: "subnet-id", values: ["subnet-1234"] }]).and_return(double(subnets: [double(vpc_id: "vpc-1")]))
547
+ expect(actual_client).to receive(:create_security_group).with(group_name: /kitchen-/, description: /Test Kitchen for/, vpc_id: "vpc-1").and_return(double(group_id: "sg-9876"))
548
+ expect(actual_client).to receive(:authorize_security_group_ingress).with(group_id: "sg-9876", ip_permissions: [
549
+ { ip_protocol: "tcp", from_port: 22, to_port: 22, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
550
+ { ip_protocol: "tcp", from_port: 5985, to_port: 5985, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
551
+ { ip_protocol: "tcp", from_port: 5986, to_port: 5986, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
552
+ ])
553
+ end
554
+
555
+ include_examples "common create"
556
+ end
557
+
558
+ context "with a default VPC" do
559
+ before do
560
+ config.delete(:subnet_id)
561
+ expect(actual_client).to receive(:describe_vpcs).with(filters: [{ name: "isDefault", values: ["true"] }]).and_return(double(vpcs: [double(vpc_id: "vpc-1")]))
562
+ expect(actual_client).to receive(:create_security_group).with(group_name: /kitchen-/, description: /Test Kitchen for/, vpc_id: "vpc-1").and_return(double(group_id: "sg-9876"))
563
+ expect(actual_client).to receive(:authorize_security_group_ingress).with(group_id: "sg-9876", ip_permissions: [
564
+ { ip_protocol: "tcp", from_port: 22, to_port: 22, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
565
+ { ip_protocol: "tcp", from_port: 5985, to_port: 5985, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
566
+ { ip_protocol: "tcp", from_port: 5986, to_port: 5986, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
567
+ ])
568
+ end
569
+
570
+ include_examples "common create"
571
+ end
572
+
573
+ context "without a default VPC" do
574
+ before do
575
+ config.delete(:subnet_id)
576
+ expect(actual_client).to receive(:describe_vpcs).with(filters: [{ name: "isDefault", values: ["true"] }]).and_return(double(vpcs: []))
577
+ expect(actual_client).to receive(:create_security_group).with(group_name: /kitchen-/, description: /Test Kitchen for/).and_return(double(group_id: "sg-9876"))
578
+ expect(actual_client).to receive(:authorize_security_group_ingress).with(group_id: "sg-9876", ip_permissions: [
579
+ { ip_protocol: "tcp", from_port: 22, to_port: 22, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
580
+ { ip_protocol: "tcp", from_port: 5985, to_port: 5985, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
581
+ { ip_protocol: "tcp", from_port: 5986, to_port: 5986, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
582
+ ])
583
+ end
584
+
585
+ include_examples "common create"
586
+ end
587
+ end
588
+
589
+ context "with no key pair configured" do
590
+ before do
591
+ config[:kitchen_root] = "/kitchen"
592
+ config.delete(:aws_ssh_key_id)
593
+ expect(driver).to receive(:submit_server).and_return(server)
594
+ allow(instance).to receive(:name).and_return("instance_name")
595
+
596
+ expect(actual_client).to receive(:create_key_pair).with(key_name: /kitchen-/).and_return(double(key_name: "kitchen-asdf", key_material: "RSA PRIVATE KEY"))
597
+ fake_fd = double()
598
+ fake_file = double()
599
+ allow(File).to receive(:sysopen).and_call_original
600
+ expect(File).to receive(:sysopen).with("/kitchen/.kitchen/instance_name.pem", kind_of(Numeric), kind_of(Numeric)).and_return(fake_fd)
601
+ allow(File).to receive(:open).and_call_original
602
+ expect(File).to receive(:open).with(fake_fd).and_yield(fake_file)
603
+ expect(fake_file).to receive(:write).with("RSA PRIVATE KEY")
604
+ end
605
+
606
+ include_examples "common create"
607
+ end
608
+
523
609
  end
524
610
 
525
611
  describe "#destroy" do
@@ -563,6 +649,29 @@ describe Kitchen::Driver::Ec2 do
563
649
  expect(state).to eq({})
564
650
  end
565
651
  end
652
+
653
+ context "when the state has an automatic security group" do
654
+ let(:state) { { auto_security_group_id: "sg-asdf" } }
655
+
656
+ it "destroys the security group" do
657
+ expect(actual_client).to receive(:delete_security_group).with(group_id: "sg-asdf")
658
+ driver.destroy(state)
659
+ expect(state).to eq({})
660
+ end
661
+ end
662
+
663
+ context "when the state has an automatic key pair" do
664
+ let(:state) { { auto_key_id: "kitchen-asdf" } }
665
+
666
+ it "destroys the key pair" do
667
+ config[:kitchen_root] = "/kitchen"
668
+ allow(instance).to receive(:name).and_return("instance_name")
669
+ expect(actual_client).to receive(:delete_key_pair).with(key_name: "kitchen-asdf")
670
+ expect(File).to receive(:unlink).with("/kitchen/.kitchen/instance_name.pem")
671
+ driver.destroy(state)
672
+ expect(state).to eq({})
673
+ end
674
+ end
566
675
  end
567
676
 
568
677
  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: 2.0.0
4
+ version: 2.1.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: 2017-12-12 00:00:00.000000000 Z
11
+ date: 2018-01-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: test-kitchen