beaker_puppet_helpers 1.0.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 +7 -0
- data/.github/dependabot.yml +17 -0
- data/.github/workflows/release.yml +31 -0
- data/.github/workflows/test.yml +55 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_todo.yml +27 -0
- data/CHANGELOG.md +35 -0
- data/Gemfile +32 -0
- data/LICENSE +202 -0
- data/README.md +64 -0
- data/Rakefile +74 -0
- data/acceptance/dummy/manifests/init.pp +8 -0
- data/acceptance/dummy/metadata.json +14 -0
- data/acceptance/tests/install_utils.rb +22 -0
- data/acceptance/tests/module_utils.rb +27 -0
- data/beaker_puppet_helpers.gemspec +21 -0
- data/lib/beaker_puppet_helpers/dsl.rb +222 -0
- data/lib/beaker_puppet_helpers/install_utils.rb +106 -0
- data/lib/beaker_puppet_helpers/module_utils.rb +59 -0
- data/lib/beaker_puppet_helpers.rb +12 -0
- data/spec/beaker_puppet_helpers/dsl_spec.rb +229 -0
- data/spec/beaker_puppet_helpers/install_utils_spec.rb +70 -0
- data/spec/beaker_puppet_helpers/module_utils_spec.rb +101 -0
- data/spec/spec_helper.rb +3 -0
- metadata +110 -0
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'beaker_puppet_helpers'
|
5
|
+
s.version = '1.0.0'
|
6
|
+
s.authors = ['Vox Pupuli']
|
7
|
+
s.email = ['voxpupuli@groups.io']
|
8
|
+
s.homepage = 'https://github.com/voxpupuli/beaker_puppet_helpers'
|
9
|
+
s.summary = "Beaker's Puppet DSL Extension Helpers"
|
10
|
+
s.description = 'For use for the Beaker acceptance testing tool'
|
11
|
+
s.license = 'Apache-2.0'
|
12
|
+
|
13
|
+
s.required_ruby_version = '>= 2.7', '< 4'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
s.require_paths = ['lib']
|
17
|
+
|
18
|
+
# Run time dependencies
|
19
|
+
s.add_runtime_dependency 'beaker', '>= 4', '< 6'
|
20
|
+
s.add_runtime_dependency 'puppet-modulebuilder', '~> 0.3', '< 2'
|
21
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BeakerPuppetHelpers
|
4
|
+
# The DSL methods for beaker. These are included in Beaker.
|
5
|
+
module DSL
|
6
|
+
# @!macro [new] common_opts
|
7
|
+
# @param [Hash{Symbol=>String}] opts Options to alter execution.
|
8
|
+
# @option opts [Boolean] :silent (false) Do not produce log output
|
9
|
+
# @option opts [Array<Fixnum>] :acceptable_exit_codes ([0]) An array
|
10
|
+
# (or range) of integer exit codes that should be considered
|
11
|
+
# acceptable. An error will be thrown if the exit code does not
|
12
|
+
# match one of the values in this list.
|
13
|
+
# @option opts [Boolean] :accept_all_exit_codes (false) Consider all
|
14
|
+
# exit codes as passing.
|
15
|
+
# @option opts [Boolean] :dry_run (false) Do not actually execute any
|
16
|
+
# commands on the SUT
|
17
|
+
# @option opts [String] :stdin (nil) Input to be provided during command
|
18
|
+
# execution on the SUT.
|
19
|
+
# @option opts [Boolean] :pty (false) Execute this command in a pseudoterminal.
|
20
|
+
# @option opts [Boolean] :expect_connection_failure (false) Expect this command
|
21
|
+
# to result in a connection failure, reconnect and continue execution.
|
22
|
+
# @option opts [Hash{String=>String}] :environment ({}) These will be
|
23
|
+
# treated as extra environment variables that should be set before
|
24
|
+
# running the command.
|
25
|
+
#
|
26
|
+
|
27
|
+
# Runs 'puppet apply' on a remote host, piping manifest through stdin
|
28
|
+
#
|
29
|
+
# @param [Beaker::Host] hosts
|
30
|
+
# The host that this command should be run on
|
31
|
+
#
|
32
|
+
# @param [String] manifest The puppet manifest to apply
|
33
|
+
#
|
34
|
+
# @!macro common_opts
|
35
|
+
# @option opts [Boolean] :parseonly (false) If this key is true, the
|
36
|
+
# "--parseonly" command line parameter will
|
37
|
+
# be passed to the 'puppet apply' command.
|
38
|
+
#
|
39
|
+
# @option opts [Boolean] :trace (false) If this key exists in the Hash,
|
40
|
+
# the "--trace" command line parameter will be
|
41
|
+
# passed to the 'puppet apply' command.
|
42
|
+
#
|
43
|
+
# @option opts [Array<Integer>] :acceptable_exit_codes ([0]) The list of exit
|
44
|
+
# codes that will NOT raise an error when found upon
|
45
|
+
# command completion. If provided, these values will
|
46
|
+
# be combined with those used in :catch_failures and
|
47
|
+
# :expect_failures to create the full list of
|
48
|
+
# passing exit codes.
|
49
|
+
#
|
50
|
+
# @option opts [Hash] :environment Additional environment variables to be
|
51
|
+
# passed to the 'puppet apply' command
|
52
|
+
#
|
53
|
+
# @option opts [Boolean] :catch_failures (false) By default `puppet
|
54
|
+
# --apply` will exit with 0, which does not count
|
55
|
+
# as a test failure, even if there were errors or
|
56
|
+
# changes when applying the manifest. This option
|
57
|
+
# enables detailed exit codes and causes a test
|
58
|
+
# failure if `puppet --apply` indicates there was
|
59
|
+
# a failure during its execution.
|
60
|
+
#
|
61
|
+
# @option opts [Boolean] :catch_changes (false) This option enables
|
62
|
+
# detailed exit codes and causes a test failure
|
63
|
+
# if `puppet --apply` indicates that there were
|
64
|
+
# changes or failures during its execution.
|
65
|
+
#
|
66
|
+
# @option opts [Boolean] :expect_changes (false) This option enables
|
67
|
+
# detailed exit codes and causes a test failure
|
68
|
+
# if `puppet --apply` indicates that there were
|
69
|
+
# no resource changes during its execution.
|
70
|
+
#
|
71
|
+
# @option opts [Boolean] :expect_failures (false) This option enables
|
72
|
+
# detailed exit codes and causes a test failure
|
73
|
+
# if `puppet --apply` indicates there were no
|
74
|
+
# failure during its execution.
|
75
|
+
#
|
76
|
+
# @option opts [Boolean] :future_parser (false) This option enables
|
77
|
+
# the future parser option that is available
|
78
|
+
# from Puppet verion 3.2
|
79
|
+
# By default it will use the 'current' parser.
|
80
|
+
#
|
81
|
+
# @option opts [Boolean] :noop (false) If this option exists, the
|
82
|
+
# the "--noop" command line parameter will be
|
83
|
+
# passed to the 'puppet apply' command.
|
84
|
+
#
|
85
|
+
# @option opts [String] :modulepath The search path for modules, as
|
86
|
+
# a list of directories separated by the system
|
87
|
+
# path separator character. (The POSIX path separator
|
88
|
+
# is ‘:’, and the Windows path separator is ‘;’.)
|
89
|
+
#
|
90
|
+
# @option opts [String] :hiera_config The path of the hiera.yaml configuration.
|
91
|
+
#
|
92
|
+
# @option opts [String] :debug (false) If this option exists,
|
93
|
+
# the "--debug" command line parameter
|
94
|
+
# will be passed to the 'puppet apply' command.
|
95
|
+
# @option opts [Boolean] :run_in_parallel Whether to run on each host in parallel.
|
96
|
+
#
|
97
|
+
# @param [Block] block This method will yield to a block of code passed
|
98
|
+
# by the caller; this can be used for additional
|
99
|
+
# validation, etc.
|
100
|
+
#
|
101
|
+
# @return [Array<Result>, Result, nil] An array of results, a result
|
102
|
+
# object, or nil. Check {Beaker::Shared::HostManager#run_block_on} for
|
103
|
+
# more details on this.
|
104
|
+
def apply_manifest_on(hosts, manifest, opts = {}, &block)
|
105
|
+
block_on hosts, opts do |host|
|
106
|
+
on_options = {}
|
107
|
+
on_options[:acceptable_exit_codes] = Array(opts[:acceptable_exit_codes])
|
108
|
+
|
109
|
+
puppet_apply_opts = {}
|
110
|
+
if opts[:debug]
|
111
|
+
puppet_apply_opts[:debug] = nil
|
112
|
+
else
|
113
|
+
puppet_apply_opts[:verbose] = nil
|
114
|
+
end
|
115
|
+
puppet_apply_opts[:parseonly] = nil if opts[:parseonly]
|
116
|
+
puppet_apply_opts[:trace] = nil if opts[:trace]
|
117
|
+
puppet_apply_opts[:parser] = 'future' if opts[:future_parser]
|
118
|
+
puppet_apply_opts[:modulepath] = opts[:modulepath] if opts[:modulepath]
|
119
|
+
puppet_apply_opts[:hiera_config] = opts[:hiera_config] if opts[:hiera_config]
|
120
|
+
puppet_apply_opts[:noop] = nil if opts[:noop]
|
121
|
+
|
122
|
+
# From puppet help:
|
123
|
+
# "... an exit code of '2' means there were changes, an exit code of
|
124
|
+
# '4' means there were failures during the transaction, and an exit
|
125
|
+
# code of '6' means there were both changes and failures."
|
126
|
+
if [opts[:catch_changes], opts[:catch_failures], opts[:expect_failures], opts[:expect_changes]].compact.length > 1
|
127
|
+
raise(ArgumentError,
|
128
|
+
'Cannot specify more than one of `catch_failures`, ' \
|
129
|
+
'`catch_changes`, `expect_failures`, or `expect_changes` ' \
|
130
|
+
'for a single manifest')
|
131
|
+
end
|
132
|
+
|
133
|
+
if opts[:catch_changes]
|
134
|
+
puppet_apply_opts['detailed-exitcodes'] = nil
|
135
|
+
|
136
|
+
# We're after idempotency so allow exit code 0 only.
|
137
|
+
on_options[:acceptable_exit_codes] |= [0]
|
138
|
+
elsif opts[:catch_failures]
|
139
|
+
puppet_apply_opts['detailed-exitcodes'] = nil
|
140
|
+
|
141
|
+
# We're after only complete success so allow exit codes 0 and 2 only.
|
142
|
+
on_options[:acceptable_exit_codes] |= [0, 2]
|
143
|
+
elsif opts[:expect_failures]
|
144
|
+
puppet_apply_opts['detailed-exitcodes'] = nil
|
145
|
+
|
146
|
+
# We're after failures specifically so allow exit codes 1, 4, and 6 only.
|
147
|
+
on_options[:acceptable_exit_codes] |= [1, 4, 6]
|
148
|
+
elsif opts[:expect_changes]
|
149
|
+
puppet_apply_opts['detailed-exitcodes'] = nil
|
150
|
+
|
151
|
+
# We're after changes specifically so allow exit code 2 only.
|
152
|
+
on_options[:acceptable_exit_codes] |= [2]
|
153
|
+
else
|
154
|
+
# Either use the provided acceptable_exit_codes or default to [0]
|
155
|
+
on_options[:acceptable_exit_codes] |= [0]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Not really thrilled with this implementation, might want to improve it
|
159
|
+
# later. Basically, there is a magic trick in the constructor of
|
160
|
+
# PuppetCommand which allows you to pass in a Hash for the last value in
|
161
|
+
# the *args Array; if you do so, it will be treated specially. So, here
|
162
|
+
# we check to see if our caller passed us a hash of environment variables
|
163
|
+
# that they want to set for the puppet command. If so, we set the final
|
164
|
+
# value of *args to a new hash with just one entry (the value of which
|
165
|
+
# is our environment variables hash)
|
166
|
+
puppet_apply_opts['ENV'] = opts[:environment] if opts.key?(:environment)
|
167
|
+
|
168
|
+
puppet_apply_opts = host[:default_apply_opts].merge(puppet_apply_opts) if host[:default_apply_opts].respond_to? :merge
|
169
|
+
|
170
|
+
file_path = host.tmpfile(%(apply_manifest_#{Time.now.strftime('%H%M%S%L')}.pp))
|
171
|
+
begin
|
172
|
+
create_remote_file(host, file_path, "#{manifest}\n")
|
173
|
+
|
174
|
+
on(host, Beaker::PuppetCommand.new('apply', file_path, puppet_apply_opts), **on_options, &block)
|
175
|
+
ensure
|
176
|
+
host.rm_rf(file_path)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Runs 'puppet apply' on default host
|
182
|
+
# @see #apply_manifest_on
|
183
|
+
# @return [Array<Result>, Result, nil] An array of results, a result
|
184
|
+
# object, or nil. Check {Beaker::Shared::HostManager#run_block_on} for
|
185
|
+
# more details on this.
|
186
|
+
def apply_manifest(manifest, opts = {}, &block)
|
187
|
+
apply_manifest_on(default, manifest, opts, &block)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Get a facter fact from a provided host
|
191
|
+
#
|
192
|
+
# @param [Beaker::Host, Array<Beaker::Host>, String, Symbol] host
|
193
|
+
# One or more hosts to act upon, or a role (String or Symbol) that
|
194
|
+
# identifies one or more hosts.
|
195
|
+
# @param [String] name The name of the fact to query for
|
196
|
+
# @!macro common_opts
|
197
|
+
# @return String The value of the fact 'name' on the provided host
|
198
|
+
# @raise [FailTest] Raises an exception if call to facter fails
|
199
|
+
def fact_on(host, name, opts = {})
|
200
|
+
raise(ArgumentError, "fact_on's `name` option must be a String. You provided a #{name.class}: '#{name}'") unless name.is_a?(String)
|
201
|
+
|
202
|
+
if opts.is_a?(Hash)
|
203
|
+
opts['json'] = nil
|
204
|
+
else
|
205
|
+
opts << ' --json'
|
206
|
+
end
|
207
|
+
|
208
|
+
result = on host, Beaker::Command.new('facter', [name], opts)
|
209
|
+
if result.is_a?(Array)
|
210
|
+
result.map { |res| JSON.parse(res.stdout)[name] }
|
211
|
+
else
|
212
|
+
JSON.parse(result.stdout)[name]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
# Get a facter fact from the default host
|
217
|
+
# @see #fact_on
|
218
|
+
def fact(name, opts = {})
|
219
|
+
fact_on(default, name, opts)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'beaker/command'
|
4
|
+
|
5
|
+
module BeakerPuppetHelpers
|
6
|
+
# Methods to install Puppet
|
7
|
+
class InstallUtils
|
8
|
+
# @api private
|
9
|
+
REPOS = {
|
10
|
+
release: {
|
11
|
+
apt: 'https://apt.puppet.com',
|
12
|
+
yum: 'https://yum.puppet.com',
|
13
|
+
},
|
14
|
+
nightly: {
|
15
|
+
apt: 'https://nightlies.puppet.com/apt',
|
16
|
+
yum: 'https://nightlies.puppet.com/yum',
|
17
|
+
},
|
18
|
+
}.freeze
|
19
|
+
|
20
|
+
# Install official Puppet release repository configuration on host(s).
|
21
|
+
#
|
22
|
+
# @example Install Puppet 7
|
23
|
+
# install_puppet_release_repo_on(hosts, 'puppet7')
|
24
|
+
#
|
25
|
+
# @param [Beaker::Host] host
|
26
|
+
# A host to act upon.
|
27
|
+
# @param [String] collection
|
28
|
+
# The collection to install. The default (puppet) is the latest
|
29
|
+
# available version.
|
30
|
+
# @param [Boolean] nightly
|
31
|
+
# Whether to install nightly or release packages
|
32
|
+
#
|
33
|
+
# @note This method only works on redhat-like and debian-like hosts. There
|
34
|
+
# are no official Puppet releases for other platforms.
|
35
|
+
#
|
36
|
+
def self.install_puppet_release_repo_on(host, collection = 'puppet', nightly: false)
|
37
|
+
repos = REPOS[nightly ? :nightly : :release]
|
38
|
+
|
39
|
+
variant, version, _arch = host['packaging_platform'].split('-', 3)
|
40
|
+
|
41
|
+
case variant
|
42
|
+
when 'el', 'fedora', 'sles', 'cisco-wrlinux'
|
43
|
+
# sles 11 and later do not handle gpg keys well. We can't
|
44
|
+
# automatically import the keys because of sad things, so we
|
45
|
+
# have to manually import it once we install the release
|
46
|
+
# package. We'll have to remember to update this block when
|
47
|
+
# we update the signing keys
|
48
|
+
if variant == 'sles' && version >= '11'
|
49
|
+
%w[puppet puppet-20250406].each do |gpg_key|
|
50
|
+
wget_on(host, "https://yum.puppet.com/RPM-GPG-KEY-#{gpg_key}") do |filename|
|
51
|
+
host.exec(Beaker::Command.new("rpm --import '#{filename}'"))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
url = "#{repos[:yum]}/#{collection}-release-#{variant}-#{version}.noarch.rpm"
|
57
|
+
host.install_package(url)
|
58
|
+
when 'debian', 'ubuntu'
|
59
|
+
url = "#{repos[:apt]}/#{collection}-release-#{host['platform'].codename}.deb"
|
60
|
+
wget_on(host, url) do |filename|
|
61
|
+
host.install_package(filename)
|
62
|
+
end
|
63
|
+
host.exec(Beaker::Command.new('apt-get update'))
|
64
|
+
|
65
|
+
# On Debian we can't count on /etc/profile.d
|
66
|
+
host.add_env_var('PATH', '/opt/puppetlabs/bin')
|
67
|
+
else
|
68
|
+
raise "No repository installation step for #{variant} yet..."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Determine the Puppet package name
|
73
|
+
#
|
74
|
+
# @param [Beaker::Host] host
|
75
|
+
# The host to act on
|
76
|
+
# @param [Boolean] prefer_aio
|
77
|
+
# Whether to prefer AIO packages or OS packages
|
78
|
+
# @return [String] The Puppet package name
|
79
|
+
def self.puppet_package_name(host, prefer_aio: true)
|
80
|
+
case host['packaging_platform'].split('-', 3).first
|
81
|
+
when /el-|fedora|sles|cisco_|debian|ubuntu/
|
82
|
+
prefer_aio ? 'puppet-agent' : 'puppet'
|
83
|
+
when /freebsd/
|
84
|
+
'sysutils/puppet'
|
85
|
+
else
|
86
|
+
'puppet'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# @param [Beaker::Host] host
|
91
|
+
# The host to act on
|
92
|
+
# @api private
|
93
|
+
def self.wget_on(host, url)
|
94
|
+
extension = File.extname(url)
|
95
|
+
name = File.basename(url, extension)
|
96
|
+
# Can't use host.tmpfile since we need to set an extension
|
97
|
+
target = host.exec(Beaker::Command.new("mktemp -t '#{name}-XXXXXX#{extension}'")).stdout.strip
|
98
|
+
begin
|
99
|
+
host.exec(Beaker::Command.new("wget -O '#{target}' '#{url}'"))
|
100
|
+
yield target
|
101
|
+
ensure
|
102
|
+
host.rm_rf(target)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'beaker/command'
|
4
|
+
require 'puppet/modulebuilder'
|
5
|
+
|
6
|
+
module BeakerPuppetHelpers
|
7
|
+
# Methods to help install puppet modules
|
8
|
+
module ModuleUtils
|
9
|
+
# Install the desired module with the PMT on a given host
|
10
|
+
#
|
11
|
+
# @param [Beaker::Host, Array<Beaker::Host>, String, Symbol] hosts
|
12
|
+
# One or more hosts to act upon, or a role (String or Symbol) that
|
13
|
+
# identifies one or more hosts.
|
14
|
+
# @param [String] module_name
|
15
|
+
# The short name of the module to be installed
|
16
|
+
# @param [String] version
|
17
|
+
# The version of the module to be installed
|
18
|
+
# @param [String] module_repository
|
19
|
+
# An optional module repository to install from
|
20
|
+
def install_puppet_module_via_pmt_on(hosts, module_name, version = nil, module_repository = nil)
|
21
|
+
block_on hosts do |host|
|
22
|
+
puppet_opts = {}
|
23
|
+
puppet_opts.merge!(host[:default_module_install_opts]) if host[:default_module_install_opts]
|
24
|
+
puppet_opts[:version] = version if version
|
25
|
+
puppet_opts[:module_repository] = module_repository if module_repository
|
26
|
+
|
27
|
+
on host, Beaker::PuppetCommand.new('module', ['install', module_name], puppet_opts)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Install local module for acceptance testing
|
32
|
+
#
|
33
|
+
# This uses puppet-modulebuilder to build the module located at source
|
34
|
+
# and then copies it to the hosts. There it runs puppet module install.
|
35
|
+
#
|
36
|
+
# @param [Beaker::Host, Array<Beaker::Host>, String, Symbol] hosts
|
37
|
+
# One or more hosts to act upon, or a role (String or Symbol) that
|
38
|
+
# identifies one or more hosts.
|
39
|
+
# @param [String] source
|
40
|
+
# The directory where the module sits
|
41
|
+
def install_local_module_on(hosts, source = '.')
|
42
|
+
builder = Puppet::Modulebuilder::Builder.new(File.realpath(source))
|
43
|
+
source_path = builder.build
|
44
|
+
begin
|
45
|
+
block_on hosts do |host|
|
46
|
+
target_file = host.tmpfile('puppet_module')
|
47
|
+
begin
|
48
|
+
host.do_scp_to(source_path, target_file, {})
|
49
|
+
install_puppet_module_via_pmt_on(host, target_file)
|
50
|
+
ensure
|
51
|
+
host.rm_rf(target_file)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
ensure
|
55
|
+
File.unlink(source_path) if source_path
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A collection of helpers to make Puppet usage easier with Beaker
|
4
|
+
module BeakerPuppetHelpers
|
5
|
+
autoload :DSL, File.join(__dir__, 'beaker_puppet_helpers', 'dsl.rb')
|
6
|
+
autoload :InstallUtils, File.join(__dir__, 'beaker_puppet_helpers', 'install_utils.rb')
|
7
|
+
autoload :ModuleUtils, File.join(__dir__, 'beaker_puppet_helpers', 'module_utils.rb')
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'beaker'
|
11
|
+
Beaker::DSL.register(BeakerPuppetHelpers::DSL)
|
12
|
+
Beaker::DSL.register(BeakerPuppetHelpers::ModuleUtils)
|
@@ -0,0 +1,229 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
class ClassMixedWithDSLHelpers
|
6
|
+
include Beaker::DSL::Patterns
|
7
|
+
include BeakerPuppetHelpers::DSL
|
8
|
+
|
9
|
+
def logger
|
10
|
+
@logger ||= RSpec::Mocks::Double.new('Beaker::Logger').as_null_object
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe BeakerPuppetHelpers::DSL do
|
15
|
+
subject(:dsl) { ClassMixedWithDSLHelpers.new }
|
16
|
+
|
17
|
+
let(:master) { instance_double(Beaker::Host) }
|
18
|
+
let(:agent) { instance_double(Beaker::Host) }
|
19
|
+
let(:hosts) { [master, agent] }
|
20
|
+
|
21
|
+
describe '#apply_manifest_on' do
|
22
|
+
before do
|
23
|
+
hosts.each do |host|
|
24
|
+
allow(host).to receive(:tmpfile).and_return('temp')
|
25
|
+
allow(host).to receive(:rm_rf).with('temp')
|
26
|
+
allow(host).to receive(:[]).with(:default_apply_opts)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'calls puppet' do
|
31
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
32
|
+
expect(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
33
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [0])
|
34
|
+
|
35
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }')
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'operates on an array of hosts' do
|
39
|
+
the_hosts = [master, agent]
|
40
|
+
|
41
|
+
expect(dsl).to receive(:create_remote_file).twice.and_return(true)
|
42
|
+
the_hosts.each do |host|
|
43
|
+
expect(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
44
|
+
expect(dsl).to receive(:on).with(host, 'puppet_command', acceptable_exit_codes: [0])
|
45
|
+
end
|
46
|
+
|
47
|
+
result = dsl.apply_manifest_on(the_hosts, 'include foobar')
|
48
|
+
expect(result).to be_an(Array)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'operates on an array of hosts in parallel' do
|
52
|
+
InParallel::InParallelExecutor.logger = dsl.logger
|
53
|
+
# This will only get hit if forking processes is supported and at least 2 items are being submitted to run in parallel
|
54
|
+
# expect( InParallel::InParallelExecutor ).to receive(:_execute_in_parallel).with(any_args).and_call_original.exactly(2).times
|
55
|
+
the_hosts = [master, agent]
|
56
|
+
|
57
|
+
allow(dsl).to receive(:create_remote_file).and_return(true)
|
58
|
+
allow(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
59
|
+
the_hosts.each do |host|
|
60
|
+
allow(dsl).to receive(:on).with(host, 'puppet_command', acceptable_exit_codes: [0])
|
61
|
+
end
|
62
|
+
|
63
|
+
result = dsl.apply_manifest_on(the_hosts, 'include foobar')
|
64
|
+
expect(result).to be_an(Array)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'runs block_on in parallel if set' do
|
68
|
+
InParallel::InParallelExecutor.logger = dsl.logger
|
69
|
+
the_hosts = [master, agent]
|
70
|
+
|
71
|
+
allow(dsl).to receive(:create_remote_file).and_return(true)
|
72
|
+
allow(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
73
|
+
the_hosts.each do |host|
|
74
|
+
allow(dsl).to receive(:on).with(host, 'puppet_command', acceptable_exit_codes: [0])
|
75
|
+
end
|
76
|
+
expect(dsl).to receive(:block_on).with(anything, { run_in_parallel: true })
|
77
|
+
|
78
|
+
dsl.apply_manifest_on(the_hosts, 'include foobar', run_in_parallel: true)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'adds acceptable exit codes with :catch_failures' do
|
82
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
83
|
+
expect(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
84
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [0, 2])
|
85
|
+
|
86
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', catch_failures: true)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'allows acceptable exit codes through :catch_failures' do
|
90
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
91
|
+
expect(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
92
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [4, 0, 2])
|
93
|
+
|
94
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', acceptable_exit_codes: [4], catch_failures: true)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'enforces a 0 exit code through :catch_changes' do
|
98
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
99
|
+
expect(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
100
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [0])
|
101
|
+
|
102
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', catch_changes: true)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'enforces a 2 exit code through :expect_changes' do
|
106
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
107
|
+
expect(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
108
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [2])
|
109
|
+
|
110
|
+
dsl.apply_manifest_on(
|
111
|
+
agent,
|
112
|
+
'class { "boo": }',
|
113
|
+
expect_changes: true,
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'enforces exit codes through :expect_failures' do
|
118
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
119
|
+
expect(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
120
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [1, 4, 6])
|
121
|
+
|
122
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', expect_failures: true)
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'enforces exit codes through :expect_failures and catch_failures' do
|
126
|
+
expect do
|
127
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', expect_failures: true, catch_failures: true)
|
128
|
+
end.to raise_error(ArgumentError, /catch_failures.+expect_failures/)
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'enforces merges exit codes from :expect_failures and acceptable_exit_codes' do
|
132
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
133
|
+
expect(Beaker::PuppetCommand).to receive(:new).and_return('puppet_command')
|
134
|
+
|
135
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [1, 2, 3, 4, 5, 6])
|
136
|
+
|
137
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', acceptable_exit_codes: (1..5), expect_failures: true)
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'can set the --parser future flag' do
|
141
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
142
|
+
|
143
|
+
expect(Beaker::PuppetCommand).to receive(:new).with('apply', anything, include(parser: 'future')).and_return('puppet_command')
|
144
|
+
|
145
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [0])
|
146
|
+
|
147
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', future_parser: true)
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'can set the --noops flag' do
|
151
|
+
expect(dsl).to receive(:create_remote_file).and_return(true)
|
152
|
+
expect(Beaker::PuppetCommand).to receive(:new).with('apply', anything, include(noop: nil)).and_return('puppet_command')
|
153
|
+
expect(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [0])
|
154
|
+
|
155
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', noop: true)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'can set the --debug flag' do
|
159
|
+
allow(dsl).to receive(:hosts).and_return(hosts)
|
160
|
+
allow(dsl).to receive(:create_remote_file).and_return(true)
|
161
|
+
allow(dsl).to receive(:on).with(agent, 'puppet_command', acceptable_exit_codes: [0])
|
162
|
+
|
163
|
+
expect(Beaker::PuppetCommand).to receive(:new).with(
|
164
|
+
'apply', anything, include(debug: nil)
|
165
|
+
).and_return('puppet_command')
|
166
|
+
|
167
|
+
dsl.apply_manifest_on(agent, 'class { "boo": }', debug: true)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe '#apply_manifest' do
|
172
|
+
it 'delegates to #apply_manifest_on with the default host' do
|
173
|
+
expect(dsl).to receive(:default).and_return(agent)
|
174
|
+
expect(dsl).to receive(:apply_manifest_on).with(agent, 'manifest', { opt: 'value' }).once
|
175
|
+
|
176
|
+
dsl.apply_manifest('manifest', { opt: 'value' })
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe '#fact_on' do
|
181
|
+
it 'retrieves a fact on a single host' do
|
182
|
+
result = instance_double(Beaker::Result, stdout: '{"osfamily": "family"}')
|
183
|
+
expect(dsl).to receive(:on).and_return(result)
|
184
|
+
|
185
|
+
expect(dsl.fact_on('host', 'osfamily')).to eq('family')
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'converts each element to a structured fact when it receives an array of results from #on' do
|
189
|
+
times = hosts.length
|
190
|
+
|
191
|
+
result = instance_double(Beaker::Result, stdout: '{"os": {"name":"name", "family": "family"}}')
|
192
|
+
allow(dsl).to receive(:on).and_return([result] * times)
|
193
|
+
|
194
|
+
expect(dsl.fact_on(hosts, 'os')).to eq([{ 'name' => 'name', 'family' => 'family' }] * times)
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'returns a single result for single host' do
|
198
|
+
result = instance_double(Beaker::Result, stdout: '{"osfamily": "family"}')
|
199
|
+
allow(dsl).to receive(:on).and_return(result)
|
200
|
+
|
201
|
+
expect(dsl.fact_on('host', 'osfamily')).to eq('family')
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'preserves data types' do
|
205
|
+
result = instance_double(Beaker::Result, stdout: '{"identity": { "uid": 0, "user": "root", "privileged": true }}')
|
206
|
+
allow(dsl).to receive(:on).and_return(result)
|
207
|
+
|
208
|
+
structured_fact = dsl.fact_on('host', 'identity')
|
209
|
+
|
210
|
+
expect(structured_fact['uid'].class).to be(Integer)
|
211
|
+
expect(structured_fact['user'].class).to be(String)
|
212
|
+
expect(structured_fact['privileged'].class).to be(TrueClass)
|
213
|
+
end
|
214
|
+
|
215
|
+
it 'raises an error when it receives a symbol for a fact' do
|
216
|
+
expect { dsl.fact_on('host', :osfamily) }
|
217
|
+
.to raise_error(ArgumentError, /fact_on's `name` option must be a String. You provided a Symbol: 'osfamily'/)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
describe '#fact' do
|
222
|
+
it 'delegates to #fact_on with the default host' do
|
223
|
+
expect(dsl).to receive(:fact_on).with(anything, 'osfamily', {}).once
|
224
|
+
expect(dsl).to receive(:default)
|
225
|
+
|
226
|
+
dsl.fact('osfamily')
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|