Module: BeakerPuppetHelpers::WindowsUtils
- Defined in:
- lib/beaker_puppet_helpers/windows_utils.rb
Overview
This module contains methods useful for Windows installs
Instance Method Summary collapse
-
#create_install_msi_batch_on(host, msi_path, msi_opts) ⇒ String
private
Given a host, path to MSI and MSI options, will create a batch file on the host, returning the path to the randomized batch file and the randomized log file.
-
#generic_install_msi_on(hosts, msi_path, msi_opts: {}, opts: {}) ⇒ Object
private
Installs a specified msi path on given hosts.
-
#get_agent_package_url(collection = 'openvox') ⇒ String
Given the puppet collection, returns the url of the newest msi available in the appropriate repo.
-
#get_system_temp_path(host) ⇒ String
(also: #get_temp_path)
Given a host, returns it’s system TEMP path.
-
#install_msi_on(hosts, msi_path, msi_opts: {}, opts: {}) ⇒ Object
private
Installs a specified MSI package on given hosts.
-
#msi_install_script(msi_path, msi_opts, log_path) ⇒ Object
private
Generates commands to be inserted into a Windows batch file to launch an MSI install.
Instance Method Details
#create_install_msi_batch_on(host, msi_path, msi_opts) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Given a host, path to MSI and MSI options, will create a batch file
on the host, returning the path to the randomized batch file and
the randomized log file
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/beaker_puppet_helpers/windows_utils.rb', line 118 def create_install_msi_batch_on(host, msi_path, msi_opts) = Time.new.strftime('%Y-%m-%d_%H.%M.%S') tmp_path = host.system_temp_path.tr('/', '\\') batch_name = "install-puppet-msi-#{timestamp}.bat" batch_path = "#{tmp_path}#{host.scp_separator}#{batch_name}" log_path = "#{tmp_path}\\install-puppet-#{timestamp}.log" Tempfile.open(batch_name) do |tmp_file| batch_contents = msi_install_script(msi_path, msi_opts, log_path) File.open(tmp_file.path, 'w') { |file| file.puts(batch_contents) } host.do_scp_to(tmp_file.path, batch_path, {}) end [batch_path, log_path] end |
#generic_install_msi_on(hosts, msi_path, msi_opts: {}, opts: {}) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Installs a specified msi path on given hosts
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/beaker_puppet_helpers/windows_utils.rb', line 269 def generic_install_msi_on(hosts, msi_path, msi_opts: {}, opts: {}) # If the msi patch matches a collection, get the url for the latest msi available for that collection = /^(puppet|openvox)\d*$/.match?(msi_path) ? get_agent_package_url(msi_path) : msi_path block_on hosts do |host| batch_path, log_file = create_install_msi_batch_on(host, , msi_opts) # Powershell command looses an escaped slash resulting in cygwin relative path # See https://github.com/puppetlabs/beaker/pull/1626#issuecomment-621341555 log_file_escaped = log_file.gsub('\\', '\\\\\\') # begin / rescue here so that we can reuse existing error msg propagation begin # 1641 = ERROR_SUCCESS_REBOOT_INITIATED # 3010 = ERROR_SUCCESS_REBOOT_REQUIRED on host, Beaker::Command.new("\"#{batch_path}\"", [], { cmdexe: true }), acceptable_exit_codes: [0, 1641, 3010] rescue StandardError logger.info(file_contents_on(host, log_file_escaped)) raise end logger.info(file_contents_on(host, log_file_escaped)) if opts[:debug] host.close unless host.is_cygwin? end end |
#get_agent_package_url(collection = 'openvox') ⇒ String
Given the puppet collection, returns the url of the newest msi available in the appropriate repo
29 30 31 32 33 34 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 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/beaker_puppet_helpers/windows_utils.rb', line 29 def get_agent_package_url(collection = 'openvox') windows_package_base_url = if collection.start_with?('puppet') 'https://downloads.puppetlabs.com/windows/' elsif collection.start_with?('openvox') 'https://downloads.voxpupuli.org/windows/' else raise "Unsupported collection: #{collection}" end # If the collection ends in a number, we can infer the package url directly if /\d+$/.match?(collection) windows_package_url = "#{windows_package_base_url}#{collection}/" else # Obtain the list of collections from the appropriate base url and pick the latest base_url = URI.parse(windows_package_base_url) base_response = Net::HTTP.get_response(base_url) raise "Failed to fetch URL: #{base_response.code} #{base_response.message}" unless base_response.is_a?(Net::HTTPSuccess) base_doc = Nokogiri::HTML(base_response.body) collection_dirs = base_doc.css('a').filter_map { |a| a['href'] }.grep(/^#{collection}\d+/) raise "No collections found at #{base_url} for colleciton #{collection}" if collection_dirs.empty? latest_collection = collection_dirs.max_by do |collection_version| # Grab the digits before the slash and convert to integer collection_version[/\d+/].to_i end windows_package_url = "#{windows_package_base_url}#{latest_collection}" end url = URI.parse(windows_package_url) response = Net::HTTP.get_response(url) # Fetch and parse the page raise "Failed to fetch URL: #{response.code} #{response.message}" unless response.is_a?(Net::HTTPSuccess) doc = Nokogiri::HTML(response.body) # Create the regex for the agent package base_collection_name = collection.gsub(/\d+$/, '') agent_regex = /#{base_collection_name}-agent-(\d+\.\d+\.\d+)-.*\.msi$/i # Extract all hrefs that look like the appropriate MSI files files = doc.css('a').filter_map { |a| a['href'] }.grep(agent_regex) raise "No MSI files found at #{windows_package_url}" if files.empty? latest_msi = files.max_by do |file| version_str = file.match(agent_regex)[1] Gem::Version.new(version_str) end # Remove index.html if it exists in the windows_package_url windows_package_repo = windows_package_url.sub(/index\.html$/, '') # Return the full url to the latest msi "#{windows_package_repo}#{latest_msi}" end |
#get_system_temp_path(host) ⇒ String Also known as: get_temp_path
Given a host, returns it’s system TEMP path
17 18 19 |
# File 'lib/beaker_puppet_helpers/windows_utils.rb', line 17 def get_system_temp_path(host) host.system_temp_path end |
#install_msi_on(hosts, msi_path, msi_opts: {}, opts: {}) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Installs a specified MSI package on given hosts.
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 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/beaker_puppet_helpers/windows_utils.rb', line 177 def install_msi_on(hosts, msi_path, msi_opts: {}, opts: {}) # If the msi patch matches a collection, get the url for the latest msi available for that collection = /^(puppet|openvox)\d*$/.match?(msi_path) ? get_agent_package_url(msi_path) : msi_path block_on hosts do |host| msi_opts['PUPPET_AGENT_STARTUP_MODE'] ||= 'Manual' batch_path, log_file = create_install_msi_batch_on(host, , msi_opts) # Powershell command looses an escaped slash resulting in cygwin relative path # See https://github.com/puppetlabs/beaker/pull/1626#issuecomment-621341555 log_file_escaped = log_file.gsub('\\', '\\\\\\') # begin / rescue here so that we can reuse existing error msg propagation begin # 1641 = ERROR_SUCCESS_REBOOT_INITIATED # 3010 = ERROR_SUCCESS_REBOOT_REQUIRED on host, Beaker::Command.new("\"#{batch_path}\"", [], { cmdexe: true }), acceptable_exit_codes: [0, 1641, 3010] rescue StandardError logger.info(file_contents_on(host, log_file_escaped)) raise end logger.info(file_contents_on(host, log_file_escaped)) if opts[:debug] unless host.is_cygwin? # Enable the PATH updates host.close # Some systems require a full reboot to trigger the enabled path host.reboot unless on(host, Beaker::Command.new('puppet -h', [], { cmdexe: true }), accept_all_exit_codes: true).exit_code.zero? end # verify service status post install # if puppet service exists, then pe-puppet is not queried # if puppet service does not exist, pe-puppet is queried and that exit code is used # therefore, this command will always exit 0 if either service is installed # # We also take advantage of this output to verify the startup # settings are honored as supplied to the MSI on host, Beaker::Command.new('sc qc puppet || sc qc pe-puppet', [], { cmdexe: true }) do |result| output = result.stdout startup_mode = msi_opts['PUPPET_AGENT_STARTUP_MODE'].upcase search = case startup_mode # rubocop:disable Style/HashLikeCase when 'AUTOMATIC' { code: 2, name: 'AUTO_START' } when 'MANUAL' { code: 3, name: 'DEMAND_START' } when 'DISABLED' { code: 4, name: 'DISABLED' } end raise "puppet service startup mode did not match supplied MSI option '#{startup_mode}'" unless /^\s+START_TYPE\s+:\s+#{search[:code]}\s+#{search[:name]}/.match?(output) end # (PA-514) value for PUPPET_AGENT_STARTUP_MODE should be present in # registry and honored after install/upgrade. reg_key = if host.is_x86_64? 'HKLM\\SOFTWARE\\Wow6432Node\\Puppet Labs\\PuppetInstaller' else 'HKLM\\SOFTWARE\\Puppet Labs\\PuppetInstaller' end reg_query_command = %(reg query "#{reg_key}" /v "RememberedPuppetAgentStartupMode" | findstr #{msi_opts['PUPPET_AGENT_STARTUP_MODE']}) on host, Beaker::Command.new(reg_query_command, [], { cmdexe: true }) # emit the misc/versions.txt file which contains component versions for # puppet, facter, hiera, pxp-agent, packaging and vendored Ruby [ "'%PROGRAMFILES%\\Puppet Labs\\puppet\\misc\\versions.txt'", "'%PROGRAMFILES(X86)%\\Puppet Labs\\puppet\\misc\\versions.txt'", ].each do |path| result = on(host, "cmd /c type #{path}", accept_all_exit_codes: true) if result.exit_code.zero? logger.info(result.stdout) break end end end end |
#msi_install_script(msi_path, msi_opts, log_path) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Generates commands to be inserted into a Windows batch file to launch an MSI install
91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/beaker_puppet_helpers/windows_utils.rb', line 91 def msi_install_script(msi_path, msi_opts, log_path) # msiexec requires backslashes in file paths launched under cmd.exe start /w url_pattern = %r{^(https?|file)://} msi_path = msi_path.tr('/', '\\') unless msi_path&.match?(url_pattern) msi_params = msi_opts.map { |k, v| "#{k}=#{v}" }.join(' ') # msiexec requires quotes around paths with backslashes - c:\ or file://c: # not strictly needed for http:// but it simplifies this code " start /w msiexec.exe /i \"\#{msi_path}\" /qn /L*V \#{log_path} \#{msi_params}\n exit /B %errorlevel%\n BATCH\nend\n" |