kitchen-vcenter 2.10.0 → 2.10.2
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/lib/kitchen-vcenter/version.rb +1 -1
- data/lib/kitchen/driver/vcenter.rb +150 -36
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c1f638e5648056082f4da2ce087031e468d0bb1113c480af1f04b98496fad5a
|
|
4
|
+
data.tar.gz: 00d9fd698d02804d4942c1be24802954d5f72ad6cbf97040eecd1d099e959631
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66e78bad8190b260de8eebcca12051a76452110ad39136e198d1f1c9823dbadbf6698aab4d7e2fe70873fb001f28268f37c5ddec2b72ecc853f6e1d06c77b17a
|
|
7
|
+
data.tar.gz: 1c60d33094d436b989b503268ce68ef7bd729a0216a848cb6de6d00c65f1ecf1af3dd92fb6913f5104a2c7de225ddc6159ddbd02ee7353c6bf1d4aa4a9357ace
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
#
|
|
17
17
|
|
|
18
18
|
require "kitchen"
|
|
19
|
+
require "rbvmomi"
|
|
19
20
|
require "vsphere-automation-cis"
|
|
20
21
|
require "vsphere-automation-vcenter"
|
|
21
22
|
require_relative "../../kitchen-vcenter/version"
|
|
@@ -29,8 +30,17 @@ module Kitchen
|
|
|
29
30
|
module Driver
|
|
30
31
|
# Extends the Base class for vCenter
|
|
31
32
|
class Vcenter < Kitchen::Driver::Base
|
|
33
|
+
class UnauthenticatedError < RuntimeError; end
|
|
34
|
+
class ResourceMissingError < RuntimeError; end
|
|
35
|
+
class ResourceAmbiguousError < RuntimeError; end
|
|
36
|
+
|
|
32
37
|
attr_accessor :connection_options, :ipaddress, :api_client
|
|
33
38
|
|
|
39
|
+
UNAUTH_CLASSES = [
|
|
40
|
+
VSphereAutomation::CIS::VapiStdErrorsUnauthenticated,
|
|
41
|
+
VSphereAutomation::VCenter::VapiStdErrorsUnauthenticated,
|
|
42
|
+
].freeze
|
|
43
|
+
|
|
34
44
|
required_config :vcenter_username
|
|
35
45
|
required_config :vcenter_password
|
|
36
46
|
required_config :vcenter_host
|
|
@@ -106,6 +116,7 @@ module Kitchen
|
|
|
106
116
|
config[:resource_pool] = root_pool
|
|
107
117
|
else
|
|
108
118
|
rp_api = VSphereAutomation::VCenter::ResourcePoolApi.new(api_client)
|
|
119
|
+
raise_if_unauthenticated rp_api, "checking for resource pools"
|
|
109
120
|
|
|
110
121
|
found_pool = nil
|
|
111
122
|
pools = rp_api.get(root_pool).value.resource_pools
|
|
@@ -114,7 +125,7 @@ module Kitchen
|
|
|
114
125
|
found_pool = pool if name == config[:resource_pool]
|
|
115
126
|
end
|
|
116
127
|
|
|
117
|
-
|
|
128
|
+
raise_if_missing found_pool, format("Resource pool `%s` not found on cluster `%s`", config[:resource_pool], config[:cluster])
|
|
118
129
|
|
|
119
130
|
config[:resource_pool] = found_pool
|
|
120
131
|
end
|
|
@@ -130,7 +141,6 @@ module Kitchen
|
|
|
130
141
|
datacenter = get_datacenter(dc_folder, dc_name)
|
|
131
142
|
cluster_id = get_cluster_id(config[:cluster])
|
|
132
143
|
|
|
133
|
-
# Using the clone class, create a machine for TK
|
|
134
144
|
# Find the identifier for the targethost to pass to rbvmomi
|
|
135
145
|
config[:targethost] = get_host(config[:targethost], datacenter, cluster_id)
|
|
136
146
|
|
|
@@ -145,6 +155,9 @@ module Kitchen
|
|
|
145
155
|
}
|
|
146
156
|
end
|
|
147
157
|
|
|
158
|
+
# Check for valid tags before cloning
|
|
159
|
+
vm_tags = map_tags(config[:tags])
|
|
160
|
+
|
|
148
161
|
# Allow different clone types
|
|
149
162
|
config[:clone_type] = :linked if config[:clone_type] == "linked"
|
|
150
163
|
config[:clone_type] = :instant if config[:clone_type] == "instant"
|
|
@@ -186,7 +199,7 @@ module Kitchen
|
|
|
186
199
|
|
|
187
200
|
rescue # Kitchen::ActionFailed => e
|
|
188
201
|
if config[:vm_rollback] == true
|
|
189
|
-
error format("Rolling back VM
|
|
202
|
+
error format("Rolling back VM `%s` after critical error", config[:vm_name])
|
|
190
203
|
|
|
191
204
|
# Inject name of failed VM for destroy to work
|
|
192
205
|
state[:vm_name] = config[:vm_name]
|
|
@@ -197,31 +210,18 @@ module Kitchen
|
|
|
197
210
|
raise
|
|
198
211
|
end
|
|
199
212
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
vm_tags = tag_api.list.value
|
|
203
|
-
raise format("No configured tags found on VCenter, but %s specified", config[:tags].to_s) if vm_tags.empty?
|
|
204
|
-
|
|
205
|
-
valid_tags = {}
|
|
206
|
-
vm_tags.each do |uid|
|
|
207
|
-
tag = tag_api.get(uid)
|
|
208
|
-
|
|
209
|
-
valid_tags[tag.value.name] = tag.value.id if tag.is_a? VSphereAutomation::CIS::CisTaggingTagResult
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# Error out on undefined tags
|
|
213
|
-
invalid = config[:tags] - valid_tags.keys
|
|
214
|
-
raise format("Specified tag(s) %s not valid", invalid.join(",")) unless invalid.empty?
|
|
213
|
+
if vm_tags
|
|
214
|
+
debug format("Setting tags on machine: `%s`", vm_tags.keys.join("`, `"))
|
|
215
215
|
|
|
216
216
|
tag_service = VSphereAutomation::CIS::TaggingTagAssociationApi.new(api_client)
|
|
217
|
-
|
|
217
|
+
raise_if_unauthenticated tag_service, "connecting to tagging service"
|
|
218
218
|
|
|
219
219
|
request_body = {
|
|
220
220
|
object_id: {
|
|
221
221
|
id: get_vm(config[:vm_name]).vm,
|
|
222
222
|
type: "VirtualMachine",
|
|
223
223
|
},
|
|
224
|
-
tag_ids:
|
|
224
|
+
tag_ids: vm_tags.values,
|
|
225
225
|
}
|
|
226
226
|
tag_service.attach_multiple_tags_to_object(request_body)
|
|
227
227
|
end
|
|
@@ -243,6 +243,7 @@ module Kitchen
|
|
|
243
243
|
vm = get_vm(state[:vm_name])
|
|
244
244
|
unless vm.nil?
|
|
245
245
|
vm_api = VSphereAutomation::VCenter::VMApi.new(api_client)
|
|
246
|
+
raise_if_unauthenticated vm_api, "connecting to VM API"
|
|
246
247
|
|
|
247
248
|
# shut the machine down if it is running
|
|
248
249
|
if vm.power_state == "POWERED_ON"
|
|
@@ -296,17 +297,69 @@ module Kitchen
|
|
|
296
297
|
state.key?(property) && !state[property].nil?
|
|
297
298
|
end
|
|
298
299
|
|
|
300
|
+
# Handle the non-ruby way of the SDK to report errors.
|
|
301
|
+
#
|
|
302
|
+
# @param api_response [Object] a generic API response class, which might include an error type
|
|
303
|
+
# @param message [String] description to output in case of error
|
|
304
|
+
# @raise UnauthenticatedError
|
|
305
|
+
def raise_if_unauthenticated(api_response, message)
|
|
306
|
+
session_id = api_response.api_client.default_headers["vmware-api-session-id"]
|
|
307
|
+
return unless UNAUTH_CLASSES.include? session_id.class
|
|
308
|
+
|
|
309
|
+
message = format("Authentication or permissions error on %s", message)
|
|
310
|
+
raise UnauthenticatedError.new(message)
|
|
311
|
+
end
|
|
312
|
+
|
|
313
|
+
# Handle missing resources in a query.
|
|
314
|
+
#
|
|
315
|
+
# @param collection [Enumerable] list which is supposed to have at least one entry
|
|
316
|
+
# @param message [String] description to output in case of error
|
|
317
|
+
# @raise ResourceMissingError
|
|
318
|
+
def raise_if_missing(collection, message)
|
|
319
|
+
return unless collection.nil? || collection.empty?
|
|
320
|
+
|
|
321
|
+
raise ResourceMissingError.new(message)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Handle ambiguous resources in a query.
|
|
325
|
+
#
|
|
326
|
+
# @param collection [Enumerable] list which is supposed to one entry at most
|
|
327
|
+
# @param message [String] description to output in case of error
|
|
328
|
+
# @raise ResourceAmbiguousError
|
|
329
|
+
def raise_if_ambiguous(collection, message)
|
|
330
|
+
return unless collection.length > 1
|
|
331
|
+
|
|
332
|
+
raise ResourceAmbiguousError.new(message)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Access to legacy SOAP based vMOMI API for some functionality
|
|
336
|
+
#
|
|
337
|
+
# @return [RbVmomi::VIM] VIM instance
|
|
338
|
+
def vim
|
|
339
|
+
@vim ||= RbVmomi::VIM.connect(connection_options)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
# Search host data via vMOMI
|
|
343
|
+
#
|
|
344
|
+
# @param moref [String] identifier of a host system ("host-xxxx")
|
|
345
|
+
# @return [RbVmomi::VIM::HostSystem]
|
|
346
|
+
def host_by_moref(moref)
|
|
347
|
+
vim.serviceInstance.content.hostSpecManager.RetrieveHostSpecification(host: moref, fromHost: false).host
|
|
348
|
+
end
|
|
349
|
+
|
|
299
350
|
# Sees in the datacenter exists or not
|
|
300
351
|
#
|
|
301
352
|
# @param [folder] folder is the name of the folder in which the Datacenter is stored in inventory, possibly nil
|
|
302
353
|
# @param [name] name is the name of the datacenter
|
|
303
354
|
def datacenter_exists?(folder, name)
|
|
304
355
|
dc_api = VSphereAutomation::VCenter::DatacenterApi.new(api_client)
|
|
356
|
+
raise_if_unauthenticated dc_api, "checking for datacenter `#{name}`"
|
|
357
|
+
|
|
305
358
|
opts = { filter_names: name }
|
|
306
359
|
opts[:filter_folders] = get_folder(folder, "DATACENTER") if folder
|
|
307
360
|
dcs = dc_api.list(opts).value
|
|
308
361
|
|
|
309
|
-
|
|
362
|
+
raise_if_missing dcs, format("Unable to find data center `%s`", name)
|
|
310
363
|
end
|
|
311
364
|
|
|
312
365
|
# Checks if a network exists or not
|
|
@@ -314,9 +367,43 @@ module Kitchen
|
|
|
314
367
|
# @param [name] name is the name of the Network
|
|
315
368
|
def network_exists?(name)
|
|
316
369
|
net_api = VSphereAutomation::VCenter::NetworkApi.new(api_client)
|
|
370
|
+
raise_if_unauthenticated net_api, "checking for VM network `#{name}`"
|
|
371
|
+
|
|
317
372
|
nets = net_api.list({ filter_names: name }).value
|
|
318
373
|
|
|
319
|
-
|
|
374
|
+
raise_if_missing nets, format("Unable to find target network: `%s`", name)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# Map VCenter tag names to URNs (VCenter needs tags to be predefined)
|
|
378
|
+
#
|
|
379
|
+
# @param tags [tags] tags is the list of tags to associate
|
|
380
|
+
# @return [Hash] mapping of VCenter tag name to URN
|
|
381
|
+
# @raise UnauthenticatedError
|
|
382
|
+
# @raise ResourceMissingError
|
|
383
|
+
def map_tags(tags)
|
|
384
|
+
return nil if tags.nil? || tags.empty?
|
|
385
|
+
|
|
386
|
+
tag_api = VSphereAutomation::CIS::TaggingTagApi.new(api_client)
|
|
387
|
+
raise_if_unauthenticated tag_api, "checking for tags"
|
|
388
|
+
|
|
389
|
+
vm_tags = tag_api.list.value
|
|
390
|
+
raise_if_missing vm_tags, format("No configured tags found on VCenter, but `%s` specified", config[:tags].to_s)
|
|
391
|
+
|
|
392
|
+
# Create list of all VCenter defined tags, associated with their internal ID
|
|
393
|
+
valid_tags = {}
|
|
394
|
+
vm_tags.each do |uid|
|
|
395
|
+
tag = tag_api.get(uid)
|
|
396
|
+
|
|
397
|
+
valid_tags[tag.value.name] = tag.value.id if tag.is_a? VSphereAutomation::CIS::CisTaggingTagResult
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
invalid = config[:tags] - valid_tags.keys
|
|
401
|
+
unless invalid.empty?
|
|
402
|
+
message = format("Specified tag(s) `%s` not preconfigured on VCenter", invalid.join("`, `"))
|
|
403
|
+
raise ResourceMissingError.new(message)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
valid_tags.select { |tag, _urn| config[:tags].include? tag }
|
|
320
407
|
end
|
|
321
408
|
|
|
322
409
|
# Validates the host name of the server you can connect to
|
|
@@ -325,15 +412,31 @@ module Kitchen
|
|
|
325
412
|
def get_host(name, datacenter, cluster = nil)
|
|
326
413
|
# create a host object to work with
|
|
327
414
|
host_api = VSphereAutomation::VCenter::HostApi.new(api_client)
|
|
415
|
+
raise_if_unauthenticated host_api, "checking for target host `#{name || "(any)"}`"
|
|
328
416
|
|
|
329
417
|
hosts = host_api.list({ filter_names: name,
|
|
330
418
|
filter_datacenters: datacenter,
|
|
331
419
|
filter_clusters: cluster,
|
|
332
420
|
filter_connection_states: ["CONNECTED"] }).value
|
|
333
421
|
|
|
334
|
-
|
|
422
|
+
raise_if_missing hosts, format("Unable to find target host `%s`", name || "(any)")
|
|
423
|
+
|
|
424
|
+
filter_maintenance!(hosts)
|
|
425
|
+
raise_if_missing hosts, "Unable to find active target host in datacenter (check maintenance mode?)"
|
|
426
|
+
|
|
427
|
+
# Randomize returned hosts
|
|
428
|
+
host = hosts.sample
|
|
429
|
+
debug format("Selected host `%s` randomly for deployment", host.name)
|
|
335
430
|
|
|
336
|
-
|
|
431
|
+
host
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def filter_maintenance!(hosts)
|
|
435
|
+
# Exclude hosts which are in maintenance mode (via SOAP API only)
|
|
436
|
+
hosts.reject! do |hostinfo|
|
|
437
|
+
host = host_by_moref(hostinfo.host)
|
|
438
|
+
host.runtime.inMaintenanceMode
|
|
439
|
+
end
|
|
337
440
|
end
|
|
338
441
|
|
|
339
442
|
# Gets the folder you want to create the VM
|
|
@@ -343,6 +446,8 @@ module Kitchen
|
|
|
343
446
|
# @param [datacenter] datacenter is the datacenter of the folder
|
|
344
447
|
def get_folder(name, type = "VIRTUAL_MACHINE", datacenter = nil)
|
|
345
448
|
folder_api = VSphereAutomation::VCenter::FolderApi.new(api_client)
|
|
449
|
+
raise_if_unauthenticated folder_api, "checking for folder `#{name}`"
|
|
450
|
+
|
|
346
451
|
parent_path, basename = File.split(name)
|
|
347
452
|
filter = { filter_names: basename, filter_type: type }
|
|
348
453
|
filter[:filter_datacenters] = datacenter if datacenter
|
|
@@ -350,9 +455,8 @@ module Kitchen
|
|
|
350
455
|
|
|
351
456
|
folders = folder_api.list(filter).value
|
|
352
457
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
raise format("`%s` returned too many folders", basename) if folders.length > 1
|
|
458
|
+
raise_if_missing folders, format("Unable to find VM/template folder: `%s`", basename)
|
|
459
|
+
raise_if_ambiguous folders, format("`%s` returned too many VM/template folders", basename)
|
|
356
460
|
|
|
357
461
|
folders.first.folder
|
|
358
462
|
end
|
|
@@ -362,8 +466,13 @@ module Kitchen
|
|
|
362
466
|
# @param [name] name is the name of the VM
|
|
363
467
|
def get_vm(name)
|
|
364
468
|
vm_api = VSphereAutomation::VCenter::VMApi.new(api_client)
|
|
469
|
+
raise_if_unauthenticated vm_api, "checking for VM `#{name}`"
|
|
470
|
+
|
|
365
471
|
vms = vm_api.list({ filter_names: name }).value
|
|
366
472
|
|
|
473
|
+
raise_if_missing vms, format("Unable to find VM `%s`", name)
|
|
474
|
+
raise_if_ambiguous vms, format("`%s` returned too many VMs", name)
|
|
475
|
+
|
|
367
476
|
vms.first
|
|
368
477
|
end
|
|
369
478
|
|
|
@@ -373,13 +482,14 @@ module Kitchen
|
|
|
373
482
|
# @param [name] name is the name of the Datacenter
|
|
374
483
|
def get_datacenter(folder, name)
|
|
375
484
|
dc_api = VSphereAutomation::VCenter::DatacenterApi.new(api_client)
|
|
485
|
+
raise_if_unauthenticated dc_api, "checking for datacenter `#{name}` in folder `#{folder}`"
|
|
486
|
+
|
|
376
487
|
opts = { filter_names: name }
|
|
377
488
|
opts[:filter_folders] = get_folder(folder, "DATACENTER") if folder
|
|
378
489
|
dcs = dc_api.list(opts).value
|
|
379
490
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
raise format("%s returned too many data centers", name) if dcs.length > 1
|
|
491
|
+
raise_if_missing dcs, format("Unable to find data center: `%s`", name)
|
|
492
|
+
raise_if_ambiguous dcs, format("`%s` returned too many data centers", name)
|
|
383
493
|
|
|
384
494
|
dcs.first.datacenter
|
|
385
495
|
end
|
|
@@ -391,11 +501,12 @@ module Kitchen
|
|
|
391
501
|
return if name.nil?
|
|
392
502
|
|
|
393
503
|
cluster_api = VSphereAutomation::VCenter::ClusterApi.new(api_client)
|
|
394
|
-
|
|
504
|
+
raise_if_unauthenticated cluster_api, "checking for ID of cluster `#{name}`"
|
|
395
505
|
|
|
396
|
-
|
|
506
|
+
clusters = cluster_api.list({ filter_names: name }).value
|
|
397
507
|
|
|
398
|
-
|
|
508
|
+
raise_if_missing clusters, format("Unable to find Cluster: `%s`", name)
|
|
509
|
+
raise_if_ambiguous clusters, format("`%s` returned too many clusters", name)
|
|
399
510
|
|
|
400
511
|
clusters.first.cluster
|
|
401
512
|
end
|
|
@@ -407,9 +518,11 @@ module Kitchen
|
|
|
407
518
|
cluster_id = get_cluster_id(name)
|
|
408
519
|
|
|
409
520
|
host_api = VSphereAutomation::VCenter::HostApi.new(api_client)
|
|
410
|
-
|
|
521
|
+
raise_if_unauthenticated host_api, "checking for cluster `#{name}`"
|
|
411
522
|
|
|
412
|
-
|
|
523
|
+
hosts = host_api.list({ filter_clusters: cluster_id, connection_states: "CONNECTED" }).value
|
|
524
|
+
filter_maintenance!(hosts)
|
|
525
|
+
raise_if_missing hosts, format("Unable to find active hosts in cluster `%s`", name)
|
|
413
526
|
|
|
414
527
|
cluster_api = VSphereAutomation::VCenter::ClusterApi.new(api_client)
|
|
415
528
|
cluster_api.get(cluster_id).value
|
|
@@ -422,6 +535,7 @@ module Kitchen
|
|
|
422
535
|
def get_resource_pool(name)
|
|
423
536
|
# Create a resource pool object
|
|
424
537
|
rp_api = VSphereAutomation::VCenter::ResourcePoolApi.new(api_client)
|
|
538
|
+
raise_if_unauthenticated rp_api, "checking for resource pool `#{name || "(default)"}`"
|
|
425
539
|
|
|
426
540
|
# If no name has been set, use the first resource pool that can be found,
|
|
427
541
|
# otherwise try to find by given name
|
|
@@ -446,7 +560,7 @@ module Kitchen
|
|
|
446
560
|
debug("Search for resource pools found: " + resource_pools.map(&:name).to_s)
|
|
447
561
|
end
|
|
448
562
|
|
|
449
|
-
|
|
563
|
+
raise_if_missing resource_pools, format("Unable to find resource pool `%s`", name || "(default)")
|
|
450
564
|
|
|
451
565
|
resource_pools.first.resource_pool
|
|
452
566
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-vcenter
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.10.
|
|
4
|
+
version: 2.10.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Chef Software
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-08-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: net-ping
|