kitchen-ec2 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/lib/kitchen/driver/ec2.rb +6 -2
- data/lib/kitchen/driver/ec2_version.rb +1 -1
- metadata +4 -24
- data/.gitignore +0 -22
- data/.rspec +0 -3
- data/.travis.yml +0 -21
- data/.yardopts +0 -3
- data/Gemfile +0 -16
- data/README.md +0 -497
- data/Rakefile +0 -43
- data/kitchen-ec2.gemspec +0 -40
- data/spec/kitchen/driver/ec2/client_spec.rb +0 -66
- data/spec/kitchen/driver/ec2/image_selection_spec.rb +0 -371
- data/spec/kitchen/driver/ec2/instance_generator_spec.rb +0 -612
- data/spec/kitchen/driver/ec2_spec.rb +0 -797
- data/spec/spec_helper.rb +0 -107
- data/spec/support/fake_image.rb +0 -36
@@ -1,797 +0,0 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Author:: Tyler Ball (<tball@chef.io>)
|
4
|
-
#
|
5
|
-
# Copyright:: 2015-2018, Fletcher Nichol
|
6
|
-
# Copyright:: 2016-2018, Chef Software, Inc.
|
7
|
-
#
|
8
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
9
|
-
# you may not use this file except in compliance with the License.
|
10
|
-
# You may obtain a copy of the License at
|
11
|
-
#
|
12
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
13
|
-
#
|
14
|
-
# Unless required by applicable law or agreed to in writing, software
|
15
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
16
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
17
|
-
# See the License for the specific language governing permissions and
|
18
|
-
# limitations under the License.
|
19
|
-
|
20
|
-
require "kitchen/driver/ec2"
|
21
|
-
require "kitchen/provisioner/dummy"
|
22
|
-
require "kitchen/transport/dummy"
|
23
|
-
require "kitchen/verifier/dummy"
|
24
|
-
|
25
|
-
describe Kitchen::Driver::Ec2 do
|
26
|
-
|
27
|
-
let(:logged_output) { StringIO.new }
|
28
|
-
let(:logger) { Logger.new(logged_output) }
|
29
|
-
let(:config) do
|
30
|
-
{
|
31
|
-
:aws_ssh_key_id => "key",
|
32
|
-
:image_id => "ami-1234567",
|
33
|
-
:block_duration_minutes => 60,
|
34
|
-
:subnet_id => "subnet-1234",
|
35
|
-
:security_group_ids => ["sg-56789"],
|
36
|
-
}
|
37
|
-
end
|
38
|
-
let(:platform) { Kitchen::Platform.new(:name => "fooos-99") }
|
39
|
-
let(:transport) { Kitchen::Transport::Dummy.new }
|
40
|
-
let(:provisioner) { Kitchen::Provisioner::Dummy.new }
|
41
|
-
let(:generator) { instance_double(Kitchen::Driver::Aws::InstanceGenerator) }
|
42
|
-
# There is too much name overlap I let creep in - my `client` is actually
|
43
|
-
# a wrapper around the actual ec2 client
|
44
|
-
let(:actual_client) { double("actual ec2 client") }
|
45
|
-
let(:client) { double(Kitchen::Driver::Aws::Client, :client => actual_client) }
|
46
|
-
let(:server) { double("aws server object") }
|
47
|
-
let(:state) { {} }
|
48
|
-
|
49
|
-
let(:driver) { Kitchen::Driver::Ec2.new(config) }
|
50
|
-
|
51
|
-
let(:instance) do
|
52
|
-
instance_double(
|
53
|
-
Kitchen::Instance,
|
54
|
-
:logger => logger,
|
55
|
-
:transport => transport,
|
56
|
-
:provisioner => provisioner,
|
57
|
-
:platform => platform,
|
58
|
-
:to_str => "str"
|
59
|
-
)
|
60
|
-
end
|
61
|
-
|
62
|
-
before do
|
63
|
-
allow(Kitchen::Driver::Aws::InstanceGenerator).to receive(:new).and_return(generator)
|
64
|
-
allow(Kitchen::Driver::Aws::Client).to receive(:new).and_return(client)
|
65
|
-
allow(driver).to receive(:windows_os?).and_return(false)
|
66
|
-
allow(driver).to receive(:instance).and_return(instance)
|
67
|
-
end
|
68
|
-
|
69
|
-
it "driver api_version is 2" do
|
70
|
-
expect(driver.diagnose_plugin[:api_version]).to eq(2)
|
71
|
-
end
|
72
|
-
|
73
|
-
it "plugin_version is set to Kitchen::Vagrant::VERSION" do
|
74
|
-
expect(driver.diagnose_plugin[:version]).to eq(
|
75
|
-
Kitchen::Driver::EC2_VERSION)
|
76
|
-
end
|
77
|
-
|
78
|
-
describe "default_config" do
|
79
|
-
context "Windows" do
|
80
|
-
let(:resource) { instance_double(::Aws::EC2::Resource, :image => image) }
|
81
|
-
before do
|
82
|
-
allow(driver).to receive(:windows_os?).and_return(true)
|
83
|
-
allow(client).to receive(:resource).and_return(resource)
|
84
|
-
allow(instance).to receive(:name).and_return("instance_name")
|
85
|
-
end
|
86
|
-
context "Windows 2016" do
|
87
|
-
let(:image) do
|
88
|
-
FakeImage.new(:name => "Windows_Server-2016-English-Full-Base-2017.01.11")
|
89
|
-
end
|
90
|
-
it "sets :user_data to something" do
|
91
|
-
expect(driver[:user_data]).to include
|
92
|
-
'$logfile=C:\\ProgramData\\Amazon\\EC2-Windows\\Launch\\Log\\kitchen-ec2.log'
|
93
|
-
end
|
94
|
-
end
|
95
|
-
context "Windows 2012R2" do
|
96
|
-
let(:image) do
|
97
|
-
FakeImage.new(:name => "Windows_Server-2012-R2_RTM-English-64Bit-Base-2017.01.11")
|
98
|
-
end
|
99
|
-
it "sets :user_data to something" do
|
100
|
-
expect(driver[:user_data]).to include
|
101
|
-
'$logfile=C:\\Program Files\\Amazon\\Ec2ConfigService\\Logs\\kitchen-ec2.log'
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
describe "#hostname" do
|
108
|
-
let(:public_dns_name) { nil }
|
109
|
-
let(:private_dns_name) { nil }
|
110
|
-
let(:public_ip_address) { nil }
|
111
|
-
let(:private_ip_address) { nil }
|
112
|
-
let(:server) do
|
113
|
-
double("server",
|
114
|
-
:public_dns_name => public_dns_name,
|
115
|
-
:private_dns_name => private_dns_name,
|
116
|
-
:public_ip_address => public_ip_address,
|
117
|
-
:private_ip_address => private_ip_address
|
118
|
-
)
|
119
|
-
end
|
120
|
-
|
121
|
-
it "returns nil if all sources are nil" do
|
122
|
-
expect(driver.hostname(server)).to eq(nil)
|
123
|
-
end
|
124
|
-
|
125
|
-
it "raises an error if provided an unknown interface" do
|
126
|
-
expect { driver.hostname(server, "foobar") }.to raise_error(Kitchen::UserError)
|
127
|
-
end
|
128
|
-
|
129
|
-
shared_examples "an interface type provided" do
|
130
|
-
it "returns public_dns_name when requested" do
|
131
|
-
expect(driver.hostname(server, "dns")).to eq(public_dns_name)
|
132
|
-
end
|
133
|
-
it "returns public_ip_address when requested" do
|
134
|
-
expect(driver.hostname(server, "public")).to eq(public_ip_address)
|
135
|
-
end
|
136
|
-
it "returns private_ip_address when requested" do
|
137
|
-
expect(driver.hostname(server, "private")).to eq(private_ip_address)
|
138
|
-
end
|
139
|
-
it "returns private_dns_name when requested" do
|
140
|
-
expect(driver.hostname(server, "private_dns")).to eq(private_dns_name)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
|
144
|
-
context "private_dns_name is populated" do
|
145
|
-
let(:private_dns_name) { "private_dns_name" }
|
146
|
-
|
147
|
-
it "returns the private_dns_name" do
|
148
|
-
expect(driver.hostname(server)).to eq(private_dns_name)
|
149
|
-
end
|
150
|
-
|
151
|
-
include_examples "an interface type provided"
|
152
|
-
end
|
153
|
-
|
154
|
-
context "private_ip_address is populated" do
|
155
|
-
let(:private_dns_name) { "private_dns_name" }
|
156
|
-
let(:private_ip_address) { "10.0.0.1" }
|
157
|
-
|
158
|
-
it "returns the private_ip_address" do
|
159
|
-
expect(driver.hostname(server)).to eq(private_ip_address)
|
160
|
-
end
|
161
|
-
|
162
|
-
include_examples "an interface type provided"
|
163
|
-
end
|
164
|
-
|
165
|
-
context "public_ip_address is populated" do
|
166
|
-
let(:private_dns_name) { "private_dns_name" }
|
167
|
-
let(:private_ip_address) { "10.0.0.1" }
|
168
|
-
let(:public_ip_address) { "127.0.0.1" }
|
169
|
-
|
170
|
-
it "returns the public_ip_address" do
|
171
|
-
expect(driver.hostname(server)).to eq(public_ip_address)
|
172
|
-
end
|
173
|
-
|
174
|
-
include_examples "an interface type provided"
|
175
|
-
end
|
176
|
-
|
177
|
-
context "public_dns_name is populated" do
|
178
|
-
let(:private_dns_name) { "private_dns_name" }
|
179
|
-
let(:private_ip_address) { "10.0.0.1" }
|
180
|
-
let(:public_ip_address) { "127.0.0.1" }
|
181
|
-
let(:public_dns_name) { "public_dns_name" }
|
182
|
-
|
183
|
-
it "returns the public_dns_name" do
|
184
|
-
expect(driver.hostname(server)).to eq(public_dns_name)
|
185
|
-
end
|
186
|
-
|
187
|
-
include_examples "an interface type provided"
|
188
|
-
end
|
189
|
-
|
190
|
-
context "public_dns_name returns as empty string" do
|
191
|
-
let(:public_dns_name) { "" }
|
192
|
-
it "returns nil" do
|
193
|
-
expect(driver.hostname(server)).to eq(nil)
|
194
|
-
end
|
195
|
-
|
196
|
-
context "and private_ip_address is populated" do
|
197
|
-
let(:private_ip_address) { "10.0.0.1" }
|
198
|
-
it "returns the private_ip_address" do
|
199
|
-
expect(driver.hostname(server)).to eq(private_ip_address)
|
200
|
-
end
|
201
|
-
|
202
|
-
context "and private_dns_name is populated" do
|
203
|
-
let(:private_dns_name) { "private_dns_name" }
|
204
|
-
it "returns the private_ip_address" do
|
205
|
-
expect(driver.hostname(server)).to eq(private_ip_address)
|
206
|
-
end
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
describe "#submit_server" do
|
213
|
-
before do
|
214
|
-
expect(driver).to receive(:instance).at_least(:once).and_return(instance)
|
215
|
-
end
|
216
|
-
|
217
|
-
it "submits the server request" do
|
218
|
-
expect(generator).to receive(:ec2_instance_data).and_return({})
|
219
|
-
expect(client).to receive(:create_instance).with(:min_count => 1, :max_count => 1)
|
220
|
-
driver.submit_server
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
describe "submit_server with terminate shutdown behaviour" do
|
225
|
-
before do
|
226
|
-
config[:instance_initiated_shutdown_behavior] = "terminate"
|
227
|
-
expect(driver).to receive(:instance).at_least(:once).and_return(instance)
|
228
|
-
end
|
229
|
-
|
230
|
-
it "submits the server request" do
|
231
|
-
expect(generator).to receive(:ec2_instance_data).and_return(
|
232
|
-
:instance_initiated_shutdown_behavior => "terminate"
|
233
|
-
)
|
234
|
-
expect(client).to receive(:create_instance).with(
|
235
|
-
:min_count => 1, :max_count => 1, :instance_initiated_shutdown_behavior => "terminate"
|
236
|
-
)
|
237
|
-
driver.submit_server
|
238
|
-
end
|
239
|
-
end
|
240
|
-
|
241
|
-
describe "#submit_spot" do
|
242
|
-
let(:state) { {} }
|
243
|
-
let(:response) do
|
244
|
-
{ :spot_instance_requests => [{ :spot_instance_request_id => "id" }] }
|
245
|
-
end
|
246
|
-
|
247
|
-
before do
|
248
|
-
expect(driver).to receive(:instance).at_least(:once).and_return(instance)
|
249
|
-
allow(Time).to receive(:now).and_return(Time.now)
|
250
|
-
end
|
251
|
-
|
252
|
-
it "submits the server request" do
|
253
|
-
expect(generator).to receive(:ec2_instance_data).and_return({})
|
254
|
-
expect(actual_client).to receive(:request_spot_instances).with(
|
255
|
-
:spot_price => "",
|
256
|
-
:launch_specification => {},
|
257
|
-
:valid_until => Time.now + (config[:retryable_tries] * config[:retryable_sleep]),
|
258
|
-
:block_duration_minutes => 60
|
259
|
-
).and_return(response)
|
260
|
-
expect(actual_client).to receive(:wait_until)
|
261
|
-
expect(client).to receive(:get_instance_from_spot_request).with("id")
|
262
|
-
driver.submit_spot(state)
|
263
|
-
expect(state).to eq(:spot_request_id => "id")
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
describe "#tag_server" do
|
268
|
-
context "with no tags specified" do
|
269
|
-
it "does not raise" do
|
270
|
-
config[:tags] = nil
|
271
|
-
expect { driver.tag_server(server) }.not_to raise_error
|
272
|
-
end
|
273
|
-
end
|
274
|
-
|
275
|
-
context "with standard string tags" do
|
276
|
-
it "tags the server" do
|
277
|
-
config[:tags] = { :key1 => "value1", :key2 => "value2" }
|
278
|
-
expect(server).to receive(:create_tags).with(
|
279
|
-
:tags => [
|
280
|
-
{ :key => :key1, :value => "value1" },
|
281
|
-
{ :key => :key2, :value => "value2" },
|
282
|
-
]
|
283
|
-
)
|
284
|
-
driver.tag_server(server)
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
|
-
context "with a tag that includes a Integer value" do
|
289
|
-
it "tags the server" do
|
290
|
-
config[:tags] = { :key1 => "value1", :key2 => 1 }
|
291
|
-
expect(server).to receive(:create_tags).with(
|
292
|
-
:tags => [
|
293
|
-
{ :key => :key1, :value => "value1" },
|
294
|
-
{ :key => :key2, :value => "1" },
|
295
|
-
]
|
296
|
-
)
|
297
|
-
driver.tag_server(server)
|
298
|
-
end
|
299
|
-
end
|
300
|
-
|
301
|
-
context "with a tag that includes a Nil value" do
|
302
|
-
it "tags the server" do
|
303
|
-
config[:tags] = { :key1 => "value1", :key2 => nil }
|
304
|
-
expect(server).to receive(:create_tags).with(
|
305
|
-
:tags => [
|
306
|
-
{ :key => :key1, :value => "value1" },
|
307
|
-
{ :key => :key2, :value => "" },
|
308
|
-
]
|
309
|
-
)
|
310
|
-
driver.tag_server(server)
|
311
|
-
end
|
312
|
-
end
|
313
|
-
end
|
314
|
-
|
315
|
-
describe "#tag_volumes" do
|
316
|
-
let(:volume) { double("aws volume resource") }
|
317
|
-
before do
|
318
|
-
allow(server).to receive(:volumes).and_return([volume])
|
319
|
-
end
|
320
|
-
context "with standard string tags" do
|
321
|
-
it "tags the instance volumes" do
|
322
|
-
config[:tags] = { :key1 => "value1", :key2 => "value2" }
|
323
|
-
expect(volume).to receive(:create_tags).with(
|
324
|
-
:tags => [
|
325
|
-
{ :key => :key1, :value => "value1" },
|
326
|
-
{ :key => :key2, :value => "value2" },
|
327
|
-
]
|
328
|
-
)
|
329
|
-
driver.tag_volumes(server)
|
330
|
-
end
|
331
|
-
end
|
332
|
-
|
333
|
-
context "with a tag that includes a Integer value" do
|
334
|
-
it "tags the instance volumes" do
|
335
|
-
config[:tags] = { :key1 => "value1", :key2 => 2 }
|
336
|
-
expect(volume).to receive(:create_tags).with(
|
337
|
-
:tags => [
|
338
|
-
{ :key => :key1, :value => "value1" },
|
339
|
-
{ :key => :key2, :value => "2" },
|
340
|
-
]
|
341
|
-
)
|
342
|
-
driver.tag_volumes(server)
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
context "with a tag that includes a Nil value" do
|
347
|
-
it "tags the instance volumes" do
|
348
|
-
config[:tags] = { :key1 => "value1", :key2 => nil }
|
349
|
-
expect(volume).to receive(:create_tags).with(
|
350
|
-
:tags => [
|
351
|
-
{ :key => :key1, :value => "value1" },
|
352
|
-
{ :key => :key2, :value => "" },
|
353
|
-
]
|
354
|
-
)
|
355
|
-
driver.tag_volumes(server)
|
356
|
-
end
|
357
|
-
end
|
358
|
-
end
|
359
|
-
|
360
|
-
describe "#wait_until_ready" do
|
361
|
-
let(:hostname) { "0.0.0.0" }
|
362
|
-
let(:msg) { "to become ready" }
|
363
|
-
let(:aws_instance) { double("aws instance") }
|
364
|
-
|
365
|
-
before do
|
366
|
-
config[:interface] = :i
|
367
|
-
expect(driver).to receive(:wait_with_destroy).with(server, state, msg).and_yield(aws_instance)
|
368
|
-
expect(driver).to receive(:hostname).with(aws_instance, :i).and_return(hostname)
|
369
|
-
end
|
370
|
-
|
371
|
-
after do
|
372
|
-
expect(state[:hostname]).to eq(hostname)
|
373
|
-
end
|
374
|
-
|
375
|
-
it "first checks instance existence" do
|
376
|
-
expect(aws_instance).to receive(:exists?).and_return(false)
|
377
|
-
expect(driver.wait_until_ready(server, state)).to eq(false)
|
378
|
-
end
|
379
|
-
|
380
|
-
it "second checks instance state" do
|
381
|
-
expect(aws_instance).to receive(:exists?).and_return(true)
|
382
|
-
expect(aws_instance).to receive_message_chain("state.name").and_return("nope")
|
383
|
-
expect(driver.wait_until_ready(server, state)).to eq(false)
|
384
|
-
end
|
385
|
-
|
386
|
-
it "third checks hostname" do
|
387
|
-
expect(aws_instance).to receive(:exists?).and_return(true)
|
388
|
-
expect(aws_instance).to receive_message_chain("state.name").and_return("running")
|
389
|
-
expect(driver.wait_until_ready(server, state)).to eq(false)
|
390
|
-
end
|
391
|
-
|
392
|
-
context "when it exists, has a valid state and a valid hostname" do
|
393
|
-
let(:hostname) { "host" }
|
394
|
-
|
395
|
-
it "returns true" do
|
396
|
-
expect(aws_instance).to receive(:exists?).and_return(true)
|
397
|
-
expect(aws_instance).to receive_message_chain("state.name").and_return("running")
|
398
|
-
expect(driver.wait_until_ready(server, state)).to eq(true)
|
399
|
-
end
|
400
|
-
end
|
401
|
-
end
|
402
|
-
|
403
|
-
describe "#wait_until_volumes_ready" do
|
404
|
-
let(:aws_instance) { double("aws instance") }
|
405
|
-
let(:msg) { "volumes to be ready" }
|
406
|
-
let(:volume) { double("aws volume resource") }
|
407
|
-
|
408
|
-
before do
|
409
|
-
expect(driver).to receive(:wait_with_destroy).with(server, state, msg).and_yield(aws_instance)
|
410
|
-
end
|
411
|
-
it "first checks instance existence" do
|
412
|
-
expect(aws_instance).to receive(:exists?).and_return(false)
|
413
|
-
expect(driver.wait_until_volumes_ready(server, state)).to eq(false)
|
414
|
-
end
|
415
|
-
it "second, it checks for prescence of described volumes" do
|
416
|
-
expect(aws_instance).to receive(:exists?).and_return(true)
|
417
|
-
expect(actual_client).to receive_message_chain(:describe_volumes, :volumes, :length
|
418
|
-
).and_return(0)
|
419
|
-
expect(aws_instance).to receive(:volumes).and_return([])
|
420
|
-
expect(driver.wait_until_volumes_ready(server, state)).to eq(false)
|
421
|
-
end
|
422
|
-
it "third, it compares the described volumes and instance volumes" do
|
423
|
-
expect(aws_instance).to receive(:exists?).and_return(true)
|
424
|
-
expect(actual_client).to receive_message_chain(:describe_volumes, :volumes, :length
|
425
|
-
).and_return(2)
|
426
|
-
expect(aws_instance).to receive(:volumes).and_return([volume])
|
427
|
-
expect(driver.wait_until_volumes_ready(server, state)).to eq(false)
|
428
|
-
end
|
429
|
-
context "when it exists, and both client and instance agree on volumes" do
|
430
|
-
it "returns true" do
|
431
|
-
expect(aws_instance).to receive(:exists?).and_return(true)
|
432
|
-
expect(actual_client).to receive_message_chain(:describe_volumes, :volumes, :length
|
433
|
-
).and_return(1)
|
434
|
-
expect(aws_instance).to receive(:volumes).and_return([volume])
|
435
|
-
expect(driver.wait_until_volumes_ready(server, state)).to eq(true)
|
436
|
-
end
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
describe "#fetch_windows_admin_password" do
|
441
|
-
let(:msg) { "to fetch windows admin password" }
|
442
|
-
let(:aws_instance) { double("aws instance") }
|
443
|
-
let(:server_id) { "server_id" }
|
444
|
-
let(:encrypted_password) { "alksdofw" }
|
445
|
-
let(:data) { double("data", :password_data => encrypted_password) }
|
446
|
-
let(:password) { "password" }
|
447
|
-
let(:transport) { { :ssh_key => "foo" } }
|
448
|
-
|
449
|
-
before do
|
450
|
-
state[:server_id] = server_id
|
451
|
-
expect(driver).to receive(:wait_with_destroy).with(server, state, msg).and_yield(aws_instance)
|
452
|
-
end
|
453
|
-
|
454
|
-
after do
|
455
|
-
expect(state[:password]).to eq(password)
|
456
|
-
end
|
457
|
-
|
458
|
-
it "fetches and decrypts the windows password" do
|
459
|
-
expect(server).to receive_message_chain("client.get_password_data").with(
|
460
|
-
:instance_id => server_id
|
461
|
-
).and_return(data)
|
462
|
-
expect(server).to receive(:decrypt_windows_password).
|
463
|
-
with(File.expand_path("foo")).
|
464
|
-
and_return(password)
|
465
|
-
driver.fetch_windows_admin_password(server, state)
|
466
|
-
end
|
467
|
-
|
468
|
-
end
|
469
|
-
|
470
|
-
describe "#wait_with_destroy" do
|
471
|
-
let(:tries) { 111 }
|
472
|
-
let(:sleep) { 222 }
|
473
|
-
let(:msg) { "msg" }
|
474
|
-
given_block = lambda { ; }
|
475
|
-
|
476
|
-
before do
|
477
|
-
config[:retryable_sleep] = sleep
|
478
|
-
config[:retryable_tries] = tries
|
479
|
-
end
|
480
|
-
|
481
|
-
it "calls wait and exits successfully if there is no error" do
|
482
|
-
expect(server).to receive(:wait_until) do |args, &block|
|
483
|
-
expect(args[:max_attempts]).to eq(tries)
|
484
|
-
expect(args[:delay]).to eq(sleep)
|
485
|
-
expect(block).to eq(given_block)
|
486
|
-
expect(driver).to receive(:info).with(/#{msg}/)
|
487
|
-
args[:before_attempt].call(0)
|
488
|
-
end
|
489
|
-
driver.wait_with_destroy(server, state, msg, &given_block)
|
490
|
-
end
|
491
|
-
|
492
|
-
it "attempts to destroy the instance if the waiter fails" do
|
493
|
-
expect(server).to receive(:wait_until).and_raise(::Aws::Waiters::Errors::WaiterFailed)
|
494
|
-
expect(driver).to receive(:destroy).with(state)
|
495
|
-
expect(driver).to receive(:error).with(/#{msg}/)
|
496
|
-
expect do
|
497
|
-
driver.wait_with_destroy(server, state, msg, &given_block)
|
498
|
-
end.to raise_error(::Aws::Waiters::Errors::WaiterFailed)
|
499
|
-
end
|
500
|
-
end
|
501
|
-
|
502
|
-
describe "#create_key" do
|
503
|
-
context "creates a key pair via the ec2 API, saves the generated key locally" do
|
504
|
-
before do
|
505
|
-
config[:kitchen_root] = "/kitchen"
|
506
|
-
config.delete(:aws_ssh_key_id)
|
507
|
-
allow(instance).to receive(:name).and_return("instance_name")
|
508
|
-
|
509
|
-
expect(actual_client).to receive(:create_key_pair).with(key_name: /kitchen-/).and_return(double(key_name: "expected-key-name", key_material: "RSA PRIVATE KEY"))
|
510
|
-
fake_file = double()
|
511
|
-
allow(File).to receive(:open).and_call_original
|
512
|
-
expect(File).to receive(:open).with("/kitchen/.kitchen/instance_name.pem", kind_of(Numeric), kind_of(Numeric)).and_yield(fake_file)
|
513
|
-
expect(fake_file).to receive(:write).with("RSA PRIVATE KEY")
|
514
|
-
end
|
515
|
-
|
516
|
-
it "generates a temporary SSH key pair for the instance" do
|
517
|
-
driver.send(:create_key, state)
|
518
|
-
expect(state[:auto_key_id]).to eq("expected-key-name")
|
519
|
-
expect(state[:ssh_key]).to eq("/kitchen/.kitchen/instance_name.pem")
|
520
|
-
end
|
521
|
-
end
|
522
|
-
end
|
523
|
-
|
524
|
-
describe "#create" do
|
525
|
-
let(:server) { double("aws server object", :id => id, :image_id => "ami-3f807145") }
|
526
|
-
let(:id) { "i-12345" }
|
527
|
-
|
528
|
-
it "returns if the instance is already created" do
|
529
|
-
state[:server_id] = id
|
530
|
-
expect(driver.create(state)).to eq(nil)
|
531
|
-
end
|
532
|
-
|
533
|
-
image_data = Aws::EC2::Types::Image.new(:root_device_type => "ebs")
|
534
|
-
ec2_stub = Aws::EC2::Types::DescribeImagesResult.new
|
535
|
-
ec2_stub.images = [image_data]
|
536
|
-
|
537
|
-
shared_examples "common create" do
|
538
|
-
it "successfully creates and tags the instance" do
|
539
|
-
expect(server).to receive(:wait_until_exists)
|
540
|
-
expect(driver).to receive(:update_username)
|
541
|
-
expect(driver).to receive(:tag_server).with(server)
|
542
|
-
expect(driver).to receive(:tag_volumes).with(server)
|
543
|
-
expect(driver).to receive(:wait_until_volumes_ready).with(server, state)
|
544
|
-
expect(driver).to receive(:wait_until_ready).with(server, state)
|
545
|
-
allow(actual_client).to receive(:describe_images).with({ :image_ids => [server.image_id] }).and_return(ec2_stub)
|
546
|
-
expect(transport).to receive_message_chain("connection.wait_until_ready")
|
547
|
-
driver.create(state)
|
548
|
-
expect(state[:server_id]).to eq(id)
|
549
|
-
end
|
550
|
-
end
|
551
|
-
|
552
|
-
context "chef provisioner" do
|
553
|
-
let(:provisioner) { double("chef provisioner", :name => "chef_solo") }
|
554
|
-
|
555
|
-
before do
|
556
|
-
expect(driver).to receive(:create_ec2_json).with(state)
|
557
|
-
expect(driver).to receive(:submit_server).and_return(server)
|
558
|
-
end
|
559
|
-
|
560
|
-
include_examples "common create"
|
561
|
-
end
|
562
|
-
|
563
|
-
context "non-windows on-demand instance" do
|
564
|
-
before do
|
565
|
-
expect(driver).to receive(:submit_server).and_return(server)
|
566
|
-
end
|
567
|
-
|
568
|
-
include_examples "common create"
|
569
|
-
end
|
570
|
-
|
571
|
-
context "config is for a spot instance" do
|
572
|
-
before do
|
573
|
-
config[:spot_price] = 1
|
574
|
-
expect(driver).to receive(:submit_spot).with(state).and_return(server)
|
575
|
-
end
|
576
|
-
|
577
|
-
include_examples "common create"
|
578
|
-
end
|
579
|
-
|
580
|
-
context "instance is not ebs-backed" do
|
581
|
-
before do
|
582
|
-
ec2_stub.images[0].root_device_type = "instance-store"
|
583
|
-
end
|
584
|
-
|
585
|
-
it "does not tag volumes or wait for volumes to be ready" do
|
586
|
-
expect(driver).to_not receive(:tag_volumes).with(server)
|
587
|
-
expect(driver).to_not receive(:wait_until_volumes_ready).with(server, state)
|
588
|
-
end
|
589
|
-
|
590
|
-
after do
|
591
|
-
ec2_stub.images[0].root_device_type = "ebs"
|
592
|
-
end
|
593
|
-
end
|
594
|
-
|
595
|
-
context "instance is a windows machine" do
|
596
|
-
before do
|
597
|
-
expect(driver).to receive(:windows_os?).and_return(true)
|
598
|
-
expect(transport).to receive(:[]).with(:username).and_return("administrator")
|
599
|
-
expect(transport).to receive(:[]).with(:password).and_return(nil)
|
600
|
-
expect(driver).to receive(:submit_server).and_return(server)
|
601
|
-
expect(driver).to receive(:fetch_windows_admin_password).with(server, state)
|
602
|
-
end
|
603
|
-
|
604
|
-
include_examples "common create"
|
605
|
-
end
|
606
|
-
|
607
|
-
context "instance is not a standard platform" do
|
608
|
-
let(:state) { {} }
|
609
|
-
before do
|
610
|
-
expect(driver).to receive(:actual_platform).and_return(nil)
|
611
|
-
end
|
612
|
-
|
613
|
-
it "doesn't set the state username" do
|
614
|
-
driver.update_username(state)
|
615
|
-
expect(state).to eq({})
|
616
|
-
end
|
617
|
-
end
|
618
|
-
|
619
|
-
context "with no security group specified" do
|
620
|
-
before do
|
621
|
-
config.delete(:security_group_ids)
|
622
|
-
expect(driver).to receive(:submit_server).and_return(server)
|
623
|
-
allow(instance).to receive(:name).and_return("instance_name")
|
624
|
-
end
|
625
|
-
|
626
|
-
context "with a subnet configured" do
|
627
|
-
before do
|
628
|
-
expect(actual_client).to receive(:describe_subnets).with(filters: [{ name: "subnet-id", values: ["subnet-1234"] }]).and_return(double(subnets: [double(vpc_id: "vpc-1")]))
|
629
|
-
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"))
|
630
|
-
expect(actual_client).to receive(:authorize_security_group_ingress).with(group_id: "sg-9876", ip_permissions: [
|
631
|
-
{ ip_protocol: "tcp", from_port: 22, to_port: 22, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
632
|
-
{ ip_protocol: "tcp", from_port: 5985, to_port: 5985, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
633
|
-
{ ip_protocol: "tcp", from_port: 5986, to_port: 5986, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
634
|
-
])
|
635
|
-
end
|
636
|
-
|
637
|
-
include_examples "common create"
|
638
|
-
end
|
639
|
-
|
640
|
-
context "with a default VPC" do
|
641
|
-
before do
|
642
|
-
config.delete(:subnet_id)
|
643
|
-
expect(actual_client).to receive(:describe_vpcs).with(filters: [{ name: "isDefault", values: ["true"] }]).and_return(double(vpcs: [double(vpc_id: "vpc-1")]))
|
644
|
-
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"))
|
645
|
-
expect(actual_client).to receive(:authorize_security_group_ingress).with(group_id: "sg-9876", ip_permissions: [
|
646
|
-
{ ip_protocol: "tcp", from_port: 22, to_port: 22, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
647
|
-
{ ip_protocol: "tcp", from_port: 5985, to_port: 5985, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
648
|
-
{ ip_protocol: "tcp", from_port: 5986, to_port: 5986, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
649
|
-
])
|
650
|
-
end
|
651
|
-
|
652
|
-
include_examples "common create"
|
653
|
-
end
|
654
|
-
|
655
|
-
context "without a default VPC" do
|
656
|
-
before do
|
657
|
-
config.delete(:subnet_id)
|
658
|
-
expect(actual_client).to receive(:describe_vpcs).with(filters: [{ name: "isDefault", values: ["true"] }]).and_return(double(vpcs: []))
|
659
|
-
expect(actual_client).to receive(:create_security_group).with(group_name: /kitchen-/, description: /Test Kitchen for/).and_return(double(group_id: "sg-9876"))
|
660
|
-
expect(actual_client).to receive(:authorize_security_group_ingress).with(group_id: "sg-9876", ip_permissions: [
|
661
|
-
{ ip_protocol: "tcp", from_port: 22, to_port: 22, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
662
|
-
{ ip_protocol: "tcp", from_port: 5985, to_port: 5985, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
663
|
-
{ ip_protocol: "tcp", from_port: 5986, to_port: 5986, ip_ranges: [{ cidr_ip: "0.0.0.0/0" }] },
|
664
|
-
])
|
665
|
-
end
|
666
|
-
|
667
|
-
include_examples "common create"
|
668
|
-
end
|
669
|
-
end
|
670
|
-
|
671
|
-
context "with no security group but filter specified" do
|
672
|
-
before do
|
673
|
-
config.delete(:security_group_ids)
|
674
|
-
config[:security_group_filter] = { tag: "SomeTag", value: "SomeValue" }
|
675
|
-
expect(driver).not_to receive(:create_security_group)
|
676
|
-
expect(driver).to receive(:submit_server).and_return(server)
|
677
|
-
allow(instance).to receive(:name).and_return("instance_name")
|
678
|
-
end
|
679
|
-
|
680
|
-
include_examples "common create"
|
681
|
-
end
|
682
|
-
|
683
|
-
context "and AWS SSH keys" do
|
684
|
-
before do
|
685
|
-
allow(driver).to receive(:submit_server).and_return(server)
|
686
|
-
allow(instance).to receive(:name).and_return("instance_name")
|
687
|
-
end
|
688
|
-
|
689
|
-
context "with no AWS-managed ssh key pair configured, creates a key pair to use" do
|
690
|
-
before do
|
691
|
-
config[:aws_ssh_key_id] = nil
|
692
|
-
expect(driver).to receive(:create_key)
|
693
|
-
state[:auto_key_id] = "autogenerated_by_create_key"
|
694
|
-
end
|
695
|
-
|
696
|
-
after do
|
697
|
-
expect(config[:aws_ssh_key_id]).to eq("autogenerated_by_create_key")
|
698
|
-
end
|
699
|
-
|
700
|
-
include_examples "common create"
|
701
|
-
end
|
702
|
-
|
703
|
-
context "with AWS-managed ssh key pair disabled, does not create a key pair or pass a key id" do
|
704
|
-
before do
|
705
|
-
config[:aws_ssh_key_id] = "_disable"
|
706
|
-
expect(driver).to_not receive(:create_key)
|
707
|
-
end
|
708
|
-
|
709
|
-
after do
|
710
|
-
expect(config[:aws_ssh_key_id]).to be_nil
|
711
|
-
end
|
712
|
-
|
713
|
-
include_examples "common create"
|
714
|
-
end
|
715
|
-
|
716
|
-
context "with AWS ssh key pair set, uses set key and does not create a key pair" do
|
717
|
-
before do
|
718
|
-
config[:aws_ssh_key_id] = "use_this_key_please"
|
719
|
-
expect(driver).to_not receive(:create_key)
|
720
|
-
end
|
721
|
-
|
722
|
-
after do
|
723
|
-
expect(config[:aws_ssh_key_id]).to eq("use_this_key_please")
|
724
|
-
end
|
725
|
-
|
726
|
-
include_examples "common create"
|
727
|
-
end
|
728
|
-
end
|
729
|
-
end
|
730
|
-
|
731
|
-
describe "#destroy" do
|
732
|
-
context "when state[:server_id] is nil" do
|
733
|
-
it "returns nil" do
|
734
|
-
expect(driver.destroy(state)).to eq(nil)
|
735
|
-
end
|
736
|
-
end
|
737
|
-
|
738
|
-
context "when state has a normal server_id" do
|
739
|
-
let(:state) { { :server_id => "id", :hostname => "name" } }
|
740
|
-
|
741
|
-
context "the server is already destroyed" do
|
742
|
-
it "does nothing" do
|
743
|
-
expect(client).to receive(:get_instance).with("id").and_return nil
|
744
|
-
driver.destroy(state)
|
745
|
-
expect(state).to eq({})
|
746
|
-
end
|
747
|
-
end
|
748
|
-
|
749
|
-
it "destroys the server" do
|
750
|
-
expect(client).to receive(:get_instance).with("id").and_return(server)
|
751
|
-
expect(instance).to receive_message_chain("transport.connection.close")
|
752
|
-
expect(server).to receive(:terminate)
|
753
|
-
driver.destroy(state)
|
754
|
-
expect(state).to eq({})
|
755
|
-
end
|
756
|
-
end
|
757
|
-
|
758
|
-
context "when state has a spot request" do
|
759
|
-
let(:state) { { :server_id => "id", :hostname => "name", :spot_request_id => "spot" } }
|
760
|
-
|
761
|
-
it "destroys the server" do
|
762
|
-
expect(client).to receive(:get_instance).with("id").and_return(server)
|
763
|
-
expect(instance).to receive_message_chain("transport.connection.close")
|
764
|
-
expect(server).to receive(:terminate)
|
765
|
-
expect(actual_client).to receive(:cancel_spot_instance_requests).with(
|
766
|
-
:spot_instance_request_ids => ["spot"]
|
767
|
-
)
|
768
|
-
driver.destroy(state)
|
769
|
-
expect(state).to eq({})
|
770
|
-
end
|
771
|
-
end
|
772
|
-
|
773
|
-
context "when the state has an automatic security group" do
|
774
|
-
let(:state) { { auto_security_group_id: "sg-asdf" } }
|
775
|
-
|
776
|
-
it "destroys the security group" do
|
777
|
-
expect(actual_client).to receive(:delete_security_group).with(group_id: "sg-asdf")
|
778
|
-
driver.destroy(state)
|
779
|
-
expect(state).to eq({})
|
780
|
-
end
|
781
|
-
end
|
782
|
-
|
783
|
-
context "when the state has an automatic key pair" do
|
784
|
-
let(:state) { { auto_key_id: "kitchen-asdf" } }
|
785
|
-
|
786
|
-
it "destroys the key pair" do
|
787
|
-
config[:kitchen_root] = "/kitchen"
|
788
|
-
allow(instance).to receive(:name).and_return("instance_name")
|
789
|
-
expect(actual_client).to receive(:delete_key_pair).with(key_name: "kitchen-asdf")
|
790
|
-
expect(File).to receive(:unlink).with("/kitchen/.kitchen/instance_name.pem")
|
791
|
-
driver.destroy(state)
|
792
|
-
expect(state).to eq({})
|
793
|
-
end
|
794
|
-
end
|
795
|
-
end
|
796
|
-
|
797
|
-
end
|