beaker_puppet_helpers 2.3.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7dd5d465e4187f1f158245ae58677b5111b37638c28167786e7eb2881b6cfd9
4
- data.tar.gz: 7321b2de3401576cac8c5cd2c9792601c4461c43ef63b0e8835356be22ea05f6
3
+ metadata.gz: b46b0611ed08ea9cc2e228f4e9ab103f324eeb98fc5d1d2a5fc2633f59345454
4
+ data.tar.gz: 404c17370e99ee3f2af756b00856df25e5df266e81b42a2ae2e630fd6d218a75
5
5
  SHA512:
6
- metadata.gz: cc57cc0e895adbb49bb7b9970c02093142e25f043eb40abadb6649ff13e41b45c9c4f8a797d6c946119566958da8be0c40d07974bb0d0ca86cef2c9c3fb32d9d
7
- data.tar.gz: aa2617a52e5b9a187e09b4c895b01e198014b98ee57c2259e9adfbe1b6782a83006d56f838b4f5865fed2432057fe7498c80517658c82e31c8fb38c56d89db74
6
+ metadata.gz: 57b78e08e951861a47093f90dd539378f70c9bc2f9c225b03fad61d2df5e7df7cc94855eb19d8f1bf9d97f721873b2ba67a4196d124c12686c4dd9d2a47cc486
7
+ data.tar.gz: 8908b4612c292892657c8c87f09a52c49512e4fb85a4ce31ab52702f29e181151eeb40d9c8a791e4a1ff1f646499f9a15aca2527e8bdba2d82e75606db0b7997
@@ -1,17 +1,17 @@
1
1
  version: 2
2
2
  updates:
3
- # raise PRs for gem updates
4
- - package-ecosystem: bundler
5
- directory: "/"
6
- schedule:
7
- interval: daily
8
- time: "13:00"
9
- open-pull-requests-limit: 10
3
+ # raise PRs for gem updates
4
+ - package-ecosystem: bundler
5
+ directory: "/"
6
+ schedule:
7
+ interval: daily
8
+ time: "13:00"
9
+ open-pull-requests-limit: 10
10
10
 
11
- # Maintain dependencies for GitHub Actions
12
- - package-ecosystem: github-actions
13
- directory: "/"
14
- schedule:
15
- interval: daily
16
- time: "13:00"
17
- open-pull-requests-limit: 10
11
+ # Maintain dependencies for GitHub Actions
12
+ - package-ecosystem: github-actions
13
+ directory: "/"
14
+ schedule:
15
+ interval: daily
16
+ time: "13:00"
17
+ open-pull-requests-limit: 10
@@ -15,7 +15,7 @@ jobs:
15
15
  name: Build the gem
16
16
  runs-on: ubuntu-24.04
17
17
  steps:
18
- - uses: actions/checkout@v4
18
+ - uses: actions/checkout@v5
19
19
  - name: Install Ruby
20
20
  uses: ruby/setup-ruby@v1
21
21
  with:
@@ -39,7 +39,7 @@ jobs:
39
39
  contents: write # clone repo and create release
40
40
  steps:
41
41
  - name: Download gem from GitHub cache
42
- uses: actions/download-artifact@v4
42
+ uses: actions/download-artifact@v5
43
43
  with:
44
44
  name: gem-artifact
45
45
  - name: Create Release
@@ -56,7 +56,7 @@ jobs:
56
56
  packages: write # publish to rubygems.pkg.github.com
57
57
  steps:
58
58
  - name: Download gem from GitHub cache
59
- uses: actions/download-artifact@v4
59
+ uses: actions/download-artifact@v5
60
60
  with:
61
61
  name: gem-artifact
62
62
  - name: Publish gem to GitHub packages
@@ -73,7 +73,7 @@ jobs:
73
73
  id-token: write # rubygems.org authentication
74
74
  steps:
75
75
  - name: Download gem from GitHub cache
76
- uses: actions/download-artifact@v4
76
+ uses: actions/download-artifact@v5
77
77
  with:
78
78
  name: gem-artifact
79
79
  - uses: rubygems/configure-rubygems-credentials@v1.0.0
@@ -92,7 +92,7 @@ jobs:
92
92
  - release-to-rubygems
93
93
  steps:
94
94
  - name: Download gem from GitHub cache
95
- uses: actions/download-artifact@v4
95
+ uses: actions/download-artifact@v5
96
96
  with:
97
97
  name: gem-artifact
98
98
  - name: Install Ruby
@@ -7,6 +7,9 @@ on:
7
7
  branches:
8
8
  - master
9
9
 
10
+ permissions:
11
+ contents: read
12
+
10
13
  env:
11
14
  BEAKER_HYPERVISOR: docker
12
15
 
@@ -16,7 +19,7 @@ jobs:
16
19
  outputs:
17
20
  ruby: ${{ steps.ruby.outputs.versions }}
18
21
  steps:
19
- - uses: actions/checkout@v4
22
+ - uses: actions/checkout@v5
20
23
  - name: Install Ruby ${{ matrix.ruby }}
21
24
  uses: ruby/setup-ruby@v1
22
25
  with:
@@ -36,7 +39,7 @@ jobs:
36
39
  ruby: ${{ fromJSON(needs.rubocop_and_matrix.outputs.ruby) }}
37
40
  name: Ruby ${{ matrix.ruby }}
38
41
  steps:
39
- - uses: actions/checkout@v4
42
+ - uses: actions/checkout@v5
40
43
  - name: Install Ruby ${{ matrix.ruby }}
41
44
  uses: ruby/setup-ruby@v1
42
45
  with:
data/.rubocop.yml CHANGED
@@ -12,3 +12,6 @@ RSpec/SubjectStub:
12
12
 
13
13
  RSpec/StubbedMock:
14
14
  Enabled: false
15
+
16
+ AllCops:
17
+ TargetRubyVersion: 3.2
data/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [3.1.0](https://github.com/voxpupuli/beaker_puppet_helpers/tree/3.1.0) (2025-09-22)
6
+
7
+ [Full Changelog](https://github.com/voxpupuli/beaker_puppet_helpers/compare/3.0.0...3.1.0)
8
+
9
+ **Implemented enhancements:**
10
+
11
+ - \(\#77\) Add support for installing openvox on windows [\#78](https://github.com/voxpupuli/beaker_puppet_helpers/pull/78) ([michael-riddle](https://github.com/michael-riddle))
12
+
13
+ **Fixed bugs:**
14
+
15
+ - gemspec: Dont set upper limit for ruby version [\#80](https://github.com/voxpupuli/beaker_puppet_helpers/pull/80) ([bastelfreak](https://github.com/bastelfreak))
16
+
17
+ ## [3.0.0](https://github.com/voxpupuli/beaker_puppet_helpers/tree/3.0.0) (2025-08-07)
18
+
19
+ [Full Changelog](https://github.com/voxpupuli/beaker_puppet_helpers/compare/2.3.0...3.0.0)
20
+
21
+ **Breaking changes:**
22
+
23
+ - Require Ruby 3.2 or newer [\#73](https://github.com/voxpupuli/beaker_puppet_helpers/pull/73) ([bastelfreak](https://github.com/bastelfreak))
24
+
25
+ **Implemented enhancements:**
26
+
27
+ - beaker: Allow 7.x [\#72](https://github.com/voxpupuli/beaker_puppet_helpers/pull/72) ([bastelfreak](https://github.com/bastelfreak))
28
+
29
+ **Merged pull requests:**
30
+
31
+ - CI: Dont test on Debian 10 [\#71](https://github.com/voxpupuli/beaker_puppet_helpers/pull/71) ([bastelfreak](https://github.com/bastelfreak))
32
+
5
33
  ## [2.3.0](https://github.com/voxpupuli/beaker_puppet_helpers/tree/2.3.0) (2025-06-15)
6
34
 
7
35
  [Full Changelog](https://github.com/voxpupuli/beaker_puppet_helpers/compare/2.2.0...2.3.0)
data/Gemfile CHANGED
@@ -4,6 +4,8 @@ source ENV['GEM_SOURCE'] || 'https://rubygems.org'
4
4
 
5
5
  gemspec
6
6
 
7
+ gem 'nokogiri', require: false
8
+ gem 'open-uri', require: false
7
9
  gem 'rake', '~> 13.0', groups: %i[development test release]
8
10
 
9
11
  group :development do
@@ -12,11 +14,9 @@ group :development do
12
14
  gem 'yard'
13
15
  end
14
16
 
15
- group :rubocop do
16
- gem 'voxpupuli-rubocop', '~> 3.1.0'
17
- end
18
-
19
17
  group :test do
18
+ gem 'fakefs', require: false
19
+ gem 'irb', require: false
20
20
  gem 'rspec', '~> 3.0'
21
21
  end
22
22
 
data/Rakefile CHANGED
@@ -35,7 +35,7 @@ DESC
35
35
  task :acceptance do
36
36
  hosts = {
37
37
  aio: %w[centos9 debian11 debian12],
38
- foss: %w[debian10 debian11 debian12 fedora37 fedora38],
38
+ foss: %w[debian11 debian12 fedora37 fedora38],
39
39
  }
40
40
  default_hosts = hosts.map { |type, h| h.map { |host| "#{host}-64{type=#{type}}" }.join('-') }.join('-')
41
41
  hosts = ENV['BEAKER_HOSTS'] || default_hosts
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'beaker_puppet_helpers'
5
- s.version = '2.3.0'
5
+ s.version = '3.1.0'
6
6
  s.authors = ['Vox Pupuli']
7
7
  s.email = ['voxpupuli@groups.io']
8
8
  s.homepage = 'https://github.com/voxpupuli/beaker_puppet_helpers'
@@ -10,12 +10,14 @@ Gem::Specification.new do |s|
10
10
  s.description = 'For use for the Beaker acceptance testing tool'
11
11
  s.license = 'Apache-2.0'
12
12
 
13
- s.required_ruby_version = '>= 2.7', '< 4'
13
+ s.required_ruby_version = '>= 3.2'
14
14
 
15
15
  s.files = `git ls-files`.split("\n")
16
16
  s.require_paths = ['lib']
17
17
 
18
18
  # Run time dependencies
19
- s.add_dependency 'beaker', '>= 5.8.1', '< 7'
19
+ s.add_dependency 'beaker', '>= 5.8.1', '< 8'
20
20
  s.add_dependency 'puppet-modulebuilder', '>= 0.3', '< 3'
21
+
22
+ s.add_development_dependency 'voxpupuli-rubocop', '~> 4.1.0'
21
23
  end
@@ -189,8 +189,8 @@ module BeakerPuppetHelpers
189
189
  # @return [Array<Result>, Result, nil] An array of results, a result
190
190
  # object, or nil. Check {Beaker::Shared::HostManager#run_block_on} for
191
191
  # more details on this.
192
- def apply_manifest(manifest, opts = {}, &block)
193
- apply_manifest_on(default, manifest, opts, &block)
192
+ def apply_manifest(manifest, opts = {}, &)
193
+ apply_manifest_on(default, manifest, opts, &)
194
194
  end
195
195
 
196
196
  # Get a facter fact from a provided host
@@ -0,0 +1,295 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'nokogiri'
5
+ require 'open-uri'
6
+
7
+ module BeakerPuppetHelpers
8
+ #
9
+ # This module contains methods useful for Windows installs
10
+ #
11
+ module WindowsUtils
12
+ # Given a host, returns it's system TEMP path
13
+ #
14
+ # @param [Host] host An object implementing {Beaker::Hosts}'s interface.
15
+ #
16
+ # @return [String] system temp path
17
+ def get_system_temp_path(host)
18
+ host.system_temp_path
19
+ end
20
+ alias get_temp_path get_system_temp_path
21
+
22
+ # Given the puppet collection, returns the url of the newest msi available in the appropriate repo
23
+ #
24
+ # @param [String]
25
+ # The collection to install. The default (openvox) is the latest
26
+ # available version. Can also be openvox8, puppet8 and others.
27
+ #
28
+ # @return [String] url of the newest msi available in the package repo
29
+ def get_agent_package_url(collection = 'openvox')
30
+ windows_package_base_url =
31
+ if collection.start_with?('puppet')
32
+ 'https://downloads.puppetlabs.com/windows/'
33
+ elsif collection.start_with?('openvox')
34
+ 'https://downloads.voxpupuli.org/windows/'
35
+ else
36
+ raise "Unsupported collection: #{collection}"
37
+ end
38
+ # If the collection ends in a number, we can infer the package url directly
39
+ if /\d+$/.match?(collection)
40
+ windows_package_url = "#{windows_package_base_url}#{collection}/"
41
+ else
42
+ # Obtain the list of collections from the appropriate base url and pick the latest
43
+ base_url = URI.parse(windows_package_base_url)
44
+ base_response = Net::HTTP.get_response(base_url)
45
+ raise "Failed to fetch URL: #{base_response.code} #{base_response.message}" unless base_response.is_a?(Net::HTTPSuccess)
46
+
47
+ base_doc = Nokogiri::HTML(base_response.body)
48
+ collection_dirs = base_doc.css('a').filter_map { |a| a['href'] }.grep(/^#{collection}\d+/)
49
+ raise "No collections found at #{base_url} for colleciton #{collection}" if collection_dirs.empty?
50
+
51
+ latest_collection = collection_dirs.max_by do |collection_version|
52
+ # Grab the digits before the slash and convert to integer
53
+ collection_version[/\d+/].to_i
54
+ end
55
+ windows_package_url = "#{windows_package_base_url}#{latest_collection}"
56
+ end
57
+ url = URI.parse(windows_package_url)
58
+ response = Net::HTTP.get_response(url)
59
+
60
+ # Fetch and parse the page
61
+ raise "Failed to fetch URL: #{response.code} #{response.message}" unless response.is_a?(Net::HTTPSuccess)
62
+
63
+ doc = Nokogiri::HTML(response.body)
64
+
65
+ # Create the regex for the agent package
66
+ base_collection_name = collection.gsub(/\d+$/, '')
67
+ agent_regex = /#{base_collection_name}-agent-(\d+\.\d+\.\d+)-.*\.msi$/i
68
+ # Extract all hrefs that look like the appropriate MSI files
69
+ files = doc.css('a').filter_map { |a| a['href'] }.grep(agent_regex)
70
+
71
+ raise "No MSI files found at #{windows_package_url}" if files.empty?
72
+
73
+ latest_msi = files.max_by do |file|
74
+ version_str = file.match(agent_regex)[1]
75
+ Gem::Version.new(version_str)
76
+ end
77
+
78
+ # Remove index.html if it exists in the windows_package_url
79
+ windows_package_repo = windows_package_url.sub(/index\.html$/, '')
80
+ # Return the full url to the latest msi
81
+ "#{windows_package_repo}#{latest_msi}"
82
+ end
83
+
84
+ # Generates commands to be inserted into a Windows batch file to launch an MSI install
85
+ # @param [String] msi_path The path of the MSI - can be a local Windows style file path like
86
+ # C:\Windows\Temp\puppet-agent.msi OR a url like https://download.com/puppet.msi or file://C:\Windows\Temp\puppet-agent.msi
87
+ # @param [Hash{String=>String}] msi_opts MSI installer options
88
+ # @param [String] log_path The path to write the MSI log - must be a local Windows style file path
89
+ #
90
+ # @api private
91
+ def msi_install_script(msi_path, msi_opts, log_path)
92
+ # msiexec requires backslashes in file paths launched under cmd.exe start /w
93
+ url_pattern = %r{^(https?|file)://}
94
+ msi_path = msi_path.tr('/', '\\') unless msi_path&.match?(url_pattern)
95
+
96
+ msi_params = msi_opts.map { |k, v| "#{k}=#{v}" }.join(' ')
97
+
98
+ # msiexec requires quotes around paths with backslashes - c:\ or file://c:\
99
+ # not strictly needed for http:// but it simplifies this code
100
+ <<~BATCH
101
+ start /w msiexec.exe /i "#{msi_path}" /qn /L*V #{log_path} #{msi_params}
102
+ exit /B %errorlevel%
103
+ BATCH
104
+ end
105
+
106
+ # Given a host, path to MSI and MSI options, will create a batch file
107
+ # on the host, returning the path to the randomized batch file and
108
+ # the randomized log file
109
+ #
110
+ # @param [Host] host An object implementing {Beaker::Hosts}'s interface.
111
+ # @param [String] msi_path The path of the MSI - can be a local Windows
112
+ # style file path like c:\temp\puppet.msi OR a url like
113
+ # https://download.com/puppet.msi or file://c:\temp\puppet.msi
114
+ # @param [Hash{String=>String}] msi_opts MSI installer options
115
+ #
116
+ # @api private
117
+ # @return [String, String] path to the batch file, patch to the log file
118
+ def create_install_msi_batch_on(host, msi_path, msi_opts)
119
+ timestamp = Time.new.strftime('%Y-%m-%d_%H.%M.%S')
120
+ tmp_path = host.system_temp_path.tr('/', '\\')
121
+
122
+ batch_name = "install-puppet-msi-#{timestamp}.bat"
123
+ batch_path = "#{tmp_path}#{host.scp_separator}#{batch_name}"
124
+ log_path = "#{tmp_path}\\install-puppet-#{timestamp}.log"
125
+
126
+ Tempfile.open(batch_name) do |tmp_file|
127
+ batch_contents = msi_install_script(msi_path, msi_opts, log_path)
128
+
129
+ File.open(tmp_file.path, 'w') { |file| file.puts(batch_contents) }
130
+ host.do_scp_to(tmp_file.path, batch_path, {})
131
+ end
132
+
133
+ [batch_path, log_path]
134
+ end
135
+
136
+ # Installs a specified MSI package on given hosts.
137
+ # @param [Host, Array<Host>, String, Symbol] hosts One or more hosts to act upon,
138
+ # or a role (String or Symbol) that identifies one or more hosts.
139
+ # @param [String] msi_path
140
+ # The path of the MSI - can be a local Windows style file path like
141
+ # c:\temp\puppet.msi OR a url like https://download.com/puppet.msi or file://c:\temp\puppet.msi
142
+ # can also be a collection like 'puppet', 'puppet8', 'openvox', or 'openvox8'
143
+ # @param [Hash{String=>String}] msi_opts MSI installer options
144
+ # @option msi_opts [String] INSTALLDIR Where Puppet and its dependencies should be installed.
145
+ # (Defaults vary based on operating system and installer architecture)
146
+ # Requires Puppet 2.7.12 / PE 2.5.0
147
+ # @option msi_opts [String] PUPPET_MASTER_SERVER The hostname where the puppet master server can be reached.
148
+ # (Defaults to puppet)
149
+ # Requires Puppet 2.7.12 / PE 2.5.0
150
+ # @option msi_opts [String] PUPPET_CA_SERVER The hostname where the CA puppet master server can be reached, if you are using multiple masters and only one of them is acting as the CA.
151
+ # (Defaults the value of PUPPET_MASTER_SERVER)
152
+ # Requires Puppet 2.7.12 / PE 2.5.0
153
+ # @option msi_opts [String] PUPPET_AGENT_CERTNAME The node’s certificate name, and the name it uses when requesting catalogs. This will set a value for
154
+ # (Defaults to the node's fqdn as discovered by facter fqdn)
155
+ # Requires Puppet 2.7.12 / PE 2.5.0
156
+ # @option msi_opts [String] PUPPET_AGENT_ENVIRONMENT The node’s environment.
157
+ # (Defaults to production)
158
+ # Requires Puppet 3.3.1 / PE 3.1.0
159
+ # @option msi_opts [String] PUPPET_AGENT_STARTUP_MODE Whether the puppet agent service should run (or be allowed to run)
160
+ # (Defaults to Manual - valid values are Automatic, Manual or Disabled)
161
+ # Requires Puppet 3.4.0 / PE 3.2.0
162
+ # @option msi_opts [String] PUPPET_AGENT_ACCOUNT_USER Whether the puppet agent service should run (or be allowed to run)
163
+ # (Defaults to LocalSystem)
164
+ # Requires Puppet 3.4.0 / PE 3.2.0
165
+ # @option msi_opts [String] PUPPET_AGENT_ACCOUNT_PASSWORD The password to use for puppet agent’s user account
166
+ # (No default)
167
+ # Requires Puppet 3.4.0 / PE 3.2.0
168
+ # @option msi_opts [String] PUPPET_AGENT_ACCOUNT_DOMAIN The domain of puppet agent’s user account.
169
+ # (Defaults to .)
170
+ # Requires Puppet 3.4.0 / PE 3.2.0
171
+ # @param [Hash] opts Options hash to control installation behavior.
172
+ #
173
+ # @example
174
+ # install_msi_on(hosts, 'c:\\puppet.msi', msi_opts: {'PUPPET_AGENT_STARTUP_MODE' => 'Manual'}, opts: {:debug => true})
175
+ #
176
+ # @api private
177
+ def install_msi_on(hosts, msi_path, msi_opts: {}, opts: {})
178
+ # If the msi patch matches a collection, get the url for the latest msi available for that collection
179
+ expanded_msi_path = /^(puppet|openvox)\d*$/.match?(msi_path) ? get_agent_package_url(msi_path) : msi_path
180
+ block_on hosts do |host|
181
+ msi_opts['PUPPET_AGENT_STARTUP_MODE'] ||= 'Manual'
182
+ batch_path, log_file = create_install_msi_batch_on(host, expanded_msi_path, msi_opts)
183
+ # Powershell command looses an escaped slash resulting in cygwin relative path
184
+ # See https://github.com/puppetlabs/beaker/pull/1626#issuecomment-621341555
185
+ log_file_escaped = log_file.gsub('\\', '\\\\\\')
186
+ # begin / rescue here so that we can reuse existing error msg propagation
187
+ begin
188
+ # 1641 = ERROR_SUCCESS_REBOOT_INITIATED
189
+ # 3010 = ERROR_SUCCESS_REBOOT_REQUIRED
190
+ on host, Beaker::Command.new("\"#{batch_path}\"", [], { cmdexe: true }), acceptable_exit_codes: [0, 1641, 3010]
191
+ rescue StandardError
192
+ logger.info(file_contents_on(host, log_file_escaped))
193
+ raise
194
+ end
195
+
196
+ logger.info(file_contents_on(host, log_file_escaped)) if opts[:debug]
197
+
198
+ unless host.is_cygwin?
199
+ # Enable the PATH updates
200
+ host.close
201
+
202
+ # Some systems require a full reboot to trigger the enabled path
203
+ host.reboot unless on(host, Beaker::Command.new('puppet -h', [], { cmdexe: true }),
204
+ accept_all_exit_codes: true).exit_code.zero?
205
+ end
206
+
207
+ # verify service status post install
208
+ # if puppet service exists, then pe-puppet is not queried
209
+ # if puppet service does not exist, pe-puppet is queried and that exit code is used
210
+ # therefore, this command will always exit 0 if either service is installed
211
+ #
212
+ # We also take advantage of this output to verify the startup
213
+ # settings are honored as supplied to the MSI
214
+ on host, Beaker::Command.new('sc qc puppet || sc qc pe-puppet', [], { cmdexe: true }) do |result|
215
+ output = result.stdout
216
+ startup_mode = msi_opts['PUPPET_AGENT_STARTUP_MODE'].upcase
217
+
218
+ search = case startup_mode # rubocop:disable Style/HashLikeCase
219
+ when 'AUTOMATIC'
220
+ { code: 2, name: 'AUTO_START' }
221
+ when 'MANUAL'
222
+ { code: 3, name: 'DEMAND_START' }
223
+ when 'DISABLED'
224
+ { code: 4, name: 'DISABLED' }
225
+ end
226
+
227
+ 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)
228
+ end
229
+
230
+ # (PA-514) value for PUPPET_AGENT_STARTUP_MODE should be present in
231
+ # registry and honored after install/upgrade.
232
+ reg_key = if host.is_x86_64?
233
+ 'HKLM\\SOFTWARE\\Wow6432Node\\Puppet Labs\\PuppetInstaller'
234
+ else
235
+ 'HKLM\\SOFTWARE\\Puppet Labs\\PuppetInstaller'
236
+ end
237
+ reg_query_command = %(reg query "#{reg_key}" /v "RememberedPuppetAgentStartupMode" | findstr #{msi_opts['PUPPET_AGENT_STARTUP_MODE']})
238
+ on host, Beaker::Command.new(reg_query_command, [], { cmdexe: true })
239
+
240
+ # emit the misc/versions.txt file which contains component versions for
241
+ # puppet, facter, hiera, pxp-agent, packaging and vendored Ruby
242
+ [
243
+ "'%PROGRAMFILES%\\Puppet Labs\\puppet\\misc\\versions.txt'",
244
+ "'%PROGRAMFILES(X86)%\\Puppet Labs\\puppet\\misc\\versions.txt'",
245
+ ].each do |path|
246
+ result = on(host, "cmd /c type #{path}", accept_all_exit_codes: true)
247
+ if result.exit_code.zero?
248
+ logger.info(result.stdout)
249
+ break
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ # Installs a specified msi path on given hosts
256
+ # @param [Host, Array<Host>, String, Symbol] hosts One or more hosts to act upon,
257
+ # or a role (String or Symbol) that identifies one or more hosts.
258
+ # @param [String] msi_path
259
+ # The path of the MSI - can be a local Windows style file path like
260
+ # c:\temp\foo.msi OR a url like https://download.com/foo.msi or file://c:\temp\foo.msi
261
+ # can also be a collection like 'puppet', 'puppet8', 'openvox', or 'openvox8'
262
+ # @param [Hash{String=>String}] msi_opts MSI installer options
263
+ # @param [Hash] opts Options hash to control installation behavior.
264
+ #
265
+ # @example
266
+ # generic_install_msi_on(hosts, 'https://releases.hashicorp.com/vagrant/1.8.4/vagrant_1.8.4.msi', msi_opts: {}, opts: {:debug => true})
267
+ #
268
+ # @api private
269
+ def generic_install_msi_on(hosts, msi_path, msi_opts: {}, opts: {})
270
+ # If the msi patch matches a collection, get the url for the latest msi available for that collection
271
+ expanded_msi_path = /^(puppet|openvox)\d*$/.match?(msi_path) ? get_agent_package_url(msi_path) : msi_path
272
+
273
+ block_on hosts do |host|
274
+ batch_path, log_file = create_install_msi_batch_on(host, expanded_msi_path, msi_opts)
275
+ # Powershell command looses an escaped slash resulting in cygwin relative path
276
+ # See https://github.com/puppetlabs/beaker/pull/1626#issuecomment-621341555
277
+ log_file_escaped = log_file.gsub('\\', '\\\\\\')
278
+ # begin / rescue here so that we can reuse existing error msg propagation
279
+ begin
280
+ # 1641 = ERROR_SUCCESS_REBOOT_INITIATED
281
+ # 3010 = ERROR_SUCCESS_REBOOT_REQUIRED
282
+ on host, Beaker::Command.new("\"#{batch_path}\"", [], { cmdexe: true }), acceptable_exit_codes: [0, 1641, 3010]
283
+ rescue StandardError
284
+ logger.info(file_contents_on(host, log_file_escaped))
285
+
286
+ raise
287
+ end
288
+
289
+ logger.info(file_contents_on(host, log_file_escaped)) if opts[:debug]
290
+
291
+ host.close unless host.is_cygwin?
292
+ end
293
+ end
294
+ end
295
+ end
@@ -5,8 +5,10 @@ module BeakerPuppetHelpers
5
5
  autoload :DSL, File.join(__dir__, 'beaker_puppet_helpers', 'dsl.rb')
6
6
  autoload :InstallUtils, File.join(__dir__, 'beaker_puppet_helpers', 'install_utils.rb')
7
7
  autoload :ModuleUtils, File.join(__dir__, 'beaker_puppet_helpers', 'module_utils.rb')
8
+ autoload :WindowsUtils, File.join(__dir__, 'beaker_puppet_helpers', 'windows_utils.rb')
8
9
  end
9
10
 
10
11
  require 'beaker'
11
12
  Beaker::DSL.register(BeakerPuppetHelpers::DSL)
12
13
  Beaker::DSL.register(BeakerPuppetHelpers::ModuleUtils)
14
+ Beaker::DSL.register(BeakerPuppetHelpers::WindowsUtils)
@@ -0,0 +1,403 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable RSpec/MultipleMemoizedHelpers
4
+ require 'spec_helper'
5
+
6
+ class ClassMixedWithDSLInstallUtils
7
+ include Beaker::DSL::Patterns
8
+ include BeakerPuppetHelpers::WindowsUtils
9
+
10
+ def logger
11
+ @logger ||= RSpec::Mocks::Double.new('Beaker::Logger').as_null_object
12
+ end
13
+ end
14
+
15
+ describe BeakerPuppetHelpers::WindowsUtils do
16
+ subject(:dsl) { ClassMixedWithDSLInstallUtils.new }
17
+
18
+ let(:windows_temp) { 'C:\\Windows\\Temp' }
19
+ let(:batch_path) { '/fake/batch/path' }
20
+ let(:msi_path) { 'c:\\foo\\puppet.msi' }
21
+ let(:winhost) do
22
+ make_host('winhost',
23
+ { platform: Beaker::Platform.new('windows-2008r2-64'),
24
+ pe_ver: '3.0',
25
+ working_dir: '/tmp',
26
+ is_cygwin: true, })
27
+ end
28
+ let(:winhost_non_cygwin) do
29
+ make_host('winhost_non_cygwin',
30
+ { platform: 'windows',
31
+ pe_ver: '3.0',
32
+ working_dir: '/tmp',
33
+ is_cygwin: 'false', })
34
+ end
35
+ let(:hosts) { [winhost, winhost_non_cygwin] }
36
+
37
+ def expect_install_called
38
+ result = Beaker::Result.new(nil, 'temp')
39
+ result.exit_code = 0
40
+
41
+ hosts.each do |host|
42
+ expectation = expect(subject).to receive(:on).with(host, having_attributes(command: "\"#{batch_path}\""),
43
+ anything).and_return(result)
44
+ if block_given?
45
+ should_break = yield expectation
46
+ break if should_break
47
+ end
48
+ end
49
+ end
50
+
51
+ def expect_status_called(start_type = 'DEMAND_START')
52
+ result = Beaker::Result.new(nil, 'temp')
53
+ result.exit_code = 0
54
+ result.stdout = case start_type
55
+ when 'DISABLED'
56
+ ' START_TYPE : 4 DISABLED'
57
+ when 'AUTOMATIC'
58
+ ' START_TYPE : 2 AUTO_START'
59
+ else # 'DEMAND_START'
60
+ ' START_TYPE : 3 DEMAND_START'
61
+ end
62
+
63
+ hosts.each do |host|
64
+ expect(subject).to receive(:on).with(host,
65
+ having_attributes(command: 'sc qc puppet || sc qc pe-puppet')).and_yield(result)
66
+ end
67
+ end
68
+
69
+ def expect_version_log_called(_times = hosts.length)
70
+ path = "'%PROGRAMFILES%\\Puppet Labs\\puppet\\misc\\versions.txt'"
71
+
72
+ result = Beaker::Result.new(nil, 'temp')
73
+ result.exit_code = 0
74
+
75
+ hosts.each do |host|
76
+ expect(subject).to receive(:on).with(host, "cmd /c type #{path}", anything).and_return(result)
77
+ end
78
+ end
79
+
80
+ def expect_reg_query_called(_times = hosts.length)
81
+ expect(hosts).to all(receive(:is_x86_64?).and_return(true))
82
+
83
+ hosts.each do |host|
84
+ expect(subject).to receive(:on)
85
+ .with(host, having_attributes(command: /reg query "HKLM\\SOFTWARE\\Wow6432Node\\Puppet Labs\\PuppetInstaller/))
86
+ end
87
+ end
88
+
89
+ def expect_puppet_path_called
90
+ hosts.each do |host|
91
+ next if host.is_cygwin?
92
+
93
+ result = Beaker::Result.new(nil, 'temp')
94
+ result.exit_code = 0
95
+
96
+ expect(subject).to receive(:on)
97
+ .with(host, having_attributes(command: 'puppet -h'), anything)
98
+ .and_return(result)
99
+ end
100
+ end
101
+
102
+ describe '#get_agent_package_url' do
103
+ let(:base_package_repo_html) do
104
+ <<-HTML
105
+ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
106
+ <html>
107
+ <head>
108
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
109
+ <title>Index of /windows/</title>
110
+ </head>
111
+ <body>
112
+ <h1>Index of /windows/</h1>
113
+ <hr>
114
+ <pre>
115
+ <a href="../">../</a>
116
+ <a href="openvox7/">openvox7/</a>
117
+ <a href="openvox8/">openvox8/</a>
118
+ </pre>
119
+ <hr>
120
+ </body>
121
+ </html>
122
+ HTML
123
+ end
124
+ let(:package_repo_html) do
125
+ <<-HTML
126
+ <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
127
+ <html>
128
+ <head>
129
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
130
+ <title>Index of /windows/openvox8/</title>
131
+ </head>
132
+ <body>
133
+ <h1>Index of /windows/openvox8/</h1>
134
+ <hr>
135
+ <pre>
136
+ <a href="../">../</a>
137
+ <a href="unsigned/">unsigned/</a>
138
+ <a href="openvox-agent-8.19.2-x64.msi">openvox-agent-8.19.2-x64.msi</a>
139
+ <a href="openvox-agent-8.21.2-rc1-x64.msi">openvox-agent-8.21.2-rc1-x64.msi</a>
140
+ <a href="openvox-agent-8.22.0-x64.msi">openvox-agent-8.22.0-x64.msi</a>
141
+ <a href="openvox-agent-8.22.1-x64.msi">openvox-agent-8.22.1-x64.msi</a>
142
+ <a href="openvox-agent-8.23.0-x64.msi">openvox-agent-8.23.0-x64.msi</a>
143
+ <a href="openvox-agent-8.23.1-x64.msi">openvox-agent-8.23.1-x64.msi</a>
144
+ </pre>
145
+ <hr>
146
+ </body>
147
+ </html>
148
+ HTML
149
+ end
150
+
151
+ before do
152
+ # Stub Net::HTTP.get_response for base url and final url
153
+ allow(Net::HTTP).to receive(:get_response) do |uri|
154
+ case uri.to_s
155
+ when 'https://downloads.voxpupuli.org/windows/'
156
+ instance_double(Net::HTTPOK, is_a?: true, body: base_package_repo_html)
157
+ when 'https://downloads.voxpupuli.org/windows/openvox8/'
158
+ instance_double(Net::HTTPOK, is_a?: true, body: package_repo_html)
159
+ else
160
+ raise "Unexpected URL: #{uri}"
161
+ end
162
+ end
163
+ end
164
+
165
+ it 'returns the latest MSI URL for the openvox collection' do
166
+ result = dsl.get_agent_package_url('openvox')
167
+ expect(result).to eq('https://downloads.voxpupuli.org/windows/openvox8/openvox-agent-8.23.1-x64.msi')
168
+ end
169
+
170
+ it 'returns the latest MSI URL for the openvox8 collection' do
171
+ result = dsl.get_agent_package_url('openvox8')
172
+ expect(result).to eq('https://downloads.voxpupuli.org/windows/openvox8/openvox-agent-8.23.1-x64.msi')
173
+ end
174
+
175
+ it 'raises for unsupported collection prefix' do
176
+ expect { dsl.get_agent_package_url('foo') }.to raise_error('Unsupported collection: foo')
177
+ end
178
+
179
+ it 'raises when no MSI files found' do
180
+ empty_html = '<html><body></body></html>'
181
+ allow(Net::HTTP).to receive(:get_response).and_return(
182
+ instance_double(Net::HTTPOK, is_a?: true, body: empty_html),
183
+ )
184
+ expect { dsl.get_agent_package_url('openvox8') }.to raise_error('No MSI files found at https://downloads.voxpupuli.org/windows/openvox8/')
185
+ end
186
+ end
187
+
188
+ describe '#install_msi_on' do
189
+ let(:log_file) { '/fake/log/file.log' }
190
+
191
+ before do
192
+ result = Beaker::Result.new(nil, 'temp')
193
+ result.exit_code = 0
194
+
195
+ hosts.each do |host|
196
+ allow(dsl).to receive(:on)
197
+ .with(host, having_attributes(command: "\"#{batch_path}\""))
198
+ .and_return(result)
199
+ end
200
+
201
+ allow(dsl).to receive(:create_install_msi_batch_on).and_return([batch_path, log_file])
202
+ end
203
+
204
+ it 'specifies a PUPPET_AGENT_STARTUP_MODE of Manual by default' do
205
+ expect_install_called
206
+ expect_puppet_path_called
207
+ expect_status_called
208
+ expect_reg_query_called
209
+ expect_version_log_called
210
+ expect(dsl).to receive(:create_install_msi_batch_on).with(anything, anything, { 'PUPPET_AGENT_STARTUP_MODE' => 'Manual' })
211
+ dsl.install_msi_on(hosts, msi_path, msi_opts: {})
212
+ end
213
+
214
+ it 'allows configuration of PUPPET_AGENT_STARTUP_MODE to Automatic' do
215
+ expect_install_called
216
+ expect_puppet_path_called
217
+ expect_status_called('AUTOMATIC')
218
+ expect_reg_query_called
219
+ expect_version_log_called
220
+ value = 'Automatic'
221
+ expect(dsl).to receive(:create_install_msi_batch_on).with(anything, anything, { 'PUPPET_AGENT_STARTUP_MODE' => value })
222
+ dsl.install_msi_on(hosts, msi_path, msi_opts: { 'PUPPET_AGENT_STARTUP_MODE' => value })
223
+ end
224
+
225
+ it 'allows configuration of PUPPET_AGENT_STARTUP_MODE to Disabled' do
226
+ expect_install_called
227
+ expect_puppet_path_called
228
+ expect_status_called('DISABLED')
229
+ expect_reg_query_called
230
+ expect_version_log_called
231
+ value = 'Disabled'
232
+ expect(dsl).to receive(:create_install_msi_batch_on).with(anything, anything, { 'PUPPET_AGENT_STARTUP_MODE' => value })
233
+ dsl.install_msi_on(hosts, msi_path, msi_opts: { 'PUPPET_AGENT_STARTUP_MODE' => value })
234
+ end
235
+
236
+ it 'does not generate a command to emit a log file without the :debug option set' do
237
+ expect_install_called
238
+ expect_puppet_path_called
239
+ expect_status_called
240
+ expect_reg_query_called
241
+ expect_version_log_called
242
+
243
+ expect(dsl).not_to receive(:file_contents_on).with(anything, log_file)
244
+
245
+ dsl.install_msi_on(hosts, msi_path)
246
+ end
247
+
248
+ it 'generates a command to emit a log file when the install script fails' do
249
+ # NOTE: a single failure aborts executing against remaining hosts
250
+ expect_install_called do |e|
251
+ e.and_raise
252
+ true # break
253
+ end
254
+
255
+ expect(dsl).to receive(:file_contents_on).with(anything, log_file)
256
+ expect do
257
+ dsl.install_msi_on(hosts, msi_path)
258
+ end.to raise_error(RuntimeError)
259
+ end
260
+
261
+ it 'generates a command to emit a log file with the :debug option set' do
262
+ expect_install_called
263
+ expect_reg_query_called
264
+ expect_puppet_path_called
265
+ expect_status_called
266
+ expect_version_log_called
267
+
268
+ expect(dsl).to receive(:file_contents_on).with(anything, log_file).exactly(hosts.length).times
269
+
270
+ dsl.install_msi_on(hosts, msi_path, msi_opts: {}, opts: { debug: true })
271
+ end
272
+
273
+ it 'passes msi_path to #create_install_msi_batch_on as-is' do
274
+ expect_install_called
275
+ expect_reg_query_called
276
+ expect_puppet_path_called
277
+ expect_status_called
278
+ expect_version_log_called
279
+ test_path = 'test/path'
280
+ expect(dsl).to receive(:create_install_msi_batch_on).with(anything, test_path, anything)
281
+ dsl.install_msi_on(hosts, test_path)
282
+ end
283
+
284
+ it 'searches in Wow6432Node for the remembered startup setting on 64-bit hosts' do
285
+ expect_install_called
286
+ expect_puppet_path_called
287
+ expect_status_called
288
+ expect_version_log_called
289
+
290
+ hosts.each do |host|
291
+ expect(host).to receive(:is_x86_64?).and_return(true)
292
+
293
+ expect(dsl).to receive(:on).with(host, having_attributes(command: 'reg query "HKLM\\SOFTWARE\\Wow6432Node\\Puppet Labs\\PuppetInstaller" /v "RememberedPuppetAgentStartupMode" | findstr Manual'))
294
+ end
295
+
296
+ dsl.install_msi_on(hosts, msi_path, msi_opts: { 'PUPPET_AGENT_STARTUP_MODE' => 'Manual' })
297
+ end
298
+
299
+ it 'omits Wow6432Node in the registry search for remembered startup setting on 32-bit hosts' do
300
+ expect_install_called
301
+ expect_puppet_path_called
302
+ expect_status_called
303
+ expect_version_log_called
304
+
305
+ hosts.each do |host|
306
+ expect(host).to receive(:is_x86_64?).and_return(false)
307
+
308
+ expect(dsl).to receive(:on).with(host, having_attributes(command: 'reg query "HKLM\\SOFTWARE\\Puppet Labs\\PuppetInstaller" /v "RememberedPuppetAgentStartupMode" | findstr Manual'))
309
+ end
310
+
311
+ dsl.install_msi_on(hosts, msi_path, msi_opts: { 'PUPPET_AGENT_STARTUP_MODE' => 'Manual' })
312
+ end
313
+ end
314
+
315
+ describe '#create_install_msi_batch_on' do
316
+ let(:tmp) { '/tmp/create_install_msi_batch_on' }
317
+ let(:tmp_slashes) { tmp.tr('/', '\\') }
318
+
319
+ before do
320
+ FakeFS::FileSystem.add(File.expand_path(tmp))
321
+ hosts.each do |host|
322
+ allow(host).to receive(:system_temp_path).and_return(tmp)
323
+ end
324
+ end
325
+
326
+ it 'passes msi_path & msi_opts down to #msi_install_script' do
327
+ allow(winhost).to receive(:do_scp_to)
328
+ test_path = '/path/to/test/with/13540'
329
+ test_opts = { 'key1' => 'val1', 'key2' => 'val2' }
330
+ expect(dsl).to receive(:msi_install_script).with(
331
+ test_path, test_opts, anything
332
+ )
333
+ dsl.create_install_msi_batch_on(winhost, test_path, test_opts)
334
+ end
335
+
336
+ it 'SCPs to & returns the same batch file path, corrected for slashes' do
337
+ test_time = Time.now
338
+ allow(Time).to receive(:new).and_return(test_time)
339
+ timestamp = test_time.strftime('%Y-%m-%d_%H.%M.%S')
340
+
341
+ correct_path = "#{tmp_slashes}\\install-puppet-msi-#{timestamp}.bat"
342
+ expect(winhost).to receive(:do_scp_to).with(anything, correct_path, {})
343
+ test_path, = dsl.create_install_msi_batch_on(winhost, msi_path, {})
344
+ expect(test_path).to eq(correct_path)
345
+ end
346
+
347
+ it 'returns & sends log_path to #msi_install_scripts, corrected for slashes' do
348
+ allow(winhost).to receive(:do_scp_to)
349
+ test_time = Time.now
350
+ allow(Time).to receive(:new).and_return(test_time)
351
+ timestamp = test_time.strftime('%Y-%m-%d_%H.%M.%S')
352
+
353
+ correct_path = "#{tmp_slashes}\\install-puppet-#{timestamp}.log"
354
+ expect(dsl).to receive(:msi_install_script).with(anything, anything, correct_path)
355
+ _, log_path = dsl.create_install_msi_batch_on(winhost, msi_path, {})
356
+ expect(log_path).to eq(correct_path)
357
+ end
358
+ end
359
+
360
+ describe '#msi_install_script' do
361
+ let(:log_path) { '/log/msi_install_script' }
362
+
363
+ context 'with msi_params parameter' do
364
+ it 'can take an empty hash' do
365
+ expected_cmd = %r{^start /w msiexec\.exe /i ".*" /qn /L\*V #{log_path}\ .exit}m
366
+ expect(dsl.msi_install_script(msi_path, {}, log_path)).to match(expected_cmd)
367
+ end
368
+
369
+ it 'uses a key-value pair correctly' do
370
+ params = { 'tk1' => 'tv1' }
371
+ expected_cmd = %r{^start /w msiexec\.exe /i ".*" /qn /L\*V #{log_path}\ tk1=tv1}
372
+ expect(dsl.msi_install_script(msi_path, params, log_path)).to match(expected_cmd)
373
+ end
374
+
375
+ it 'uses multiple key-value pairs correctly' do
376
+ params = { 'tk1' => 'tv1', 'tk2' => 'tv2' }
377
+ expected_cmd = %r{^start /w msiexec\.exe /i ".*" /qn /L\*V #{log_path}\ tk1=tv1\ tk2=tv2}
378
+ expect(dsl.msi_install_script(msi_path, params, log_path)).to match(expected_cmd)
379
+ end
380
+ end
381
+
382
+ context 'with msi_path parameter' do
383
+ it 'generates an appropriate command with a MSI file path using non-Windows slashes' do
384
+ msi_path = 'c:/foo/puppet.msi'
385
+ expected_cmd = %r{^start /w msiexec\.exe /i "c:\\foo\\puppet.msi" /qn /L\*V #{log_path}}
386
+ expect(dsl.msi_install_script(msi_path, {}, log_path)).to match(expected_cmd)
387
+ end
388
+
389
+ it 'generates an appropriate command with a MSI http(s) url' do
390
+ msi_url = 'https://downloads.puppetlabs.com/puppet.msi'
391
+ expected_cmd = %r{^start /w msiexec\.exe /i "https://downloads\.puppetlabs\.com/puppet\.msi" /qn /L\*V #{log_path}}
392
+ expect(dsl.msi_install_script(msi_url, {}, log_path)).to match(expected_cmd)
393
+ end
394
+
395
+ it 'generates an appropriate command with a MSI file url' do
396
+ msi_url = 'file://c:\\foo\\puppet.msi'
397
+ expected_cmd = %r{^start /w msiexec\.exe /i "file://c:\\foo\\puppet\.msi" /qn /L\*V #{log_path}}
398
+ expect(dsl.msi_install_script(msi_url, {}, log_path)).to match(expected_cmd)
399
+ end
400
+ end
401
+ end
402
+ end
403
+ # rubocop:enable RSpec/MultipleMemoizedHelpers
data/spec/helpers.rb ADDED
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HostHelpers
4
+ HOST_DEFAULTS = { platform: 'unix',
5
+ roles: ['agent'],
6
+ snapshot: 'snap',
7
+ ip: 'default.ip.address',
8
+ private_ip: 'private.ip.address',
9
+ dns_name: 'default.box.tld',
10
+ box: 'default_box_name',
11
+ box_url: 'http://default.box.url',
12
+ image: 'default_image',
13
+ flavor: 'm1.large',
14
+ user_data: '#cloud-config\nmanage_etc_hosts: true\nfinal_message: "The host is finally up!"', }.freeze
15
+
16
+ HOST_NAME = 'vm%d'
17
+ HOST_SNAPSHOT = 'snapshot%d'
18
+ HOST_IP = 'ip.address.for.%s'
19
+ HOST_BOX = 'vm2%s_of_my_box'
20
+ HOST_BOX_URL = 'http://address.for.my.box.%s'
21
+ HOST_DNS_NAME = '%s.box.tld'
22
+ HOST_TEMPLATE = '%s_has_a_template'
23
+ HOST_PRIVATE_IP = 'private.ip.for.%s'
24
+
25
+ def logger
26
+ instance_double(Logger).as_null_object
27
+ end
28
+
29
+ def make_opts
30
+ opts = Beaker::Options::Presets.new
31
+ opts.presets.merge(opts.env_vars).merge({ logger: logger,
32
+ host_config: 'sample.config',
33
+ type: nil,
34
+ pooling_api: 'http://vcloud.delivery.puppetlabs.net/',
35
+ datastore: 'instance0',
36
+ folder: 'Delivery/Quality Assurance/Staging/Dynamic',
37
+ resourcepool: 'delivery/Quality Assurance/Staging/Dynamic',
38
+ gce_project: 'beaker-compute',
39
+ gce_keyfile: '/path/to/keyfile.p12',
40
+ gce_password: 'notasecret',
41
+ gce_email: '12345678910@developer.gserviceaccount.com',
42
+ openstack_api_key: 'P1as$w0rd',
43
+ openstack_username: 'user',
44
+ openstack_auth_url: 'http://openstack_hypervisor.labs.net:5000/v2.0/tokens',
45
+ openstack_tenant: 'testing',
46
+ openstack_network: 'testing',
47
+ openstack_keyname: 'nopass',
48
+ floating_ip_pool: 'my_pool',
49
+ security_group: %w[my_sg default], })
50
+ end
51
+
52
+ def generate_result(name, opts)
53
+ stdout = opts.key?(:stdout) ? opts[:stdout] : name
54
+ stderr = opts.key?(:stderr) ? opts[:stderr] : name
55
+ exit_code = opts.key?(:exit_code) ? opts[:exit_code] : 0
56
+ exit_code = [exit_code].flatten
57
+ result = instance_double(Beaker::Result, stdout: stdout, stderr: stderr)
58
+ allow(result).to receive_messages(stdout: stdout, stderr: stderr)
59
+ allow(result).to receive(:exit_code).and_return(*exit_code)
60
+ result
61
+ end
62
+
63
+ def make_host_opts(name, opts)
64
+ make_opts.merge({ 'HOSTS' => { name => opts } }).merge(opts)
65
+ end
66
+
67
+ def make_host(name, host_hash)
68
+ host_hash = Beaker::Options::OptionsHash.new.merge(HOST_DEFAULTS.merge(host_hash))
69
+
70
+ host = Beaker::Host.create(name, host_hash, make_opts)
71
+
72
+ allow(host).to receive(:exec).and_return(generate_result(name, host_hash))
73
+ allow(host).to receive(:close)
74
+ host
75
+ end
76
+
77
+ def make_hosts(preset_opts = {}, amt = 3)
78
+ hosts = []
79
+ (1..amt).each do |num|
80
+ name = HOST_NAME % num
81
+ opts = { snapshot: HOST_SNAPSHOT % num,
82
+ ip: HOST_IP % name,
83
+ private_ip: HOST_PRIVATE_IP % name,
84
+ dns_name: HOST_DNS_NAME % name,
85
+ template: HOST_TEMPLATE % name,
86
+ box: HOST_BOX % name,
87
+ box_url: HOST_BOX_URL % name, }.merge(preset_opts)
88
+ hosts << make_host(name, opts)
89
+ end
90
+ hosts
91
+ end
92
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'beaker_puppet_helpers'
4
+
5
+ # require 'pp' statement needed before fakefs, otherwise they can collide. Ref:
6
+ # https://github.com/fakefs/fakefs#fakefs-----typeerror-superclass-mismatch-for-class-file
7
+ require 'pp'
8
+ require 'fakefs/spec_helpers'
9
+ require 'helpers'
10
+
11
+ RSpec.configure do |config|
12
+ config.include FakeFS::SpecHelpers
13
+ config.include HostHelpers
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: beaker_puppet_helpers
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vox Pupuli
@@ -18,7 +18,7 @@ dependencies:
18
18
  version: 5.8.1
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
- version: '7'
21
+ version: '8'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -28,7 +28,7 @@ dependencies:
28
28
  version: 5.8.1
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
- version: '7'
31
+ version: '8'
32
32
  - !ruby/object:Gem::Dependency
33
33
  name: puppet-modulebuilder
34
34
  requirement: !ruby/object:Gem::Requirement
@@ -49,6 +49,20 @@ dependencies:
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
51
  version: '3'
52
+ - !ruby/object:Gem::Dependency
53
+ name: voxpupuli-rubocop
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: 4.1.0
59
+ type: :development
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: 4.1.0
52
66
  description: For use for the Beaker acceptance testing tool
53
67
  email:
54
68
  - voxpupuli@groups.io
@@ -77,9 +91,12 @@ files:
77
91
  - lib/beaker_puppet_helpers/dsl.rb
78
92
  - lib/beaker_puppet_helpers/install_utils.rb
79
93
  - lib/beaker_puppet_helpers/module_utils.rb
94
+ - lib/beaker_puppet_helpers/windows_utils.rb
80
95
  - spec/beaker_puppet_helpers/dsl_spec.rb
81
96
  - spec/beaker_puppet_helpers/install_utils_spec.rb
82
97
  - spec/beaker_puppet_helpers/module_utils_spec.rb
98
+ - spec/beaker_puppet_helpers/windows_utils_spec.rb
99
+ - spec/helpers.rb
83
100
  - spec/spec_helper.rb
84
101
  homepage: https://github.com/voxpupuli/beaker_puppet_helpers
85
102
  licenses:
@@ -92,17 +109,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
92
109
  requirements:
93
110
  - - ">="
94
111
  - !ruby/object:Gem::Version
95
- version: '2.7'
96
- - - "<"
97
- - !ruby/object:Gem::Version
98
- version: '4'
112
+ version: '3.2'
99
113
  required_rubygems_version: !ruby/object:Gem::Requirement
100
114
  requirements:
101
115
  - - ">="
102
116
  - !ruby/object:Gem::Version
103
117
  version: '0'
104
118
  requirements: []
105
- rubygems_version: 3.6.7
119
+ rubygems_version: 3.6.9
106
120
  specification_version: 4
107
121
  summary: Beaker's Puppet DSL Extension Helpers
108
122
  test_files: []