kitchen-vcair 1.0.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.
@@ -0,0 +1,25 @@
1
+ # Encoding: UTF-8
2
+ #
3
+ # Authors:: Chris McClimans (<c@vulk.co>)
4
+ # Authors:: Taylor Carpenter (<t@vulk.co>)
5
+ # Authors:: Chef Partner Engineering (<partnereng@chef.io>)
6
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
7
+ # License:: Apache License, Version 2.0
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+
21
+ module Kitchen
22
+ module Driver
23
+ VCAIR_VERSION = '1.0.0'
24
+ end
25
+ end
@@ -0,0 +1,17 @@
1
+ # Encoding: UTF-8
2
+ #
3
+ # Authors:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
@@ -0,0 +1,825 @@
1
+ # Encoding: UTF-8
2
+ #
3
+ # Authors:: Chef Partner Engineering (<partnereng@chef.io>)
4
+ # Copyright:: Copyright (c) 2015 Chef Software, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'spec_helper'
20
+ require 'excon'
21
+ require 'kitchen/driver/vcair'
22
+ require 'kitchen/provisioner/dummy'
23
+ require 'kitchen/transport/dummy'
24
+ require 'kitchen/verifier/dummy'
25
+
26
+ describe Kitchen::Driver::Vcair do
27
+ let(:logged_output) { StringIO.new }
28
+ let(:logger) { Logger.new(logged_output) }
29
+ let(:platform) { Kitchen::Platform.new(name: 'fake_platform') }
30
+ let(:transport) { Kitchen::Transport::Dummy.new }
31
+ let(:driver) { Kitchen::Driver::Vcair.new(config) }
32
+
33
+ let(:config) do
34
+ {
35
+ vcair_username: 'myuser',
36
+ vcair_password: 'mypassword',
37
+ vcair_api_host: 'https://vcloud.air',
38
+ vcair_api_path: '/api/compute/api',
39
+ vcair_org: 'myorg',
40
+ cpus: 2,
41
+ memory: 2048,
42
+ vdc_id: 1,
43
+ catalog_id: 2,
44
+ image_id: 3,
45
+ network_id: 4
46
+ }
47
+ end
48
+
49
+ let(:instance) do
50
+ instance_double(Kitchen::Instance,
51
+ logger: logger,
52
+ transport: transport,
53
+ platform: platform,
54
+ to_str: 'instance_str'
55
+ )
56
+ end
57
+
58
+ before do
59
+ allow(driver).to receive(:instance).and_return(instance)
60
+ end
61
+
62
+ describe '#create' do
63
+ context 'when the server is already created' do
64
+ let(:state) { { vapp_id: 'vapp1' } }
65
+ it 'does not call create_server' do
66
+ expect(driver).not_to receive(:create_server)
67
+ end
68
+ end
69
+
70
+ context 'when the server is not yet created' do
71
+ let(:state) { {} }
72
+ let(:vm) { double('vm') }
73
+
74
+ it 'calls the expected methods' do
75
+ allow(driver).to receive(:vm).and_return(vm)
76
+ allow(driver.vm).to receive(:ip_address).and_return('1.2.3.4')
77
+
78
+ expect(driver).to receive(:validate!)
79
+ expect(driver).to receive(:create_server).with(state)
80
+ expect(driver.vm).to receive(:wait_for)
81
+ expect(driver).to receive(:wait_for_server).with(state)
82
+
83
+ driver.create(state)
84
+ end
85
+ end
86
+ end
87
+
88
+ describe '#destroy' do
89
+ context 'when the server does not exist' do
90
+ let(:state) { {} }
91
+ it 'does not fetch the vapp' do
92
+ expect(driver).not_to receive(:vapp)
93
+
94
+ driver.destroy(state)
95
+ end
96
+ end
97
+
98
+ context 'when the server exists' do
99
+ let(:state) { { vapp_id: 'vapp1' } }
100
+ let(:vapp) { double('vapp') }
101
+ before do
102
+ allow(driver).to receive(:validate!)
103
+ allow(driver).to receive(:vapp).and_return(vapp)
104
+ allow(vapp).to receive(:power_off)
105
+ allow(vapp).to receive(:undeploy)
106
+ allow(vapp).to receive(:destroy)
107
+ end
108
+
109
+ it 'calls validate!' do
110
+ expect(driver).to receive(:validate!)
111
+ driver.destroy(state)
112
+ end
113
+
114
+ it 'sets the vapp_id' do
115
+ expect(driver).to receive(:vapp_id=).with('vapp1')
116
+ driver.destroy(state)
117
+ end
118
+
119
+ context 'when the vapp does not exist' do
120
+ it 'does not call vapp again to power_off, etc.' do
121
+ expect(driver).to receive(:vapp).once.and_raise(Fog::Compute::VcloudDirector::Forbidden)
122
+ driver.destroy(state)
123
+ end
124
+ end
125
+
126
+ it 'fetches the vapp' do
127
+ expect(driver).to receive(:vapp)
128
+ driver.destroy(state)
129
+ end
130
+
131
+ it 'gets rid of the vapp' do
132
+ expect(driver.vapp).to receive(:power_off)
133
+ expect(driver.vapp).to receive(:undeploy)
134
+ expect(driver.vapp).to receive(:destroy)
135
+
136
+ driver.destroy(state)
137
+ end
138
+ end
139
+ end
140
+
141
+ describe '#vcloud_client' do
142
+ let(:fog_server_def) { double('fog_server_def') }
143
+ let(:client) { double('client') }
144
+
145
+ before do
146
+ allow(driver).to receive(:fog_server_def).and_return(fog_server_def)
147
+ end
148
+
149
+ it 'sets up a new Fog::Compute instance' do
150
+ expect(Fog::Compute).to receive(:new).with(fog_server_def).and_return(client)
151
+ expect(driver.vcloud_client).to eq(client)
152
+ end
153
+
154
+ it 'raises an error if unauthorized' do
155
+ allow(Fog::Compute).to receive(:new).and_raise(Excon::Errors::Unauthorized, 'auth failed')
156
+ expect { driver.vcloud_client }.to raise_error(
157
+ RuntimeError,
158
+ 'Connection failure, please check your username and password. -- auth failed'
159
+ )
160
+ end
161
+ end
162
+
163
+ describe '#org' do
164
+ let(:client) { double('client') }
165
+ let(:organizations) { double('organizations') }
166
+ let(:org) { double('org') }
167
+
168
+ it 'fetches the organization by name' do
169
+ allow(driver).to receive(:vcloud_client).and_return(client)
170
+ allow(client).to receive(:organizations).and_return(organizations)
171
+ expect(organizations).to receive(:get_by_name).with('myorg').and_return(org)
172
+ expect(driver.org).to eq(org)
173
+ end
174
+ end
175
+
176
+ describe '#create_server' do
177
+ let(:state) { {} }
178
+
179
+ it 'calls the expected methods' do
180
+ expect(driver).to receive(:instantiate).and_return('vapp1')
181
+ expect(driver).to receive(:vapp_id=).with('vapp1')
182
+ expect(driver).to receive(:validate_vapp).and_return(true)
183
+ expect(driver).to receive(:update_customization)
184
+ expect(driver).to receive(:adjust_hardware)
185
+ expect(driver).to receive(:attach_network)
186
+ expect(driver).to receive(:tag_vm)
187
+ expect(driver).to receive(:power_on)
188
+
189
+ driver.create_server(state)
190
+ end
191
+
192
+ context 'when the vapp validation fails' do
193
+ before do
194
+ allow(driver).to receive(:instantiate)
195
+ allow(driver).to receive(:validate_vapp).and_return(false)
196
+ end
197
+
198
+ it 'destroys the server' do
199
+ expect(driver).to receive(:destroy).with(state)
200
+ driver.create_server(state)
201
+ end
202
+
203
+ it 'does not power on the vapp' do
204
+ expect(driver).not_to receive(:power_on)
205
+ driver.create_server(state)
206
+ end
207
+ end
208
+ end
209
+
210
+ describe '#adjust_hardware' do
211
+ let(:vm) { double('vm') }
212
+ before do
213
+ allow(driver).to receive(:vm).and_return(vm)
214
+ end
215
+
216
+ context 'when config parameters are supplied' do
217
+ it 'sets the cpus and memory on the VM' do
218
+ expect(vm).to receive(:cpu=).with(2)
219
+ expect(vm).to receive(:memory=).with(2048)
220
+
221
+ driver.adjust_hardware
222
+ end
223
+ end
224
+
225
+ context 'when config parameters are not supplied' do
226
+ before do
227
+ config[:cpus] = nil
228
+ config[:memory] = nil
229
+ end
230
+
231
+ it 'does not set the cpus and memory on the VM' do
232
+ expect(vm).not_to receive(:cpu=)
233
+ expect(vm).not_to receive(:memory=)
234
+
235
+ driver.adjust_hardware
236
+ end
237
+ end
238
+ end
239
+
240
+ describe '#attach_network' do
241
+ let(:client) { double('client') }
242
+ let(:payload) { {} }
243
+ let(:task) { double('task', body: 'body text') }
244
+ let(:vm) { double('vm', id: 'vm1') }
245
+
246
+ it 'submits the request and processes it' do
247
+ allow(driver).to receive(:vcloud_client).and_return(client)
248
+ allow(driver).to receive(:attach_network_payload).and_return(payload)
249
+ allow(driver).to receive(:vm).and_return(vm)
250
+
251
+ expect(client).to receive(:put_network_connection_system_section_vapp)
252
+ .with('vm1', payload).and_return(task)
253
+ expect(client).to receive(:process_task).with('body text')
254
+
255
+ driver.attach_network
256
+ end
257
+ end
258
+
259
+ describe '#tag_vm' do
260
+ let(:vm) { double('vm') }
261
+ let(:tags) { double('tags') }
262
+
263
+ it 'tags the VM' do
264
+ allow(driver).to receive(:vm).and_return(vm)
265
+ allow(vm).to receive(:tags).and_return(tags)
266
+ expect(tags).to receive(:create).with('created-by', 'test-kitchen')
267
+
268
+ driver.tag_vm
269
+ end
270
+ end
271
+
272
+ describe '#power_on' do
273
+ let(:vapp) { double('vapp') }
274
+
275
+ it 'powers on the vApp' do
276
+ allow(driver).to receive(:vapp).and_return(vapp)
277
+ expect(vapp).to receive(:power_on)
278
+
279
+ driver.power_on
280
+ end
281
+ end
282
+
283
+ describe '#wait_for_server' do
284
+ let(:connection) { instance.transport.connection(state) }
285
+ let(:state) { {} }
286
+ let(:vapp) { double('vapp', id: 'vapp1') }
287
+ let(:vm) { double('vm', name: 'vm1') }
288
+
289
+ before do
290
+ allow(transport).to receive(:connection).and_return(connection)
291
+ allow(driver).to receive(:vapp).and_return(vapp)
292
+ allow(driver).to receive(:vm).and_return(vm)
293
+ end
294
+
295
+ it 'calls wait_until_ready on the transport connection' do
296
+ expect(connection).to receive(:wait_until_ready)
297
+ driver.wait_for_server(state)
298
+ end
299
+
300
+ it 'destroys the vApp if the server failed to become ready' do
301
+ allow(connection).to receive(:wait_until_ready).and_raise(RuntimeError)
302
+ expect(driver).to receive(:destroy).with(state)
303
+ expect { driver.wait_for_server(state) }.to raise_error(RuntimeError)
304
+ end
305
+ end
306
+
307
+ describe '#vcloud_username' do
308
+ it 'returns a properly-formatted username' do
309
+ expect(driver.vcloud_username).to eq('myuser@myorg')
310
+ end
311
+ end
312
+
313
+ describe '#image' do
314
+ let(:catalog) { double('catalog') }
315
+ let(:catalog_items) { double('catalog_items') }
316
+
317
+ before do
318
+ allow(driver).to receive(:catalog).and_return(catalog)
319
+ allow(catalog).to receive(:catalog_items).and_return(catalog_items)
320
+ end
321
+
322
+ context 'when an ID is provided' do
323
+ before do
324
+ config[:image_id] = 1
325
+ config[:image_name] = nil
326
+ end
327
+
328
+ it 'fetches the catalog item by ID' do
329
+ expect(catalog_items).to receive(:get).with(1)
330
+ driver.image
331
+ end
332
+ end
333
+
334
+ context 'when a name is provided' do
335
+ before do
336
+ config[:image_id] = nil
337
+ config[:image_name] = 'image name'
338
+ end
339
+
340
+ it 'fetches the catalog item by name' do
341
+ expect(catalog_items).to receive(:get_by_name).with('image name')
342
+ driver.image
343
+ end
344
+ end
345
+ end
346
+
347
+ describe '#catalog' do
348
+ let(:org) { double('org') }
349
+ let(:catalogs) { double('catalogs') }
350
+
351
+ before do
352
+ allow(driver).to receive(:org).and_return(org)
353
+ allow(org).to receive(:catalogs).and_return(catalogs)
354
+ end
355
+
356
+ context 'when an ID is provided' do
357
+ before do
358
+ config[:catalog_id] = 1
359
+ config[:catalog_name] = nil
360
+ end
361
+
362
+ it 'fetches the catalog by ID' do
363
+ expect(catalogs).to receive(:get).with(1)
364
+ driver.catalog
365
+ end
366
+ end
367
+
368
+ context 'when a name is provided' do
369
+ before do
370
+ config[:catalog_id] = nil
371
+ config[:catalog_name] = 'catalog name'
372
+ end
373
+
374
+ it 'fetches the catalog by name' do
375
+ expect(catalogs).to receive(:get_by_name).with('catalog name')
376
+ driver.catalog
377
+ end
378
+ end
379
+ end
380
+
381
+ describe '#vdc' do
382
+ let(:org) { double('org') }
383
+ let(:vdcs) { double('vdcs') }
384
+
385
+ before do
386
+ allow(driver).to receive(:org).and_return(org)
387
+ allow(org).to receive(:vdcs).and_return(vdcs)
388
+ end
389
+
390
+ context 'when an ID is provided' do
391
+ before do
392
+ config[:vdc_id] = 1
393
+ config[:vdc_name] = nil
394
+ end
395
+
396
+ it 'fetches the vdc by ID' do
397
+ expect(vdcs).to receive(:get).with(1)
398
+ driver.vdc
399
+ end
400
+ end
401
+
402
+ context 'when a name is provided' do
403
+ before do
404
+ config[:vdc_id] = nil
405
+ config[:vdc_name] = 'vdc name'
406
+ end
407
+
408
+ it 'fetches the vdc by name' do
409
+ expect(vdcs).to receive(:get_by_name).with('vdc name')
410
+ driver.vdc
411
+ end
412
+ end
413
+ end
414
+
415
+ describe '#network' do
416
+ let(:org) { double('org') }
417
+ let(:networks) { double('networks') }
418
+
419
+ before do
420
+ allow(driver).to receive(:org).and_return(org)
421
+ allow(org).to receive(:networks).and_return(networks)
422
+ end
423
+
424
+ context 'when an ID is provided' do
425
+ before do
426
+ config[:network_id] = 1
427
+ config[:network_name] = nil
428
+ end
429
+
430
+ it 'fetches the network by ID' do
431
+ expect(networks).to receive(:get).with(1)
432
+ driver.network
433
+ end
434
+ end
435
+
436
+ context 'when a name is provided' do
437
+ before do
438
+ config[:network_id] = nil
439
+ config[:network_name] = 'network name'
440
+ end
441
+
442
+ it 'fetches the network by name' do
443
+ expect(networks).to receive(:get_by_name).with('network name')
444
+ driver.network
445
+ end
446
+ end
447
+ end
448
+
449
+ describe '#node_description' do
450
+ context 'when a node description is provided' do
451
+ before do
452
+ config[:node_description] = 'sample description'
453
+ end
454
+
455
+ it 'returns the configured description' do
456
+ expect(driver.node_description).to eq('sample description')
457
+ end
458
+ end
459
+
460
+ context 'when a node description is not provided' do
461
+ it 'returns the default description' do
462
+ allow(driver).to receive(:node_name).and_return('node')
463
+ expect(driver.node_description).to eq('Test Kitchen: node')
464
+ end
465
+ end
466
+ end
467
+
468
+ describe '#node_name' do
469
+ context 'when a node name is provided' do
470
+ before do
471
+ config[:node_name] = 'testnode'
472
+ end
473
+
474
+ it 'returns the configured node name' do
475
+ expect(driver.node_name).to eq('testnode')
476
+ end
477
+ end
478
+
479
+ context 'when a node name is not provided' do
480
+ it 'returns a generated node name' do
481
+ expect(driver).to receive(:generate_node_name).and_return('a12345')
482
+ expect(driver.node_name).to eq('a12345')
483
+ end
484
+ end
485
+ end
486
+
487
+ describe '#generate_node_name' do
488
+ it 'generates a node name using SecureRandom' do
489
+ expect(SecureRandom).to receive(:hex).with(6).and_return('randomchars')
490
+ expect(driver.generate_node_name).to eq('tk-randomchars')
491
+ end
492
+ end
493
+
494
+ describe '#print_error_and_exit' do
495
+ it 'prints an error message and raises an exception' do
496
+ expect(driver).to receive(:error).with('error text')
497
+ expect { driver.print_error_and_exit('error text') }.to raise_error(RuntimeError)
498
+ end
499
+ end
500
+
501
+ describe '#validate!' do
502
+ it 'calls all the expected validate methods' do
503
+ expect(driver).to receive(:validate_parameter_pair!).with('vdc')
504
+ expect(driver).to receive(:validate_parameter_pair!).with('catalog')
505
+ expect(driver).to receive(:validate_parameter_pair!).with('image')
506
+ expect(driver).to receive(:validate_parameter_pair!).with('network')
507
+
508
+ expect(driver).to receive(:validate_method!).with(:org)
509
+ expect(driver).to receive(:validate_method!).with(:vdc)
510
+ expect(driver).to receive(:validate_method!).with(:catalog)
511
+ expect(driver).to receive(:validate_method!).with(:image)
512
+ expect(driver).to receive(:validate_method!).with(:network)
513
+
514
+ expect(driver).to receive(:validate_customization_script!)
515
+ expect(driver).to receive(:validate_computer_name!)
516
+
517
+ driver.validate!
518
+ end
519
+ end
520
+
521
+ describe '#validate_parameter_pair!' do
522
+ context 'when an ID exists but not a name' do
523
+ before do
524
+ config[:test_id] = 1
525
+ config[:test_name] = nil
526
+ end
527
+
528
+ it 'does not print an error' do
529
+ expect(driver).not_to receive(:print_error_and_exit)
530
+ driver.validate_parameter_pair!('test')
531
+ end
532
+ end
533
+
534
+ context 'when a name exists but not an ID' do
535
+ before do
536
+ config[:test_id] = nil
537
+ config[:test_name] = 'test'
538
+ end
539
+
540
+ it 'does not print an error' do
541
+ expect(driver).not_to receive(:print_error_and_exit)
542
+ driver.validate_parameter_pair!('test')
543
+ end
544
+ end
545
+
546
+ context 'when neither a name nor an ID exists' do
547
+ before do
548
+ config[:test_id] = nil
549
+ config[:test_name] = nil
550
+ end
551
+
552
+ it 'prints an error' do
553
+ expect(driver).to receive(:print_error_and_exit)
554
+ driver.validate_parameter_pair!('test')
555
+ end
556
+ end
557
+ end
558
+
559
+ describe '#validate_method!' do
560
+ context 'when the method is successful' do
561
+ it 'does not raise an exception' do
562
+ allow(driver).to receive(:test_method)
563
+ expect { driver.validate_method!(:test_method) }.not_to raise_error
564
+ end
565
+ end
566
+
567
+ context 'when the method is not successful' do
568
+ it 'raises an exception' do
569
+ allow(driver).to receive(:test_method).and_raise(RuntimeError)
570
+ expect { driver.validate_method!(:test_method) }.to raise_error(RuntimeError)
571
+ end
572
+ end
573
+ end
574
+
575
+ describe '#validate_computer_name' do
576
+ it 'allows an alphanumeric 15-char string' do
577
+ allow(driver).to receive(:node_name).and_return('a12345678901234')
578
+ expect(driver).not_to receive(:print_error_and_exit)
579
+
580
+ driver.validate_computer_name!
581
+ end
582
+
583
+ it 'does not allow a computer name that only has numbers' do
584
+ allow(driver).to receive(:node_name).and_return('12345')
585
+ expect(driver).to receive(:print_error_and_exit)
586
+
587
+ driver.validate_computer_name!
588
+ end
589
+
590
+ it 'does not allow a 16 character name' do
591
+ allow(driver).to receive(:node_name).and_return('a123456789012345')
592
+ expect(driver).to receive(:print_error_and_exit)
593
+
594
+ driver.validate_computer_name!
595
+ end
596
+
597
+ it 'does not allow a hyphen at the end' do
598
+ allow(driver).to receive(:node_name).and_return('a12345-')
599
+ expect(driver).to receive(:print_error_and_exit)
600
+
601
+ driver.validate_computer_name!
602
+ end
603
+ end
604
+
605
+ describe '#validate_customization_script!' do
606
+ context 'when no customization script has been configured' do
607
+ before do
608
+ config[:customization_script] = nil
609
+ end
610
+
611
+ it 'does not print an error' do
612
+ expect(driver).not_to receive(:print_error_and_exit)
613
+
614
+ driver.validate_customization_script!
615
+ end
616
+ end
617
+
618
+ context 'when a script is configured and is readable' do
619
+ before do
620
+ config[:customization_script] = '/path/to/script'
621
+ end
622
+
623
+ it 'does not print an error' do
624
+ expect(File).to receive(:readable?).with('/path/to/script').and_return(true)
625
+ expect(driver).not_to receive(:print_error_and_exit)
626
+
627
+ driver.validate_customization_script!
628
+ end
629
+ end
630
+
631
+ context 'when a script is configured but is not readable' do
632
+ before do
633
+ config[:customization_script] = '/path/to/script'
634
+ end
635
+
636
+ it 'does not print an error' do
637
+ expect(File).to receive(:readable?).with('/path/to/script').and_return(false)
638
+ expect(driver).to receive(:print_error_and_exit)
639
+
640
+ driver.validate_customization_script!
641
+ end
642
+ end
643
+ end
644
+
645
+ describe '#instantiate' do
646
+ let(:image) { double('image') }
647
+
648
+ it 'calls instantiate on the image' do
649
+ allow(driver).to receive(:image).and_return(image)
650
+ allow(driver).to receive(:node_name).and_return('node')
651
+ allow(driver).to receive(:instantiate_config).and_return('config')
652
+ expect(image).to receive(:instantiate).with('node', 'config')
653
+
654
+ driver.instantiate
655
+ end
656
+ end
657
+
658
+ describe '#vapp' do
659
+ let(:vdc) { double('vdc') }
660
+ let(:vapps) { double('vapps') }
661
+ let(:vapp) { double('vapp') }
662
+
663
+ it 'gets the vApp by ID' do
664
+ allow(driver).to receive(:vapp_id).and_return('vapp1')
665
+ allow(driver).to receive(:vdc).and_return(vdc)
666
+ allow(vdc).to receive(:vapps).and_return(vapps)
667
+ expect(vapps).to receive(:get).with('vapp1').and_return(vapp)
668
+ expect(driver.vapp).to eq(vapp)
669
+ end
670
+ end
671
+
672
+ describe '#vm' do
673
+ let(:vapp) { double('vapp') }
674
+ let(:vms) { %w(vm1 vm2) }
675
+
676
+ it 'returns the first VM from the array' do
677
+ allow(driver).to receive(:vapp).and_return(vapp)
678
+ allow(vapp).to receive(:vms).and_return(vms)
679
+
680
+ expect(driver.vm).to eq('vm1')
681
+ end
682
+ end
683
+
684
+ describe '#validate_vapp' do
685
+ let(:vapp) { double('vapp') }
686
+ before do
687
+ allow(driver).to receive(:vapp).and_return(vapp)
688
+ end
689
+
690
+ it 'returns true when only 1 VM is present' do
691
+ allow(vapp).to receive(:vms).and_return([1])
692
+ expect(driver.validate_vapp).to eq(true)
693
+ end
694
+
695
+ it 'returns false when 0 VMs are present' do
696
+ allow(vapp).to receive(:vms).and_return([])
697
+ expect(driver.validate_vapp).to eq(false)
698
+ end
699
+
700
+ it 'returns false when >1 VMs are present' do
701
+ allow(vapp).to receive(:vms).and_return([1, 2])
702
+ expect(driver.validate_vapp).to eq(false)
703
+ end
704
+ end
705
+
706
+ describe '#customization' do
707
+ let(:vm) { double('vm') }
708
+ let(:customization) { double('customization') }
709
+ it 'fetches the customization from the VM' do
710
+ allow(driver).to receive(:vm).and_return(vm)
711
+ expect(vm).to receive(:customization).and_return(customization)
712
+ expect(driver.customization).to eq(customization)
713
+ end
714
+ end
715
+
716
+ describe '#update_customization' do
717
+ before do
718
+ allow(driver).to receive(:set_customization_script)
719
+ allow(driver).to receive(:set_customization_password)
720
+ allow(driver).to receive(:set_customization_computer_name)
721
+ allow(driver).to receive(:save_customization)
722
+ end
723
+
724
+ context 'when a customization script is provided' do
725
+ before do
726
+ config[:customization_script] = '/path/to/script'
727
+ end
728
+
729
+ it 'calls set_customization_script' do
730
+ expect(driver).to receive(:set_customization_script)
731
+ driver.update_customization
732
+ end
733
+ end
734
+
735
+ context 'when a customization script is not provided' do
736
+ before do
737
+ config[:customization_script] = nil
738
+ end
739
+
740
+ it 'does not call set_customization_script' do
741
+ expect(driver).not_to receive(:set_customization_script)
742
+ driver.update_customization
743
+ end
744
+ end
745
+
746
+ it 'calls the expected methods' do
747
+ expect(driver).to receive(:set_customization_password)
748
+ expect(driver).to receive(:set_customization_computer_name)
749
+ expect(driver).to receive(:save_customization)
750
+ driver.update_customization
751
+ end
752
+ end
753
+
754
+ describe '#set_customization_script' do
755
+ let(:customization) { double('customization') }
756
+
757
+ before do
758
+ config[:customization_script] = '/path/to/script'
759
+ allow(driver).to receive(:customization).and_return(customization)
760
+ allow(File).to receive(:read).with('/path/to/script').and_return('script body')
761
+ end
762
+
763
+ it 'sets the customization script to the file contents' do
764
+ expect(customization).to receive(:script=).with('script body')
765
+ driver.set_customization_script
766
+ end
767
+ end
768
+
769
+ describe '#set_customization_password' do
770
+ let(:customization) { double('customization') }
771
+ before do
772
+ allow(driver).to receive(:customization).and_return(customization)
773
+ end
774
+
775
+ context 'when a VM password is provided' do
776
+ before do
777
+ config[:vm_password] = 'password123'
778
+ end
779
+
780
+ it 'sets the password and disables auto-generation and reset' do
781
+ expect(customization).to receive(:admin_password=).with('password123')
782
+ expect(customization).to receive(:admin_password_auto=).with(false)
783
+ expect(customization).to receive(:reset_password_required=).with(false)
784
+
785
+ driver.set_customization_password
786
+ end
787
+ end
788
+
789
+ context 'when a VM password is not provided' do
790
+ before do
791
+ config[:vm_password] = nil
792
+ end
793
+
794
+ it 'sets nulls-out the password, enables auto-generation, disables reset reset' do
795
+ expect(customization).to receive(:admin_password=).with(nil)
796
+ expect(customization).to receive(:admin_password_auto=).with(true)
797
+ expect(customization).to receive(:reset_password_required=).with(false)
798
+
799
+ driver.set_customization_password
800
+ end
801
+ end
802
+ end
803
+
804
+ describe '#set_customization_computer_name' do
805
+ let(:customization) { double('customization') }
806
+ it 'sets the computer name' do
807
+ allow(driver).to receive(:customization).and_return(customization)
808
+ allow(driver).to receive(:node_name).and_return('test node')
809
+ expect(customization).to receive(:computer_name=).with('test node')
810
+
811
+ driver.set_customization_computer_name
812
+ end
813
+ end
814
+
815
+ describe '#save_customization' do
816
+ let(:customization) { double('customization') }
817
+ it 'enables and saves the customization' do
818
+ allow(driver).to receive(:customization).and_return(customization)
819
+ expect(customization).to receive(:enabled=).with(true)
820
+ expect(customization).to receive(:save)
821
+
822
+ driver.save_customization
823
+ end
824
+ end
825
+ end