kitchen-vcenter 2.10.0 → 2.10.2

Sign up to get free protection for your applications and to get access to all the features.
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