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 +4 -4
- data/.travis.yml +15 -4
- data/CHANGELOG.md +9 -1
- data/README.md +78 -138
- data/lib/kitchen/driver/aws/client.rb +0 -3
- data/lib/kitchen/driver/ec2.rb +160 -22
- data/lib/kitchen/driver/ec2_version.rb +1 -1
- data/spec/kitchen/driver/ec2/client_spec.rb +0 -3
- data/spec/kitchen/driver/ec2_spec.rb +110 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 959a1ab9d5bc68f378d275e54b2582173dd1b639
|
4
|
+
data.tar.gz: e1860a9b7dd6edc37eb04af7ef451d0b0e05575b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
9
|
-
- 2.3
|
10
|
-
|
11
|
-
|
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/
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
-
|
39
|
-
|
23
|
+
driver:
|
24
|
+
name: ec2
|
40
25
|
```
|
41
|
-
|
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: `
|
58
|
-
`
|
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
|
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.
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
204
|
-
|
205
|
-
|
162
|
+
```yaml
|
163
|
+
transport:
|
164
|
+
ssh_key: ~/.ssh/mykey.pem
|
165
|
+
```
|
206
166
|
|
207
|
-
|
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
|
-
|
210
|
-
default AMI out of `amis.json` if one is not specified.
|
170
|
+
#### WinRM
|
211
171
|
|
212
|
-
|
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
|
-
|
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
|
-
####
|
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
|
data/lib/kitchen/driver/ec2.rb
CHANGED
@@ -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
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
255
|
-
|
256
|
-
state
|
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
|
@@ -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.
|
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:
|
11
|
+
date: 2018-01-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: test-kitchen
|