kitchen-vcenter 2.8.6 → 2.9.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: 1da356cf16429bae878bc2a5672f2c574c9cd82028914f4aca53d1311955eb4c
4
- data.tar.gz: ddf119be74e493604cef87b3cf3d45e2c486492a26eeac5fd5bb6c21ef58a38a
3
+ metadata.gz: d91703414f7cc8c9c377a7e1ef6e322b716c90b61c77cfbd671da0053ac4e666
4
+ data.tar.gz: 42d6e401ffca66e898a98df2d8924243e925a28c34cef635e50885a84e8f1c76
5
5
  SHA512:
6
- metadata.gz: '07824b2bb5850ba139bea24ea65082c602e6e56ea79903a3645ce90d64ff1614e35eeee52ce1dae68670d688be6c9aaec577eab283ac4efb5fcfc4d9da23af1b'
7
- data.tar.gz: a3d4f536ad36b3bf666183a607f2ba06a8bbfe9806e2e1917db30b65ec900a808fd69977587df44622e71f9a9ebce3363581c9cc689992f0fa8aac815457f93d
6
+ metadata.gz: e9075327273580d4ff320138e067b5691e551bc79d45728ede0057c4bb0b57b8cbeb6e63d7cf958ef06e69fe11f3e4ff885b37887829b022a764c4e44aa15f60
7
+ data.tar.gz: 22ae34bfd2071b727b1ea4fec269246080021cac4dec31f83c3687faea5c1d8e650a36a5b1420c3a6e74ac081f4afc2e33e446e2e463c94ef8c462119dfdb057
@@ -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.8.6"
23
+ VERSION = "2.9.0"
24
24
  end
@@ -50,7 +50,7 @@ module Kitchen
50
50
  default_config :vm_wait_timeout, 90
51
51
  default_config :vm_wait_interval, 2.0
52
52
  default_config :vm_rollback, false
53
- default_config :customize, nil
53
+ default_config :vm_customization, nil
54
54
  default_config :guest_customization, nil
55
55
  default_config :interface, nil
56
56
  default_config :active_discovery, false
@@ -80,6 +80,10 @@ module Kitchen
80
80
  The 'aggressive_password' setting was renamed to 'vm_password' and will
81
81
  be removed in future versions.
82
82
  MSG
83
+ deprecate_config_for :customize, Util.outdent!(<<-MSG)
84
+ The `customize` setting was renamed to `vm_customization` and will
85
+ be removed in future versions.
86
+ MSG
83
87
 
84
88
  # The main create method
85
89
  #
@@ -147,7 +151,7 @@ module Kitchen
147
151
 
148
152
  # Create a hash of options that the clone requires
149
153
  options = {
150
- name: config[:vm_name],
154
+ vm_name: config[:vm_name],
151
155
  targethost: config[:targethost],
152
156
  poweron: config[:poweron],
153
157
  template: config[:template],
@@ -159,7 +163,7 @@ module Kitchen
159
163
  interface: config[:interface],
160
164
  wait_timeout: config[:vm_wait_timeout],
161
165
  wait_interval: config[:vm_wait_interval],
162
- customize: config[:customize],
166
+ vm_customization: config[:vm_customization],
163
167
  guest_customization: config[:guest_customization],
164
168
  active_discovery: config[:active_discovery],
165
169
  active_discovery_command: config[:active_discovery_command],
@@ -178,7 +182,7 @@ module Kitchen
178
182
  new_vm.clone
179
183
 
180
184
  state[:hostname] = new_vm.ip
181
- state[:vm_name] = new_vm.name
185
+ state[:vm_name] = new_vm.vm_name
182
186
 
183
187
  rescue # Kitchen::ActionFailed => e
184
188
  if config[:vm_rollback] == true
@@ -280,6 +284,7 @@ module Kitchen
280
284
  config[:vm_os] = config[:aggressive_os] unless config[:aggressive_os].nil?
281
285
  config[:vm_username] = config[:aggressive_username] unless config[:aggressive_username].nil?
282
286
  config[:vm_password] = config[:aggressive_password] unless config[:aggressive_password].nil?
287
+ config[:vm_customization] = config[:customize] unless config[:customize].nil?
283
288
  end
284
289
 
285
290
  # A helper method to validate the state
@@ -1,20 +1,25 @@
1
1
  require "kitchen"
2
2
  require "rbvmomi"
3
+
4
+ require_relative "guest_customization"
3
5
  require_relative "guest_operations"
4
6
 
5
7
  class Support
6
8
  class CloneError < RuntimeError; end
7
9
 
8
10
  class CloneVm
9
- attr_reader :vim, :options, :ssl_verify, :vm, :name, :ip, :guest_auth, :username
11
+ attr_reader :vim, :vem, :options, :ssl_verify, :src_vm, :vm, :vm_name, :ip, :guest_auth, :username
12
+
13
+ include GuestCustomization
10
14
 
11
15
  def initialize(conn_opts, options)
12
16
  @options = options
13
- @name = options[:name]
17
+ @vm_name = options[:vm_name]
14
18
  @ssl_verify = !conn_opts[:insecure]
15
19
 
16
20
  # Connect to vSphere
17
21
  @vim ||= RbVmomi::VIM.connect conn_opts
22
+ @vem ||= vim.serviceContent.eventManager
18
23
 
19
24
  @username = options[:vm_username]
20
25
  password = options[:vm_password]
@@ -156,8 +161,8 @@ class Support
156
161
  Kitchen.logger.debug format("Benchmark: Appended data to file %s", benchmark_file)
157
162
  end
158
163
 
159
- def detect_os
160
- vm.config&.guestId&.match(/^win/) ? :windows : :linux
164
+ def detect_os(vm_or_template)
165
+ vm_or_template.config&.guestId&.match(/^win/) ? :windows : :linux
161
166
  end
162
167
 
163
168
  def windows?
@@ -318,14 +323,14 @@ class Support
318
323
  end
319
324
  end
320
325
 
321
- def reconfigure_guest
322
- Kitchen.logger.info "Waiting for reconfiguration to finish"
326
+ def vm_customization
327
+ Kitchen.logger.info "Waiting for VM customization..."
323
328
 
324
329
  # Pass some contents right through
325
330
  # https://pubs.vmware.com/vsphere-6-5/index.jsp?topic=%2Fcom.vmware.wssdk.smssdk.doc%2Fvim.vm.ConfigSpec.html
326
- config = options[:customize].select { |key, _| %i{annotation memoryMB numCPUs}.include? key }
331
+ config = options[:vm_customization].select { |key, _| %i{annotation memoryMB numCPUs}.include? key }
327
332
 
328
- add_disks = options[:customize]&.fetch(:add_disks, nil)
333
+ add_disks = options[:vm_customization]&.fetch(:add_disks, nil)
329
334
  unless add_disks.nil?
330
335
  config[:deviceChange] = []
331
336
 
@@ -438,86 +443,16 @@ class Support
438
443
  false
439
444
  end
440
445
 
441
- def customization_spec
442
- unless options[:guest_customization]
443
- return false
444
- end
445
-
446
- if options[:guest_customization][:ip_address]
447
- unless ip?(options[:guest_customization][:ip_address])
448
- raise Support::CloneError.new("Guest customization error: ip_address is required to be formatted as an IPv4 address")
449
- end
450
-
451
- unless options[:guest_customization][:subnet_mask]
452
- raise Support::CloneError.new("Guest customization error: subnet_mask is required if assigning a fixed IPv4 address")
453
- end
454
-
455
- unless ip?(options[:guest_customization][:subnet_mask])
456
- raise Support::CloneError.new("Guest customization error: subnet_mask is required to be formatted as an IPv4 address")
457
- end
458
- end
446
+ def vm_events(event_types = [])
447
+ raise Support::CloneError.new("`vm_events` called before VM clone") unless vm
459
448
 
460
- if options[:guest_customization][:gateway]
461
- unless options[:guest_customization][:gateway].is_a?(Array)
462
- raise Support::CloneError.new("Guest customization error: gateway must be an array")
463
- end
464
-
465
- options[:guest_customization][:gateway].each do |v|
466
- unless ip?(v)
467
- raise Support::CloneError.new("Guest customization error: gateway is required to be formatted as an IPv4 address")
468
- end
469
- end
470
- end
471
-
472
- required = %i{dns_domain timezone dns_server_list dns_suffix_list}
473
- missing = required - options[:guest_customization].keys
474
- unless missing.empty?
475
- raise Support::CloneError.new("Guest customization error: #{missing.join(", ")} are required to support guest customization")
476
- end
477
-
478
- options[:guest_customization][:dns_server_list].each do |v|
479
- unless ip?(v)
480
- raise Support::CloneError.new("Guest customization error: dns_server_list is required to be formatted as an IPv4 address")
481
- end
482
- end
483
-
484
- if !options[:guest_customization][:dns_server_list].is_a?(Array)
485
- raise Support::CloneError.new("Guest customization error: dns_server_list must be an array")
486
- elsif !options[:guest_customization][:dns_suffix_list].is_a?(Array)
487
- raise Support::CloneError.new("Guest customization error: dns_suffix_list must be an array")
488
- end
489
-
490
- if options[:guest_customization][:ip_address]
491
- customized_ip = RbVmomi::VIM::CustomizationIPSettings.new(
492
- ip: RbVmomi::VIM::CustomizationFixedIp(ipAddress: options[:guest_customization][:ip_address]),
493
- gateway: options[:guest_customization][:gateway],
494
- subnetMask: options[:guest_customization][:subnet_mask],
495
- dnsDomain: options[:guest_customization][:dns_domain]
496
- )
497
- else
498
- customized_ip = RbVmomi::VIM::CustomizationIPSettings.new(
499
- ip: RbVmomi::VIM::CustomizationDhcpIpGenerator.new,
500
- dnsDomain: options[:guest_customization][:dns_domain]
501
- )
502
- end
503
-
504
- RbVmomi::VIM::CustomizationSpec.new(
505
- identity: RbVmomi::VIM::CustomizationLinuxPrep.new(
506
- domain: options[:guest_customization][:dns_domain],
507
- hostName: RbVmomi::VIM::CustomizationFixedName.new(
508
- name: name
509
- ),
510
- hwClockUTC: true,
511
- timeZone: options[:guest_customization][:timezone]
512
- ),
513
- globalIPSettings: RbVmomi::VIM::CustomizationGlobalIPSettings.new(
514
- dnsServerList: options[:guest_customization][:dns_server_list],
515
- dnsSuffixList: options[:guest_customization][:dns_suffix_list]
449
+ vem.QueryEvents(filter: RbVmomi::VIM::EventFilterSpec(
450
+ entity: RbVmomi::VIM::EventFilterSpecByEntity(
451
+ entity: vm,
452
+ recursion: RbVmomi::VIM::EventFilterSpecRecursionOption(:self)
516
453
  ),
517
- nicSettingMap: [RbVmomi::VIM::CustomizationAdapterMapping.new(
518
- adapter: customized_ip
519
- )]
520
- )
454
+ eventTypeId: event_types
455
+ ))
521
456
  end
522
457
 
523
458
  def clone
@@ -526,12 +461,9 @@ class Support
526
461
  # set the datacenter name
527
462
  dc = find_datacenter
528
463
 
529
- # get guest customization spec
530
- guest_customization = customization_spec
531
-
532
464
  # reference template using full inventory path
533
465
  inventory_path = format("/%s/vm/%s", datacenter, options[:template])
534
- src_vm = root_folder.findByInventoryPath(inventory_path)
466
+ @src_vm = root_folder.findByInventoryPath(inventory_path)
535
467
  raise Support::CloneError.new(format("Unable to find template: %s", options[:template])) if src_vm.nil?
536
468
 
537
469
  if src_vm.config.template && !full_clone?
@@ -544,6 +476,13 @@ class Support
544
476
  options[:clone_type] = :full
545
477
  end
546
478
 
479
+ # Autodetect OS, if none given
480
+ if options[:vm_os].nil?
481
+ os = detect_os(src_vm)
482
+ Kitchen.logger.debug format('OS for VM not configured, got "%s" from VMware', os.to_s.capitalize)
483
+ options[:vm_os] = os
484
+ end
485
+
547
486
  # Specify where the machine is going to be created
548
487
  relocate_spec = RbVmomi::VIM.VirtualMachineRelocateSpec
549
488
 
@@ -642,23 +581,21 @@ class Support
642
581
  ]
643
582
 
644
583
  clone_spec = RbVmomi::VIM.VirtualMachineInstantCloneSpec(location: relocate_spec,
645
- name: name)
584
+ name: vm_name)
646
585
 
647
586
  benchmark_checkpoint("initialized") if benchmark?
648
587
  task = src_vm.InstantClone_Task(spec: clone_spec)
649
588
  else
650
589
  clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(
651
590
  location: relocate_spec,
652
- powerOn: options[:poweron] && options[:customize].nil?,
591
+ powerOn: options[:poweron] && options[:vm_customization].nil?,
653
592
  template: false
654
593
  )
655
594
 
656
- if guest_customization
657
- clone_spec.customization = guest_customization
658
- end
595
+ clone_spec.customization = guest_customization_spec if options[:guest_customization]
659
596
 
660
597
  benchmark_checkpoint("initialized") if benchmark?
661
- task = src_vm.CloneVM_Task(spec: clone_spec, folder: dest_folder, name: name)
598
+ task = src_vm.CloneVM_Task(spec: clone_spec, folder: dest_folder, name: vm_name)
662
599
  end
663
600
  task.wait_for_completion
664
601
 
@@ -666,31 +603,28 @@ class Support
666
603
 
667
604
  # get the IP address of the machine for bootstrapping
668
605
  # machine name is based on the path, e.g. that includes the folder
669
- path = options[:folder].nil? ? name : format("%s/%s", options[:folder][:name], name)
606
+ path = options[:folder].nil? ? vm_name : format("%s/%s", options[:folder][:name], vm_name)
670
607
  @vm = dc.find_vm(path)
671
608
  raise Support::CloneError.new(format("Unable to find machine: %s", path)) if vm.nil?
672
609
 
673
- if options[:vm_os].nil?
674
- os = detect_os
675
- Kitchen.logger.debug format('OS for VM not configured, got "%s" from VMware', os.to_s.capitalize)
676
- options[:vm_os] = os
677
- end
678
-
679
610
  # Reconnect network device after Instant Clone is ready
680
611
  if instant_clone?
681
612
  Kitchen.logger.info "Reconnecting network adapter"
682
613
  reconnect_network_device(vm)
683
614
  end
684
615
 
685
- reconfigure_guest unless options[:customize].nil?
616
+ vm_customization if options[:vm_customization]
686
617
 
687
618
  # Start only if specified or customizations wanted; no need for instant clones as they start in running state
688
- if options[:poweron] && !options[:customize].nil? && !instant_clone?
619
+ if options[:poweron] && !options[:vm_customization].nil? && !instant_clone?
689
620
  task = vm.PowerOnVM_Task
690
621
  task.wait_for_completion
691
622
  end
692
623
  benchmark_checkpoint("powered_on") if benchmark?
693
624
 
625
+ # Windows customization takes a while, so check for its completion
626
+ guest_customization_wait if options[:guest_customization]
627
+
694
628
  Kitchen.logger.info format("Waiting for VMware tools to become available (timeout: %d seconds)...", options[:wait_timeout])
695
629
  wait_for_tools(options[:wait_timeout], options[:wait_interval])
696
630
 
@@ -698,7 +632,7 @@ class Support
698
632
  benchmark_checkpoint("ip_detected") if benchmark?
699
633
 
700
634
  benchmark_persist if benchmark?
701
- Kitchen.logger.info format("Created machine %s with IP %s", name, ip)
635
+ Kitchen.logger.info format("Created machine %s with IP %s", vm_name, ip)
702
636
  end
703
637
  end
704
638
  end
@@ -0,0 +1,327 @@
1
+ require "net/ping"
2
+ require "rbvmomi"
3
+
4
+ class Support
5
+ class GuestCustomizationError < RuntimeError; end
6
+ class GuestCustomizationOptionsError < RuntimeError; end
7
+
8
+ module GuestCustomization
9
+ DEFAULT_LINUX_TIMEZONE = "Etc/UTC".freeze
10
+ DEFAULT_WINDOWS_ORG = "TestKitchen".freeze
11
+ DEFAULT_WINDOWS_TIMEZONE = 0x80000050 # Etc/UTC
12
+ DEFAULT_TIMEOUT_TASK = 600
13
+ DEFAULT_TIMEOUT_IP = 60
14
+
15
+ # Generic Volume License Keys for temporary Windows Server setup.
16
+ #
17
+ # @see https://docs.microsoft.com/en-us/windows-server/get-started/kmsclientkeys
18
+ WINDOWS_KMS_KEYS = {
19
+ "Microsoft Windows Server 2019 (64-bit)" => "N69G4-B89J2-4G8F4-WWYCC-J464C",
20
+ "Microsoft Windows Server 2016 (64-bit)" => "WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY",
21
+ "Microsoft Windows Server 2012R2 (64-bit)" => "D2N9P-3P6X9-2R39C-7RTCD-MDVJX",
22
+ "Microsoft Windows Server 2012 (64-bit)" => "BN3D2-R7TKB-3YPBD-8DRP2-27GG4",
23
+ }.freeze
24
+
25
+ # Configuration values for Guest Customization
26
+ #
27
+ # @returns [Hash] Configuration values from file
28
+ def guest_customization
29
+ options[:guest_customization]
30
+ end
31
+
32
+ # Build CustomizationSpec for Guest OS Customization
33
+ #
34
+ # @returns [RbVmomi::VIM::CustomizationSpec] Customization Spec for guest adjustments
35
+ def guest_customization_spec
36
+ return unless guest_customization
37
+
38
+ guest_customization_validate_options
39
+
40
+ if guest_customization[:ip_address]
41
+ customized_ip = RbVmomi::VIM::CustomizationIPSettings.new(
42
+ ip: RbVmomi::VIM::CustomizationFixedIp(ipAddress: guest_customization[:ip_address]),
43
+ gateway: guest_customization[:gateway],
44
+ subnetMask: guest_customization[:subnet_mask],
45
+ dnsDomain: guest_customization[:dns_domain]
46
+ )
47
+ else
48
+ customized_ip = RbVmomi::VIM::CustomizationIPSettings.new(
49
+ ip: RbVmomi::VIM::CustomizationDhcpIpGenerator.new,
50
+ dnsDomain: guest_customization[:dns_domain]
51
+ )
52
+ end
53
+
54
+ RbVmomi::VIM::CustomizationSpec.new(
55
+ identity: guest_customization_identity,
56
+ globalIPSettings: RbVmomi::VIM::CustomizationGlobalIPSettings.new(
57
+ dnsServerList: guest_customization[:dns_server_list],
58
+ dnsSuffixList: guest_customization[:dns_suffix_list]
59
+ ),
60
+ nicSettingMap: [RbVmomi::VIM::CustomizationAdapterMapping.new(
61
+ adapter: customized_ip
62
+ )]
63
+ )
64
+ end
65
+
66
+ # Check options for existance and format
67
+ #
68
+ # @raise [Support::GuestCustomizationOptionsError] For any violation
69
+ def guest_customization_validate_options
70
+ if guest_customization_ip_change?
71
+ unless ip?(guest_customization[:ip_address])
72
+ raise Support::GuestCustomizationOptionsError.new("Parameter `ip_address` is required to be formatted as an IPv4 address")
73
+ end
74
+
75
+ unless guest_customization[:subnet_mask]
76
+ raise Support::GuestCustomizationOptionsError.new("Parameter `subnet_mask` is required if assigning a fixed IPv4 address")
77
+ end
78
+
79
+ unless ip?(guest_customization[:subnet_mask])
80
+ raise Support::GuestCustomizationOptionsError.new("Parameter `subnet_mask` is required to be formatted as an IPv4 address")
81
+ end
82
+
83
+ if up?(guest_customization[:ip_address])
84
+ raise Support::GuestCustomizationOptionsError.new("Parameter `ip_address` points to a host reachable via ICMP") unless guest_customization[:continue_on_ip_conflict]
85
+
86
+ Kitchen.logger.warn("Continuing customization despite `ip_address` conflicting with a reachable host per user request")
87
+ end
88
+ end
89
+
90
+ if guest_customization[:gateway]
91
+ unless guest_customization[:gateway].is_a?(Array)
92
+ raise Support::GuestCustomizationOptionsError.new("Parameter `gateway` must be an array")
93
+ end
94
+
95
+ guest_customization[:gateway].each do |v|
96
+ unless ip?(v)
97
+ raise Support::GuestCustomizationOptionsError.new("Parameter `gateway` is required to be formatted as an IPv4 address")
98
+ end
99
+ end
100
+ end
101
+
102
+ required = %i{dns_domain dns_server_list dns_suffix_list}
103
+ missing = required - guest_customization.keys
104
+ unless missing.empty?
105
+ raise Support::GuestCustomizationOptionsError.new("Parameters `#{missing.join("`, `")}` are required to support guest customization")
106
+ end
107
+
108
+ guest_customization[:dns_server_list].each do |v|
109
+ unless ip?(v)
110
+ raise Support::GuestCustomizationOptionsError.new("Parameter `dns_server_list` is required to be formatted as an IPv4 address")
111
+ end
112
+ end
113
+
114
+ if !guest_customization[:dns_server_list].is_a?(Array)
115
+ raise Support::GuestCustomizationOptionsError.new("Parameter `dns_server_list` must be an array")
116
+ elsif !guest_customization[:dns_suffix_list].is_a?(Array)
117
+ raise Support::GuestCustomizationOptionsError.new("Parameter `dns_suffix_list` must be an array")
118
+ end
119
+ end
120
+
121
+ # Check if an IP change is requested
122
+ #
123
+ # @returns [Boolean] If `ip_address` is to be changed
124
+ def guest_customization_ip_change?
125
+ guest_customization[:ip_address]
126
+ end
127
+
128
+ # Return OS-specific CustomizationIdentity object
129
+ def guest_customization_identity
130
+ if linux?
131
+ guest_customization_identity_linux
132
+ elsif windows?
133
+ guest_customization_identity_windows
134
+ else
135
+ raise Support::GuestCustomizationError.new("Unknown OS, no valid customization found")
136
+ end
137
+ end
138
+
139
+ # Construct Linux-specific customization information
140
+ def guest_customization_identity_linux
141
+ timezone = guest_customization[:timezone]
142
+ if timezone && !valid_linux_timezone?(timezone)
143
+ raise Support::GuestCustomizationError.new <<~ERROR
144
+ Linux customization requires `timezone` in `Area/Location` format.
145
+ See https://kb.vmware.com/s/article/2145518
146
+ ERROR
147
+ end
148
+
149
+ Kitchen.logger.warn("Linux guest customization: No timezone passed, assuming UTC") unless timezone
150
+
151
+ RbVmomi::VIM::CustomizationLinuxPrep.new(
152
+ domain: guest_customization[:dns_domain],
153
+ hostName: guest_hostname,
154
+ hwClockUTC: true,
155
+ timeZone: timezone || DEFAULT_LINUX_TIMEZONE
156
+ )
157
+ end
158
+
159
+ # Construct Windows-specific customization information
160
+ def guest_customization_identity_windows
161
+ timezone = guest_customization[:timezone]
162
+ if timezone && !valid_windows_timezone?(timezone)
163
+ raise Support::GuestCustomizationOptionsError.new <<~ERROR
164
+ Windows customization requires `timezone` as decimal number or hex number (0x55).
165
+ See https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values
166
+ ERROR
167
+ end
168
+
169
+ Kitchen.logger.warn("Windows guest customization: No timezone passed, assuming UTC") unless timezone
170
+
171
+ product_id = guest_customization[:product_id]
172
+
173
+ # Try to look up and use a known, documented 120-day trial key
174
+ unless product_id
175
+ guest_os = src_vm.guest&.guestFullName
176
+ product_id = windows_kms_for_guest(guest_os)
177
+
178
+ Kitchen.logger.warn format("Windows guest customization:: Using KMS Key `%<key>s` for %<os>s", key: product_id, os: guest_os) if product_id
179
+ end
180
+
181
+ unless valid_windows_key? product_id
182
+ raise Support::GuestCustomizationOptionsError.new <<~ERROR
183
+ Windows customization requires `product_id` to work. Add a valid product key or
184
+ see https://docs.microsoft.com/en-us/windows-server/get-started/kmsclientkeys for KMS trial keys
185
+ ERROR
186
+ end
187
+
188
+ RbVmomi::VIM::CustomizationSysprep.new(
189
+ guiUnattended: RbVmomi::VIM::CustomizationGuiUnattended.new(
190
+ timeZone: timezone.to_i || DEFAULT_WINDOWS_TIMEZONE,
191
+ autoLogon: false,
192
+ autoLogonCount: 1
193
+ ),
194
+ identification: RbVmomi::VIM::CustomizationIdentification.new,
195
+ userData: RbVmomi::VIM::CustomizationUserData.new(
196
+ computerName: guest_hostname,
197
+ fullName: guest_customization[:org_name] || DEFAULT_WINDOWS_ORG,
198
+ orgName: guest_customization[:org_name] || DEFAULT_WINDOWS_ORG,
199
+ productId: product_id
200
+ )
201
+ )
202
+ end
203
+
204
+ # Check if a host is reachable
205
+ def up?(host)
206
+ check = Net::Ping::External.new(host)
207
+ check.ping?
208
+ end
209
+
210
+ # Retrieve a GVLK (evaluation key) for the named OS
211
+ #
212
+ # @param [String] name Name of the OS as reported by VMware
213
+ # @returns [String] GVLK key, if any
214
+ def windows_kms_for_guest(name)
215
+ WINDOWS_KMS_KEYS.fetch(name, false)
216
+ end
217
+
218
+ # Check format of Linux-specific timezone, according to VMware support
219
+ #
220
+ # @param [Integer] input Value to check for validity
221
+ # @returns [Boolean] if value is valid
222
+ def valid_linux_timezone?(input)
223
+ # Specific to VMware: https://kb.vmware.com/s/article/2145518
224
+ linux_timezone_pattern = %r{^[A-Z][A-Za-z]+\/[A-Z][-_+A-Za-z0-9]+$}
225
+
226
+ input.to_s.match? linux_timezone_pattern
227
+ end
228
+
229
+ # Check format of Windows-specific timezone
230
+ #
231
+ # @param [Integer] input Value to check for validity
232
+ # @returns [Boolean] if value is valid
233
+ def valid_windows_timezone?(input)
234
+ # Accept decimals and hex
235
+ # See https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values
236
+ windows_timezone_pattern = /^([0-9]+|0x[0-9a-fA-F]+)$/
237
+
238
+ input.to_s.match? windows_timezone_pattern
239
+ end
240
+
241
+ # Check for format of Windows Product IDs
242
+ #
243
+ # @param [String] input String to check
244
+ # @returns [Boolean] if value is in Windows Key format
245
+ def valid_windows_key?(input)
246
+ windows_key_pattern = /^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$/
247
+
248
+ input.to_s.match? windows_key_pattern
249
+ end
250
+
251
+ # Return Guest hostname to be configured and check for validity.
252
+ #
253
+ # @returns [String] New hostname to assign
254
+ def guest_hostname
255
+ hostname = guest_customization[:hostname] || options[:vm_name]
256
+
257
+ hostname_pattern = /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/
258
+ unless hostname.match?(hostname_pattern)
259
+ raise Support::GuestCustomizationError.new("Only letters, numbers or hyphens in hostnames allowed")
260
+ end
261
+
262
+ RbVmomi::VIM::CustomizationFixedName.new(name: hostname)
263
+ end
264
+
265
+ # Wait for vSphere task completion and subsequent IP address update (if any).
266
+ def guest_customization_wait
267
+ guest_customization_wait_task(guest_customization[:timeout_task] || DEFAULT_TIMEOUT_TASK)
268
+ guest_customization_wait_ip(guest_customization[:timeout_ip] || DEFAULT_TIMEOUT_IP)
269
+ end
270
+
271
+ # Wait for Guest customization to finish successfully.
272
+ #
273
+ # @param [Integer] timeout Timeout in seconds
274
+ # @param [Integer] sleep_time Time to wait between tries
275
+ def guest_customization_wait_task(timeout = 600, sleep_time = 10)
276
+ waited_seconds = 0
277
+
278
+ Kitchen.logger.info "Waiting for guest customization (timeout: #{timeout} seconds)..."
279
+
280
+ while waited_seconds < timeout
281
+ events = guest_customization_events
282
+
283
+ if events.any? { |event| event.is_a? RbVmomi::VIM::CustomizationSucceeded }
284
+ return
285
+ elsif (failed = events.detect { |event| event.is_a? RbVmomi::VIM::CustomizationFailed })
286
+ # Only matters for Linux, as Windows won't come up at all to report a failure via VMware Tools
287
+ raise Support::GuestCustomizationError.new("Customization of VM failed: #{failed.fullFormattedMessage}")
288
+ end
289
+
290
+ sleep(sleep_time)
291
+ waited_seconds += sleep_time
292
+ end
293
+
294
+ raise Support::GuestCustomizationError.new("Customization of VM did not complete within #{timeout} seconds.")
295
+ end
296
+
297
+ # Wait for new IP to be reported, if any.
298
+ #
299
+ # @param [Integer] timeout Timeout in seconds. Tools report every 30 seconds, Default: 30 seconds
300
+ # @param [Integer] sleep_time Time to wait between tries
301
+ def guest_customization_wait_ip(timeout = 30, sleep_time = 1)
302
+ return unless guest_customization_ip_change?
303
+
304
+ waited_seconds = 0
305
+
306
+ Kitchen.logger.info "Waiting for guest customization IP update..."
307
+
308
+ while waited_seconds < timeout
309
+ found_ip = wait_for_ip(timeout, 1.0)
310
+
311
+ return if found_ip == guest_customization[:ip_address]
312
+
313
+ sleep(sleep_time)
314
+ waited_seconds += sleep_time
315
+ end
316
+
317
+ raise Support::GuestCustomizationError.new("Customized IP was not reported within #{timeout} seconds.")
318
+ end
319
+
320
+ # Filter Customization events for the current VM
321
+ #
322
+ # @returns [Array<RbVmomi::VIM::CustomizationEvent>] All matching events
323
+ def guest_customization_events
324
+ vm_events %w{CustomizationSucceeded CustomizationFailed CustomizationStartedEvent}
325
+ end
326
+ end
327
+ end
metadata CHANGED
@@ -1,15 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kitchen-vcenter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.6
4
+ version: 2.9.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-22 00:00:00.000000000 Z
11
+ date: 2020-10-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-ping
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 2.0.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 2.0.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
13
33
  - !ruby/object:Gem::Dependency
14
34
  name: rbvmomi
15
35
  requirement: !ruby/object:Gem::Requirement
@@ -75,6 +95,7 @@ files:
75
95
  - lib/kitchen-vcenter/version.rb
76
96
  - lib/kitchen/driver/vcenter.rb
77
97
  - lib/support/clone_vm.rb
98
+ - lib/support/guest_customization.rb
78
99
  - lib/support/guest_operations.rb
79
100
  homepage: https://github.com/chef/kitchen-vcenter
80
101
  licenses:
@@ -98,5 +119,5 @@ requirements: []
98
119
  rubygems_version: 3.0.3
99
120
  signing_key:
100
121
  specification_version: 4
101
- summary: Test Kitchen driver for VMare vCenter
122
+ summary: Test Kitchen driver for VMware vCenter
102
123
  test_files: []