beaker_puppet_helpers 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|