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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c282cd775e9dcb923d3f883b6fc6869f7ee36048421f5fc31e641f38f7c15778
4
- data.tar.gz: 55c727a19afcf3b3a2f2d597a5919fd7be04a37af0593ae5309076f75d5b6673
3
+ metadata.gz: 7c1f638e5648056082f4da2ce087031e468d0bb1113c480af1f04b98496fad5a
4
+ data.tar.gz: 00d9fd698d02804d4942c1be24802954d5f72ad6cbf97040eecd1d099e959631
5
5
  SHA512:
6
- metadata.gz: 9e859fe6d9ae8271f5867c1906e5772f09535b184b4f64826ab5fb7af7661d8f401e1f34b54abdca181fa8e7bc7fe02519adf18b86690790c6ad18a35c1e2817
7
- data.tar.gz: 6cae1dc0db973588dcaa4a52cfebbe194d06bd9eaa1bfd31bf6defc781569ed15062f008a5790dcc1fa5f669ec4f108aff6c484cbb541ca7167b977ee733ae3c
6
+ metadata.gz: 66e78bad8190b260de8eebcca12051a76452110ad39136e198d1f1c9823dbadbf6698aab4d7e2fe70873fb001f28268f37c5ddec2b72ecc853f6e1d06c77b17a
7
+ data.tar.gz: 1c60d33094d436b989b503268ce68ef7bd729a0216a848cb6de6d00c65f1ecf1af3dd92fb6913f5104a2c7de225ddc6159ddbd02ee7353c6bf1d4aa4a9357ace
@@ -20,5 +20,5 @@
20
20
  # The main kitchen-vcenter module
21
21
  module KitchenVcenter
22
22
  # The version of this version of test-kitchen we assume enterprises want.
23
- VERSION = "2.10.0"
23
+ VERSION = "2.10.2"
24
24
  end
@@ -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
- raise format("Pool %s not found on cluster %s", config[:resource_pool], config[:cluster]) if found_pool.nil?
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 %s after critical error", config[:vm_name])
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
- unless config[:tags].nil? || config[:tags].empty?
201
- tag_api = VSphereAutomation::CIS::TaggingTagApi.new(api_client)
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
- tag_ids = config[:tags].map { |name| valid_tags[name] }
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: 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
- raise format("Unable to find data center: %s", name) if dcs.empty?
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
- raise format("Unable to find target network: %s", name) if nets.empty?
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
- raise format("Unable to find target host: %s", name) if hosts.empty?
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
- hosts.sample
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
- raise format("Unable to find folder: %s", basename) if folders.empty?
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
- raise format("Unable to find data center: %s", name) if dcs.empty?
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
- clusters = cluster_api.list({ filter_names: name }).value
504
+ raise_if_unauthenticated cluster_api, "checking for ID of cluster `#{name}`"
395
505
 
396
- raise format("Unable to find Cluster: %s", name) if clusters.empty?
506
+ clusters = cluster_api.list({ filter_names: name }).value
397
507
 
398
- raise format("%s returned too many clusters", name) if clusters.length > 1
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
- hosts = host_api.list({ filter_clusters: cluster_id, connection_states: "CONNECTED" }).value
521
+ raise_if_unauthenticated host_api, "checking for cluster `#{name}`"
411
522
 
412
- raise format("Unable to find active host in cluster %s", name) if hosts.empty?
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
- raise format("Unable to find Resource Pool: %s", name) if resource_pools.empty?
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.0
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-07-02 00:00:00.000000000 Z
11
+ date: 2021-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-ping