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,57 @@
1
+ # kitchen-vcair demonstration videos
2
+
3
+ Below are videos created by the original plugin author that demonstrate
4
+ kitchen-vcair and how to use it. Some configuration parameters have changed
5
+ since the initial development of this plugin to keep is consistent with other
6
+ VMware-based plugins, so be sure to check the README.md in this repo for the
7
+ latest instructions.
8
+
9
+ ## Linux
10
+
11
+ * [github.com/vulk/kitchen-vcair](https://www.youtube.com/watch?v=5srDko69XJ0&t=03)
12
+ * [vchs.vmware.com](https://www.youtube.com/watch?v=5srDko69XJ0&t=15)
13
+ * [Walkthrough steps for cloning, building gem](https://www.youtube.com/watch?v=5srDko69XJ0&t=30)
14
+ * [git clone git@github.com:/vulk/kitchen-vcair.git](https://www.youtube.com/watch?v=5srDko69XJ0&t=68)
15
+ * [cd kitchen-vcair](https://www.youtube.com/watch?v=5srDko69XJ0&t=94)
16
+ * [gem build kitchen-vcair.gemspec](https://www.youtube.com/watch?v=5srDko69XJ0&t=100)
17
+ * [gem install ./kitchen-vcair-0.1.0.gem](https://www.youtube.com/watch?v=5srDko69XJ0&t=120)
18
+ * [quick look through code ](https://www.youtube.com/watch?v=5srDko69XJ0&t=126)
19
+ * [git clone git@github.com:chef-cookbooks/httpd.git ](https://www.youtube.com/watch?v=5srDko69XJ0&t=173)
20
+ * [walkthrough of .kitchen.vcair.yml](https://www.youtube.com/watch?v=5srDko69XJ0&t=199)
21
+ * [walkthrough of environment variables](https://www.youtube.com/watch?v=5srDko69XJ0&t=247)
22
+ * [kitchen test](https://www.youtube.com/watch?v=5srDko69XJ0&t=282)
23
+ * [vchs.vmware.com virtualmachine list, showing creation of helloworldtest VM](https://www.youtube.com/watch?v=5srDko69XJ0&t=296)
24
+ * [knife vcair server list showing creation of helloworld test VM](https://www.youtube.com/watch?v=5srDko69XJ0&t=326)
25
+ * [instance provisionied, waiting for ssh](https://www.youtube.com/watch?v=5srDko69XJ0&t=355)
26
+ * [ssh available, installing chef-client](https://www.youtube.com/watch?v=5srDko69XJ0&t=400)
27
+ * [chef-client starting](https://www.youtube.com/watch?v=5srDko69XJ0&t=499)
28
+ * [chef-client finished, apache install completed](https://www.youtube.com/watch?v=5srDko69XJ0&t=515)
29
+ * [Kitchen Setup and Verify](https://www.youtube.com/watch?v=5srDko69XJ0&t=516)
30
+ * [Kitichen Destroy](https://www.youtube.com/watch?v=5srDko69XJ0&t=517)
31
+ * [Kitchen is finished](https://www.youtube.com/watch?v=5srDko69XJ0&t=525)
32
+ * [vchs.vmware.com and knife vcair shows vm destroyed](https://www.youtube.com/watch?v=5srDko69XJ0&t=530)
33
+
34
+ ## Windows
35
+
36
+ * [vmwair-vcair.env.example](https://www.youtube.com/watch?v=k8OZII4UGZs&t=09)
37
+ * [.kitchen.vcair.yml](https://www.youtube.com/watch?v=k8OZII4UGZs&t=20)
38
+ * [.yml / platforms:customization_script note](https://www.youtube.com/watch?v=k8OZII4UGZs&t=30)
39
+ * [customization_script install-winrm-vcair.bat](https://www.youtube.com/watch?v=k8OZII4UGZs&t=37)
40
+ * [git clone opscode-cookbooks/iis](https://www.youtube.com/watch?v=k8OZII4UGZs&t=54)
41
+ * [start coping files into iis cookbooks](https://www.youtube.com/watch?v=k8OZII4UGZs&t=60)
42
+ * [Add kitchen-vcair and kitchen-pester to the Gemfile](https://www.youtube.com/watch?v=k8OZII4UGZs&t=98)
43
+ * [bundle install kitchen vcair and pester](https://www.youtube.com/watch?v=k8OZII4UGZs&t=120)
44
+ * [KITCHEN_YAML=.kitchen.vcair.yml bundle exec kitchen verify](https://www.youtube.com/watch?v=k8OZII4UGZs&t=150)
45
+ * [Server is allocated.](https://www.youtube.com/watch?v=k8OZII4UGZs&t=270)
46
+ * ['pre'/'post' customization script ](https://www.youtube.com/watch?v=k8OZII4UGZs&t=300)
47
+ * ['pre' customization reboot ](https://www.youtube.com/watch?v=k8OZII4UGZs&t=412)
48
+ * ['post' customization boot ](https://www.youtube.com/watch?v=k8OZII4UGZs&t=440)
49
+ * [winrm is online](https://www.youtube.com/watch?v=k8OZII4UGZs&t=555)
50
+ * [installing chef omnibus](https://www.youtube.com/watch?v=k8OZII4UGZs&t=560)
51
+ * [chef-client starts](https://www.youtube.com/watch?v=k8OZII4UGZs&t=600)
52
+ * [iis:default recipe runs](https://www.youtube.com/watch?v=k8OZII4UGZs&t=630)
53
+ * [verification via kitche-pester](https://www.youtube.com/watch?v=k8OZII4UGZs&t=647)
54
+ * [kitchen verify complete!](https://www.youtube.com/watch?v=k8OZII4UGZs&t=660)
55
+ * [iis default web page via links](https://www.youtube.com/watch?v=k8OZII4UGZs&t=695)
56
+ * [kitchen verify again](https://www.youtube.com/watch?v=k8OZII4UGZs&t=710)
57
+ * [kitchen destroy](https://www.youtube.com/watch?v=k8OZII4UGZs&t=735)
@@ -0,0 +1,53 @@
1
+ @echo off
2
+
3
+ @rem First Boot...
4
+ if “%1%” == “precustomization” (
5
+
6
+ echo Do precustomization tasks
7
+ @rem during this boot the hostname is set, which requires a reboot
8
+
9
+ @rem we also enable winrm over http, plaintext, long timeout, more memory etc
10
+
11
+ cmd.exe /c winrm quickconfig -q
12
+ cmd.exe /c winrm quickconfig -transport:http
13
+ cmd.exe /c winrm set winrm/config @{MaxTimeoutms="1800000"}
14
+ cmd.exe /c winrm set winrm/config/winrs @{MaxMemoryPerShellMB="300"}
15
+ cmd.exe /c winrm set winrm/config/service @{AllowUnencrypted="true"}
16
+ cmd.exe /c winrm set winrm/config/service/auth @{Basic="true"}
17
+ cmd.exe /c winrm set winrm/config/client/auth @{Basic="true"}
18
+ cmd.exe /c winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"}
19
+
20
+ @rem Make sure winrm is off for this boot, but enabled on next
21
+ @rem as we don't want a tcp connection available until we are
22
+ @rem past postcustomization
23
+
24
+ cmd.exe /c net stop winrm
25
+ cmd.exe /c sc config winrm start= auto
26
+
27
+ @rem make sure the default on password age is unlimited
28
+ @rem this ensures we don't have a password change forced on us
29
+ cmd.exe /c net accounts /maxpwage:unlimited
30
+
31
+ @rem write out a timestamp for this first boot / customization completes
32
+ echo %DATE% %TIME% > C:\vm-is-customized
33
+
34
+ ) else if “%1%” == “postcustomization” (
35
+
36
+ @rem Second Boot / start winrm, just incase, and fix firewall
37
+
38
+ cmd.exe /c net start winrm
39
+ cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes
40
+ cmd.exe /c netsh firewall add portopening TCP 5985 "Port 5985 for WinRM"
41
+
42
+ @rem Password Setting and Autologin currently seem broken
43
+ @rem when done via the API, so we MUST set it in the postcustomization phase
44
+ cmd.exe /c net user administrator Password1
45
+
46
+ @rem in some environments we found the need to specify a DNS address
47
+ @rem cmd.exe /c netsh interface ipv4 add dnsserver "Ethernet" address=8.8.8.8
48
+ @rem cmd.exe /c netsh interface ipv4 add dnsserver "Ethernet0" address=8.8.8.8
49
+
50
+ @rem this is our 'ready' boot, password and winrm should be up
51
+ echo %DATE% %TIME% > C:\vm-is-ready
52
+
53
+ )
@@ -0,0 +1,32 @@
1
+ # Encoding: UTF-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'kitchen/driver/vcair_version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'kitchen-vcair'
9
+ spec.version = Kitchen::Driver::VCAIR_VERSION
10
+ spec.authors = ['Chef Partner Engineering', 'Taylor Carpenter', 'Chris McClimans']
11
+ spec.email = %w(partnereng@chef.io wolfpack+c+t@vulk.co)
12
+ spec.description = 'A Test Kitchen vCloud Air driver'
13
+ spec.summary = 'A Test Kitchen vCloud Air driver built on Fog'
14
+ spec.homepage = 'https://github.com/chef-partners/kitchen-vcair'
15
+ spec.license = 'Apache 2.0'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = %w(lib)
21
+
22
+ spec.required_ruby_version = '>= 2.0.0'
23
+
24
+ spec.add_dependency 'test-kitchen', '~> 1.4', '>= 1.4.1'
25
+ spec.add_dependency 'fog', '~> 1.33'
26
+
27
+ spec.add_development_dependency 'bundler', '~> 1.7'
28
+ spec.add_development_dependency 'rake', '~> 10.0'
29
+ spec.add_development_dependency 'rspec', '~> 3.2'
30
+ spec.add_development_dependency 'rubocop', '~> 0.33'
31
+ spec.add_development_dependency 'pry', '~> 0.10'
32
+ end
@@ -0,0 +1,359 @@
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
+ require 'fog'
22
+ require 'kitchen'
23
+ require 'securerandom'
24
+
25
+ module Kitchen
26
+ module Driver
27
+ class Vcair < Kitchen::Driver::Base
28
+ attr_accessor :vapp_id
29
+
30
+ default_config :wait_for, 600
31
+ default_config :vcair_api_path, '/api'
32
+ default_config :catalog_id, nil
33
+ default_config :catalog_name, nil
34
+ default_config :image_id, nil
35
+ default_config :image_name, nil
36
+ default_config :vdc_id, nil
37
+ default_config :vdc_name, nil
38
+ default_config :network_id, nil
39
+ default_config :network_name, nil
40
+ default_config :cpus, 1
41
+ default_config :memory, 1024
42
+ default_config :vm_password
43
+
44
+ required_config :vcair_username
45
+ required_config :vcair_password
46
+ required_config :vcair_api_host
47
+ required_config :vcair_org
48
+
49
+ def initialize(config)
50
+ super
51
+ Fog.timeout = config[:wait_for].to_i
52
+ end
53
+
54
+ def name
55
+ 'vCloudAir'
56
+ end
57
+
58
+ def create(state)
59
+ return unless state[:vapp_id].nil?
60
+
61
+ validate!
62
+
63
+ create_server(state)
64
+ vm.wait_for { ready? }
65
+ state[:hostname] = vm.ip_address
66
+
67
+ info("Server #{state[:hostname]} is powered on. Waiting for it to be ready...")
68
+ wait_for_server(state)
69
+ end
70
+
71
+ def destroy(state)
72
+ return if state[:vapp_id].nil?
73
+
74
+ validate!
75
+
76
+ self.vapp_id = state[:vapp_id]
77
+
78
+ info("Destroying vApp #{vapp_id}...")
79
+ begin
80
+ vapp
81
+ rescue Fog::Compute::VcloudDirector::Forbidden
82
+ warn("Unable to locate vApp <#{state[:vapp_id]}> - assuming it is already destroyed.")
83
+ return
84
+ end
85
+
86
+ info('Powering off the vApp...')
87
+ vapp.power_off
88
+
89
+ info('Undeploying the vApp...')
90
+ vapp.undeploy
91
+
92
+ info('Deleting the vApp...')
93
+ vapp.destroy
94
+
95
+ info("vApp <#{state[:vapp_id]}> destroyed.")
96
+ end
97
+
98
+ def vcloud_client
99
+ @vcloud_client ||= Fog::Compute.new(fog_server_def)
100
+ rescue Excon::Errors::Unauthorized => e
101
+ raise "Connection failure, please check your username and password. -- #{e.message}"
102
+ end
103
+
104
+ def org
105
+ @org ||= vcloud_client.organizations.get_by_name(config[:vcair_org])
106
+ end
107
+
108
+ def create_server(state)
109
+ self.vapp_id = instantiate
110
+ state[:vapp_id] = vapp_id
111
+
112
+ info("vApp ID #{vapp_id} created.")
113
+
114
+ info('Validating the vApp...')
115
+ unless validate_vapp
116
+ destroy(state)
117
+ return
118
+ end
119
+
120
+ info('Updating the VM customization...')
121
+ update_customization
122
+
123
+ info('Adjusting VM hardware...')
124
+ adjust_hardware
125
+
126
+ info('Attaching it to the network...')
127
+ attach_network
128
+
129
+ info('Tagging the VM...')
130
+ tag_vm
131
+
132
+ info('Powering on the VM...')
133
+ power_on
134
+ end
135
+
136
+ def adjust_hardware
137
+ vm.cpu = config[:cpus] if config[:cpus]
138
+ vm.memory = config[:memory] if config[:memory]
139
+ end
140
+
141
+ def attach_network_payload
142
+ {
143
+ PrimaryNetworkConnectionIndex: 0,
144
+ NetworkConnection: [
145
+ {
146
+ network: network.name,
147
+ needsCustomization: true,
148
+ NetworkConnectionIndex: 0,
149
+ IsConnected: true,
150
+ IpAddressAllocationMode: 'POOL'
151
+ }
152
+ ]
153
+ }
154
+ end
155
+
156
+ def attach_network
157
+ task = vcloud_client.put_network_connection_system_section_vapp(vm.id, attach_network_payload)
158
+ vcloud_client.process_task(task.body)
159
+ end
160
+
161
+ def tag_vm
162
+ vm.tags.create('created-by', 'test-kitchen')
163
+ end
164
+
165
+ def power_on
166
+ vapp.power_on
167
+ end
168
+
169
+ def wait_for_server(state)
170
+ instance.transport.connection(state).wait_until_ready
171
+ rescue
172
+ error("Server #{vapp.id} (#{vm.name}) not reachable. Destroying server...")
173
+ destroy(state)
174
+ raise
175
+ end
176
+
177
+ def vcloud_username
178
+ [ config[:vcair_username], config[:vcair_org] ].join('@')
179
+ end
180
+
181
+ def fog_server_def
182
+ {
183
+ provider: 'vclouddirector',
184
+ vcloud_director_username: vcloud_username,
185
+ vcloud_director_password: config[:vcair_password],
186
+ vcloud_director_host: config[:vcair_api_host],
187
+ vcloud_director_api_version: config[:vcair_api_version],
188
+ vcloud_director_show_progress: false,
189
+ path: config[:vcair_api_path]
190
+ }
191
+ end
192
+
193
+ def image
194
+ if config[:image_id]
195
+ catalog.catalog_items.get(config[:image_id])
196
+ else
197
+ catalog.catalog_items.get_by_name(config[:image_name])
198
+ end
199
+ end
200
+
201
+ def catalog
202
+ if config[:catalog_id]
203
+ org.catalogs.get(config[:catalog_id])
204
+ else
205
+ org.catalogs.get_by_name(config[:catalog_name])
206
+ end
207
+ end
208
+
209
+ def vdc
210
+ if config[:vdc_id]
211
+ org.vdcs.get(config[:vdc_id])
212
+ else
213
+ org.vdcs.get_by_name(config[:vdc_name])
214
+ end
215
+ end
216
+
217
+ def network
218
+ if config[:network_id]
219
+ org.networks.get(config[:network_id])
220
+ else
221
+ org.networks.get_by_name(config[:network_name])
222
+ end
223
+ end
224
+
225
+ def node_description
226
+ config[:node_description] || "Test Kitchen: #{node_name}"
227
+ end
228
+
229
+ def node_name
230
+ config[:node_name] || generate_node_name
231
+ end
232
+
233
+ def generate_node_name
234
+ # SecureRandom.hex generates a string 2x the argument.
235
+ # We need the name to be 15 chars or less to play nicely
236
+ # with windows, so we're generating a 12-char random
237
+ # string prefixed with "tk-"
238
+ 'tk-' + SecureRandom.hex(6)
239
+ end
240
+
241
+ def instantiate_config
242
+ {
243
+ vdc_id: vdc.id,
244
+ network_id: network.id,
245
+ description: node_description
246
+ }
247
+ end
248
+
249
+ def print_error_and_exit(message)
250
+ error(message)
251
+ fail message
252
+ end
253
+
254
+ def validate!
255
+ %w(vdc catalog image network).each do |param|
256
+ validate_parameter_pair!(param)
257
+ end
258
+
259
+ [ :org, :vdc, :catalog, :image, :network].each do |method|
260
+ validate_method!(method)
261
+ end
262
+
263
+ validate_customization_script!
264
+ validate_computer_name!
265
+ end
266
+
267
+ def validate_parameter_pair!(param)
268
+ id_key = param + '_id'
269
+ name_key = param + '_name'
270
+
271
+ print_error_and_exit("No #{param} found. You must specify #{id_key} or #{name_key}.") if
272
+ config[id_key.to_sym].nil? && config[name_key.to_sym].nil?
273
+ end
274
+
275
+ def validate_method!(method)
276
+ send(method)
277
+ rescue => e
278
+ raise "Unable to validate #{method} - check your configuration and try again. #{e.class} -- #{e.message}"
279
+ end
280
+
281
+ def validate_computer_name!
282
+ # regex proudly modified after stealing from:
283
+ # http://stackoverflow.com/questions/2063213/regular-expression-for-validating-dns-label-host-name
284
+ print_error_and_exit('Node name is not valid - must be 15 characters or less, and be a valid Windows node name') unless
285
+ node_name =~ /^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{,15}(?<!-)$/
286
+ end
287
+
288
+ def validate_customization_script!
289
+ return unless config[:customization_script]
290
+
291
+ print_error_and_exit("Customization script #{config[:customization_script]} is not found or not readable.") unless
292
+ ::File.readable?(config[:customization_script])
293
+ end
294
+
295
+ def instantiate
296
+ image.instantiate(node_name, instantiate_config)
297
+ end
298
+
299
+ def vapp
300
+ @vapp ||= vdc.vapps.get(vapp_id)
301
+ end
302
+
303
+ def vm
304
+ @vm ||= vapp.vms.first
305
+ end
306
+
307
+ def validate_vapp
308
+ vms = vapp.vms
309
+ if vms.empty?
310
+ error('vApp created, but did not contain any VMs')
311
+ return false
312
+ end
313
+
314
+ if vms.size > 1
315
+ error('vApp created, but contained more than one VM')
316
+ return false
317
+ end
318
+
319
+ true
320
+ end
321
+
322
+ def customization
323
+ @customization ||= vm.customization
324
+ end
325
+
326
+ def update_customization
327
+ set_customization_script if config[:customization_script]
328
+ set_customization_password
329
+ set_customization_computer_name
330
+ save_customization
331
+ end
332
+
333
+ def set_customization_script
334
+ customization.script = ::File.read(config[:customization_script])
335
+ end
336
+
337
+ def set_customization_password
338
+ if config[:vm_password]
339
+ customization.admin_password = config[:vm_password]
340
+ customization.admin_password_auto = false
341
+ customization.reset_password_required = false
342
+ else
343
+ customization.admin_password = nil
344
+ customization.admin_password_auto = true
345
+ customization.reset_password_required = false
346
+ end
347
+ end
348
+
349
+ def set_customization_computer_name
350
+ customization.computer_name = node_name
351
+ end
352
+
353
+ def save_customization
354
+ customization.enabled = true
355
+ customization.save
356
+ end
357
+ end
358
+ end
359
+ end