Module: Support::GuestCustomization
- Included in:
- CloneVm
- Defined in:
- lib/support/guest_customization.rb
Constant Summary collapse
- DEFAULT_LINUX_TIMEZONE =
"Etc/UTC".freeze
- DEFAULT_WINDOWS_ORG =
"TestKitchen".freeze
- DEFAULT_WINDOWS_TIMEZONE =
Etc/UTC
0x80000050
- DEFAULT_TIMEOUT_TASK =
600
- DEFAULT_TIMEOUT_IP =
60
- WINDOWS_KMS_KEYS =
Generic Volume License Keys for temporary Windows Server setup.
{ "Microsoft Windows Server 2019 (64-bit)" => "N69G4-B89J2-4G8F4-WWYCC-J464C", "Microsoft Windows Server 2016 (64-bit)" => "WC2BQ-8NRM3-FDDYY-2BFGV-KHKQY", "Microsoft Windows Server 2012R2 (64-bit)" => "D2N9P-3P6X9-2R39C-7RTCD-MDVJX", "Microsoft Windows Server 2012 (64-bit)" => "BN3D2-R7TKB-3YPBD-8DRP2-27GG4", }.freeze
Instance Method Summary collapse
-
#guest_customization ⇒ Object
Configuration values for Guest Customization.
-
#guest_customization_events ⇒ Object
Filter Customization events for the current VM.
-
#guest_customization_identity ⇒ Object
Return OS-specific CustomizationIdentity object.
-
#guest_customization_identity_linux ⇒ Object
Construct Linux-specific customization information.
-
#guest_customization_identity_windows ⇒ Object
Construct Windows-specific customization information.
-
#guest_customization_ip_change? ⇒ Boolean
Check if an IP change is requested.
-
#guest_customization_spec ⇒ Object
Build CustomizationSpec for Guest OS Customization.
-
#guest_customization_validate_options ⇒ Object
Check options for existance and format.
-
#guest_customization_wait ⇒ Object
Wait for vSphere task completion and subsequent IP address update (if any).
-
#guest_customization_wait_ip(timeout = 30, sleep_time = 1) ⇒ Object
Wait for new IP to be reported, if any.
-
#guest_customization_wait_task(timeout = 600, sleep_time = 10) ⇒ Object
Wait for Guest customization to finish successfully.
-
#guest_hostname ⇒ Object
Return Guest hostname to be configured and check for validity.
-
#up?(host) ⇒ Boolean
Check if a host is reachable.
-
#valid_linux_timezone?(input) ⇒ Boolean
Check format of Linux-specific timezone, according to VMware support.
-
#valid_windows_key?(input) ⇒ Boolean
Check for format of Windows Product IDs.
-
#valid_windows_timezone?(input) ⇒ Boolean
Check format of Windows-specific timezone.
-
#windows_kms_for_guest(name) ⇒ Object
Retrieve a GVLK (evaluation key) for the named OS.
Instance Method Details
#guest_customization ⇒ Object
Configuration values for Guest Customization
28 29 30 |
# File 'lib/support/guest_customization.rb', line 28 def guest_customization [:guest_customization] end |
#guest_customization_events ⇒ Object
Filter Customization events for the current VM
332 333 334 |
# File 'lib/support/guest_customization.rb', line 332 def guest_customization_events vm_events %w{CustomizationSucceeded CustomizationFailed CustomizationStartedEvent} end |
#guest_customization_identity ⇒ Object
Return OS-specific CustomizationIdentity object
129 130 131 132 133 134 135 136 137 |
# File 'lib/support/guest_customization.rb', line 129 def guest_customization_identity if linux? guest_customization_identity_linux elsif windows? guest_customization_identity_windows else raise Support::GuestCustomizationError.new("Unknown OS, no valid customization found") end end |
#guest_customization_identity_linux ⇒ Object
Construct Linux-specific customization information
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# File 'lib/support/guest_customization.rb', line 140 def guest_customization_identity_linux timezone = guest_customization[:timezone] if timezone && !valid_linux_timezone?(timezone) raise Support::GuestCustomizationError.new <<~ERROR Linux customization requires `timezone` in `Area/Location` format. See https://kb.vmware.com/s/article/2145518 ERROR end Kitchen.logger.warn("Linux guest customization: No timezone passed, assuming UTC") unless timezone RbVmomi::VIM::CustomizationLinuxPrep.new( domain: guest_customization[:dns_domain], hostName: guest_hostname, hwClockUTC: true, timeZone: timezone || DEFAULT_LINUX_TIMEZONE ) end |
#guest_customization_identity_windows ⇒ Object
Construct Windows-specific customization information
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
# File 'lib/support/guest_customization.rb', line 160 def guest_customization_identity_windows timezone = guest_customization[:timezone] if timezone && !valid_windows_timezone?(timezone) raise Support::GuestCustomizationOptionsError.new <<~ERROR Windows customization requires `timezone` as decimal number or hex number (0x55). See https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values ERROR end Kitchen.logger.warn("Windows guest customization: No timezone passed, assuming UTC") unless timezone product_id = guest_customization[:product_id] # Try to look up and use a known, documented 120-day trial key unless product_id guest_os = src_vm.guest&.guestFullName product_id = windows_kms_for_guest(guest_os) Kitchen.logger.warn format("Windows guest customization:: Using KMS Key `%<key>s` for %<os>s", key: product_id, os: guest_os) if product_id end unless valid_windows_key? product_id raise Support::GuestCustomizationOptionsError.new <<~ERROR Windows customization requires `product_id` to work. Add a valid product key or see https://docs.microsoft.com/en-us/windows-server/get-started/kmsclientkeys for KMS trial keys ERROR end customization_pass = nil if guest_customization[:administrator_password] customization_pass = RbVmomi::VIM::CustomizationPassword.new( plainText: true, value: guest_customization[:administrator_password] ) end RbVmomi::VIM::CustomizationSysprep.new( guiUnattended: RbVmomi::VIM::CustomizationGuiUnattended.new( timeZone: timezone.to_i || DEFAULT_WINDOWS_TIMEZONE, autoLogon: false, autoLogonCount: 1, password: customization_pass ), identification: RbVmomi::VIM::CustomizationIdentification.new, userData: RbVmomi::VIM::CustomizationUserData.new( computerName: guest_hostname, fullName: guest_customization[:org_name] || DEFAULT_WINDOWS_ORG, orgName: guest_customization[:org_name] || DEFAULT_WINDOWS_ORG, productId: product_id ) ) end |
#guest_customization_ip_change? ⇒ Boolean
Check if an IP change is requested
124 125 126 |
# File 'lib/support/guest_customization.rb', line 124 def guest_customization_ip_change? guest_customization[:ip_address] end |
#guest_customization_spec ⇒ Object
Build CustomizationSpec for Guest OS Customization
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/support/guest_customization.rb', line 35 def guest_customization_spec return unless guest_customization if guest_customization[:ip_address] customized_ip = RbVmomi::VIM::CustomizationIPSettings.new( ip: RbVmomi::VIM::CustomizationFixedIp(ipAddress: guest_customization[:ip_address]), gateway: guest_customization[:gateway], subnetMask: guest_customization[:subnet_mask], dnsDomain: guest_customization[:dns_domain] ) else customized_ip = RbVmomi::VIM::CustomizationIPSettings.new( ip: RbVmomi::VIM::CustomizationDhcpIpGenerator.new, dnsDomain: guest_customization[:dns_domain] ) end RbVmomi::VIM::CustomizationSpec.new( identity: guest_customization_identity, globalIPSettings: RbVmomi::VIM::CustomizationGlobalIPSettings.new( dnsServerList: guest_customization[:dns_server_list], dnsSuffixList: guest_customization[:dns_suffix_list] ), nicSettingMap: [RbVmomi::VIM::CustomizationAdapterMapping.new( adapter: customized_ip )] ) end |
#guest_customization_validate_options ⇒ Object
Check options for existance and format
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/support/guest_customization.rb', line 69 def if guest_customization_ip_change? unless ip?(guest_customization[:ip_address]) raise Support::GuestCustomizationOptionsError.new("Parameter `ip_address` is required to be formatted as an IPv4 address") end unless guest_customization[:subnet_mask] raise Support::GuestCustomizationOptionsError.new("Parameter `subnet_mask` is required if assigning a fixed IPv4 address") end unless ip?(guest_customization[:subnet_mask]) raise Support::GuestCustomizationOptionsError.new("Parameter `subnet_mask` is required to be formatted as an IPv4 address") end if up?(guest_customization[:ip_address]) raise Support::GuestCustomizationOptionsError.new("Parameter `ip_address` points to a host reachable via ICMP") unless guest_customization[:continue_on_ip_conflict] Kitchen.logger.warn("Continuing customization despite `ip_address` conflicting with a reachable host per user request") end end if guest_customization[:gateway] unless guest_customization[:gateway].is_a?(Array) raise Support::GuestCustomizationOptionsError.new("Parameter `gateway` must be an array") end guest_customization[:gateway].each do |v| unless ip?(v) raise Support::GuestCustomizationOptionsError.new("Parameter `gateway` is required to be formatted as an IPv4 address") end end end required = %i{dns_domain dns_server_list dns_suffix_list} missing = required - guest_customization.keys unless missing.empty? raise Support::GuestCustomizationOptionsError.new("Parameters `#{missing.join("`, `")}` are required to support guest customization") end guest_customization[:dns_server_list].each do |v| unless ip?(v) raise Support::GuestCustomizationOptionsError.new("Parameter `dns_server_list` is required to be formatted as an IPv4 address") end end if !guest_customization[:dns_server_list].is_a?(Array) raise Support::GuestCustomizationOptionsError.new("Parameter `dns_server_list` must be an array") elsif !guest_customization[:dns_suffix_list].is_a?(Array) raise Support::GuestCustomizationOptionsError.new("Parameter `dns_suffix_list` must be an array") end end |
#guest_customization_wait ⇒ Object
Wait for vSphere task completion and subsequent IP address update (if any).
275 276 277 278 |
# File 'lib/support/guest_customization.rb', line 275 def guest_customization_wait guest_customization_wait_task(guest_customization[:timeout_task] || DEFAULT_TIMEOUT_TASK) guest_customization_wait_ip(guest_customization[:timeout_ip] || DEFAULT_TIMEOUT_IP) end |
#guest_customization_wait_ip(timeout = 30, sleep_time = 1) ⇒ Object
Wait for new IP to be reported, if any.
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/support/guest_customization.rb', line 310 def guest_customization_wait_ip(timeout = 30, sleep_time = 1) return unless guest_customization_ip_change? waited_seconds = 0 Kitchen.logger.info "Waiting for guest customization IP update..." while waited_seconds < timeout found_ip = wait_for_ip(timeout, 1.0) return if found_ip == guest_customization[:ip_address] sleep(sleep_time) waited_seconds += sleep_time end raise Support::GuestCustomizationError.new("Customized IP was not reported within #{timeout} seconds.") end |
#guest_customization_wait_task(timeout = 600, sleep_time = 10) ⇒ Object
Wait for Guest customization to finish successfully.
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 |
# File 'lib/support/guest_customization.rb', line 284 def guest_customization_wait_task(timeout = 600, sleep_time = 10) waited_seconds = 0 Kitchen.logger.info "Waiting for guest customization (timeout: #{timeout} seconds)..." while waited_seconds < timeout events = guest_customization_events if events.any? { |event| event.is_a? RbVmomi::VIM::CustomizationSucceeded } return elsif (failed = events.detect { |event| event.is_a? RbVmomi::VIM::CustomizationFailed }) # Only matters for Linux, as Windows won't come up at all to report a failure via VMware Tools raise Support::GuestCustomizationError.new("Customization of VM failed: #{failed.fullFormattedMessage}") end sleep(sleep_time) waited_seconds += sleep_time end raise Support::GuestCustomizationError.new("Customization of VM did not complete within #{timeout} seconds.") end |
#guest_hostname ⇒ Object
Return Guest hostname to be configured and check for validity.
263 264 265 266 267 268 269 270 271 272 |
# File 'lib/support/guest_customization.rb', line 263 def guest_hostname hostname = guest_customization[:hostname] || [:vm_name] hostname_pattern = /^([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])$/ unless hostname.match?(hostname_pattern) raise Support::GuestCustomizationError.new("Only letters, numbers or hyphens in hostnames allowed") end RbVmomi::VIM::CustomizationFixedName.new(name: hostname) end |
#up?(host) ⇒ Boolean
Check if a host is reachable
214 215 216 217 |
# File 'lib/support/guest_customization.rb', line 214 def up?(host) check = Net::Ping::External.new(host) check.ping? end |
#valid_linux_timezone?(input) ⇒ Boolean
Check format of Linux-specific timezone, according to VMware support
231 232 233 234 235 236 |
# File 'lib/support/guest_customization.rb', line 231 def valid_linux_timezone?(input) # Specific to VMware: https://kb.vmware.com/s/article/2145518 linux_timezone_pattern = %r{^[A-Z][A-Za-z]+\/[A-Z][-_+A-Za-z0-9]+$} input.to_s.match? linux_timezone_pattern end |
#valid_windows_key?(input) ⇒ Boolean
Check for format of Windows Product IDs
254 255 256 257 258 |
# File 'lib/support/guest_customization.rb', line 254 def valid_windows_key?(input) windows_key_pattern = /^[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}-[A-Z0-9]{5}$/ input.to_s.match? windows_key_pattern end |
#valid_windows_timezone?(input) ⇒ Boolean
Check format of Windows-specific timezone
242 243 244 245 246 247 248 |
# File 'lib/support/guest_customization.rb', line 242 def valid_windows_timezone?(input) # Accept decimals and hex # See https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values windows_timezone_pattern = /^([0-9]+|0x[0-9a-fA-F]+)$/ input.to_s.match? windows_timezone_pattern end |
#windows_kms_for_guest(name) ⇒ Object
Retrieve a GVLK (evaluation key) for the named OS
223 224 225 |
# File 'lib/support/guest_customization.rb', line 223 def windows_kms_for_guest(name) WINDOWS_KMS_KEYS.fetch(name, false) end |