kitchen-vcenter 2.9.0 → 2.11.0

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: d91703414f7cc8c9c377a7e1ef6e322b716c90b61c77cfbd671da0053ac4e666
4
- data.tar.gz: 42d6e401ffca66e898a98df2d8924243e925a28c34cef635e50885a84e8f1c76
3
+ metadata.gz: 67058560fd24551eec490d96384db30c85da728655bde6495320c142582b82db
4
+ data.tar.gz: 3b7d9ff608825c060e51bcbc1937848d7a3764435898d28e031d5a49f22006a9
5
5
  SHA512:
6
- metadata.gz: e9075327273580d4ff320138e067b5691e551bc79d45728ede0057c4bb0b57b8cbeb6e63d7cf958ef06e69fe11f3e4ff885b37887829b022a764c4e44aa15f60
7
- data.tar.gz: 22ae34bfd2071b727b1ea4fec269246080021cac4dec31f83c3687faea5c1d8e650a36a5b1420c3a6e74ac081f4afc2e33e446e2e463c94ef8c462119dfdb057
6
+ metadata.gz: 51483886c3582d2761ec89033a452ee2c369bace2f0ede2d89d3b9000656190a368775b27a3620ede8948f8913af6fa5b0062f84998361f0034353953d6ef5f1
7
+ data.tar.gz: 4e3ebba36a422d3e5b7a8a5692e825fa78e79735755f2015ee3db2bd92955c5984ec515e341ec6dc04eb622cc298b05ace2b0f090c4d7b203b6dcc5ae36a3a4e
@@ -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
@@ -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.9.0"
23
+ VERSION = "2.11.0"
24
24
  end
@@ -393,6 +393,12 @@ class Support
393
393
  end
394
394
  end
395
395
 
396
+ guestinfo = options[:vm_customization].select { |key, _| key =~ /^guestinfo\..*/ }
397
+ unless guestinfo.empty?
398
+ gi = guestinfo.map { |k, v| { key: k, value: v } }
399
+ config[:extraConfig] = gi
400
+ end
401
+
396
402
  config_spec = RbVmomi::VIM.VirtualMachineConfigSpec(config)
397
403
 
398
404
  task = vm.ReconfigVM_Task(spec: config_spec)
@@ -185,11 +185,20 @@ class Support
185
185
  ERROR
186
186
  end
187
187
 
188
+ customization_pass = nil
189
+ if guest_customization[:administrator_password]
190
+ customization_pass = RbVmomi::VIM::CustomizationPassword.new(
191
+ plainText: true,
192
+ value: guest_customization[:administrator_password]
193
+ )
194
+ end
195
+
188
196
  RbVmomi::VIM::CustomizationSysprep.new(
189
197
  guiUnattended: RbVmomi::VIM::CustomizationGuiUnattended.new(
190
198
  timeZone: timezone.to_i || DEFAULT_WINDOWS_TIMEZONE,
191
199
  autoLogon: false,
192
- autoLogonCount: 1
200
+ autoLogonCount: 1,
201
+ password: customization_pass
193
202
  ),
194
203
  identification: RbVmomi::VIM::CustomizationIdentification.new,
195
204
  userData: RbVmomi::VIM::CustomizationUserData.new(
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.9.0
4
+ version: 2.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef Software
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-10-30 00:00:00.000000000 Z
11
+ date: 2021-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: net-ping
@@ -59,7 +59,7 @@ dependencies:
59
59
  version: '1.16'
60
60
  - - "<"
61
61
  - !ruby/object:Gem::Version
62
- version: '3.0'
62
+ version: '4'
63
63
  type: :runtime
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
@@ -69,7 +69,7 @@ dependencies:
69
69
  version: '1.16'
70
70
  - - "<"
71
71
  - !ruby/object:Gem::Version
72
- version: '3.0'
72
+ version: '4'
73
73
  - !ruby/object:Gem::Dependency
74
74
  name: vsphere-automation-sdk
75
75
  requirement: !ruby/object:Gem::Requirement
@@ -109,14 +109,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
109
109
  requirements:
110
110
  - - ">="
111
111
  - !ruby/object:Gem::Version
112
- version: '2.4'
112
+ version: '2.5'
113
113
  required_rubygems_version: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
116
116
  - !ruby/object:Gem::Version
117
117
  version: '0'
118
118
  requirements: []
119
- rubygems_version: 3.0.3
119
+ rubygems_version: 3.1.4
120
120
  signing_key:
121
121
  specification_version: 4
122
122
  summary: Test Kitchen driver for VMware vCenter