puppetlabs_spec_helper 2.6.2 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathspec'
4
+
5
+ module PuppetlabsSpecHelper; end
6
+ module PuppetlabsSpecHelper::Tasks; end
7
+
8
+ class PuppetlabsSpecHelper::Tasks::CheckSymlinks
9
+ DEFAULT_IGNORED = [
10
+ '/.git/',
11
+ '/.bundle/',
12
+ '/vendor/',
13
+ ].freeze
14
+
15
+ IGNORE_LIST_FILES = [
16
+ '.pdkignore',
17
+ '.gitignore',
18
+ ].freeze
19
+
20
+ def check(dir = Dir.pwd)
21
+ dir = Pathname.new(dir) unless dir.is_a?(Pathname)
22
+ results = []
23
+
24
+ dir.each_child(true) do |child|
25
+ next if ignored?(child.to_s)
26
+
27
+ if child.symlink?
28
+ results << child
29
+ elsif child.directory? && child.basename.to_s !~ %r{^(\.git|\.?bundle)$}
30
+ results.concat(check(child))
31
+ end
32
+ end
33
+
34
+ results
35
+ end
36
+
37
+ def ignored?(path)
38
+ path = "#{path}/" if File.directory?(path)
39
+
40
+ !ignore_pathspec.match_paths([path], Dir.pwd).empty?
41
+ end
42
+
43
+ def ignore_pathspec
44
+ @ignore_pathspec ||= PathSpec.new(DEFAULT_IGNORED).tap do |pathspec|
45
+ IGNORE_LIST_FILES.each do |f|
46
+ next unless File.file?(f) && File.readable?(f)
47
+
48
+ File.open(f, 'r') { |fd| pathspec.add(fd) }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,462 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'yaml'
4
+ require 'open3'
5
+ require 'json'
6
+
7
+ module PuppetlabsSpecHelper; end
8
+ module PuppetlabsSpecHelper::Tasks; end
9
+
10
+ module PuppetlabsSpecHelper::Tasks::FixtureHelpers
11
+ # This is a helper for the self-symlink entry of fixtures.yml
12
+ def source_dir
13
+ Dir.pwd
14
+ end
15
+
16
+ # @return [String] - the name of current module
17
+ def module_name
18
+ raise ArgumentError unless File.file?('metadata.json') && File.readable?('metadata.json')
19
+
20
+ metadata = JSON.parse(File.read('metadata.json'))
21
+ metadata_name = metadata.fetch('name', nil) || ''
22
+
23
+ raise ArgumentError if metadata_name.empty?
24
+
25
+ metadata_name.split('-').last
26
+ rescue JSON::ParserError, ArgumentError
27
+ File.basename(Dir.pwd).split('-').last
28
+ end
29
+
30
+ def module_version(path)
31
+ metadata_path = File.join(path, 'metadata.json')
32
+ raise ArgumentError unless File.file?(metadata_path) && File.readable?(metadata_path)
33
+
34
+ metadata = JSON.parse(File.read(metadata_path))
35
+ metadata.fetch('version', nil) || '0.0.1'
36
+ rescue JSON::ParserError, ArgumentError
37
+ logger.warn "Failed to find module version at path #{path}"
38
+ '0.0.1'
39
+ end
40
+
41
+ # @return [Hash] - returns a hash of all the fixture repositories
42
+ # @example
43
+ # {"puppetlabs-stdlib"=>{"target"=>"https://gitlab.com/puppetlabs/puppet-stdlib.git",
44
+ # "ref"=>nil, "branch"=>"main", "scm"=>nil,
45
+ # }}
46
+ def repositories
47
+ @repositories ||= fixtures('repositories') || {}
48
+ end
49
+
50
+ # @return [Hash] - returns a hash of all the fixture forge modules
51
+ # @example
52
+ # {"puppetlabs-stdlib"=>{"target"=>"spec/fixtures/modules/stdlib",
53
+ # "ref"=>nil, "branch"=>nil, "scm"=>nil,
54
+ # "flags"=>"--module_repository=https://myforge.example.com/", "subdir"=>nil}}
55
+ def forge_modules
56
+ @forge_modules ||= fixtures('forge_modules') || {}
57
+ end
58
+
59
+ # @return [Hash] - a hash of symlinks specified in the fixtures file
60
+ def symlinks
61
+ @symlinks ||= fixtures('symlinks') || {}
62
+ end
63
+
64
+ # @return [Hash] - returns a hash with the module name and the source directory
65
+ def auto_symlink
66
+ { module_name => "\#{source_dir}" }
67
+ end
68
+
69
+ # @return [Boolean] - true if the os is a windows system
70
+ def windows?
71
+ !!File::ALT_SEPARATOR
72
+ end
73
+
74
+ def fixtures(category)
75
+ fixtures_yaml = if ENV['FIXTURES_YML']
76
+ ENV['FIXTURES_YML']
77
+ elsif File.exist?('.fixtures.yml')
78
+ '.fixtures.yml'
79
+ elsif File.exist?('.fixtures.yaml')
80
+ '.fixtures.yaml'
81
+ else
82
+ false
83
+ end
84
+
85
+ begin
86
+ fixtures = if fixtures_yaml
87
+ YAML.load_file(fixtures_yaml) || { 'fixtures' => {} }
88
+ else
89
+ { 'fixtures' => {} }
90
+ end
91
+ rescue Errno::ENOENT
92
+ raise("Fixtures file not found: '#{fixtures_yaml}'")
93
+ rescue Psych::SyntaxError => e
94
+ raise("Found malformed YAML in '#{fixtures_yaml}' on line #{e.line} column #{e.column}: #{e.problem}")
95
+ end
96
+
97
+ unless fixtures.include?('fixtures')
98
+ # File is non-empty, but does not specify fixtures
99
+ raise("No 'fixtures' entries found in '#{fixtures_yaml}'; required")
100
+ end
101
+
102
+ fixture_defaults = if fixtures.include? 'defaults'
103
+ fixtures['defaults']
104
+ else
105
+ {}
106
+ end
107
+
108
+ fixtures = fixtures['fixtures']
109
+
110
+ if fixtures['symlinks'].nil?
111
+ fixtures['symlinks'] = auto_symlink
112
+ end
113
+
114
+ result = {}
115
+ if fixtures.include?(category) && !fixtures[category].nil?
116
+ defaults = { 'target' => 'spec/fixtures/modules' }
117
+
118
+ # load defaults from the `.fixtures.yml` `defaults` section
119
+ # for the requested category and merge them into my defaults
120
+ if fixture_defaults.include? category
121
+ defaults = defaults.merge(fixture_defaults[category])
122
+ end
123
+
124
+ fixtures[category].each do |fixture, opts|
125
+ # convert a simple string fixture to a hash, by
126
+ # using the string fixture as the `repo` option of the hash.
127
+ if opts.instance_of?(String)
128
+ opts = { 'repo' => opts }
129
+ end
130
+ # there should be a warning or something if it's not a hash...
131
+ next unless opts.instance_of?(Hash)
132
+
133
+ # merge our options into the defaults to get the
134
+ # final option list
135
+ opts = defaults.merge(opts)
136
+
137
+ next unless include_repo?(opts['puppet_version'])
138
+
139
+ real_target = eval("\"#{opts['target']}\"", binding, __FILE__, __LINE__) # evaluating target reference in this context (see auto_symlink)
140
+ real_source = eval("\"#{opts['repo']}\"", binding, __FILE__, __LINE__) # evaluating repo reference in this context (see auto_symlink)
141
+
142
+ result[real_source] = validate_fixture_hash!(
143
+ 'target' => File.join(real_target, fixture),
144
+ 'ref' => opts['ref'] || opts['tag'],
145
+ 'branch' => opts['branch'],
146
+ 'scm' => opts['scm'],
147
+ 'flags' => opts['flags'],
148
+ 'subdir' => opts['subdir'],
149
+ )
150
+ end
151
+ end
152
+ result
153
+ end
154
+
155
+ def validate_fixture_hash!(hash)
156
+ # Can only validate git based scm
157
+ return hash unless hash['scm'] == 'git'
158
+
159
+ # Forward slashes in the ref aren't allowed. And is probably a branch name.
160
+ raise ArgumentError, "The ref for #{hash['target']} is invalid (Contains a forward slash). If this is a branch name, please use the 'branch' setting instead." if hash['ref'] =~ %r{/}
161
+
162
+ hash
163
+ end
164
+
165
+ def include_repo?(version_range)
166
+ if version_range && defined?(SemanticPuppet)
167
+ puppet_spec = Gem::Specification.find_by_name('puppet')
168
+ puppet_version = SemanticPuppet::Version.parse(puppet_spec.version.to_s)
169
+
170
+ constraint = SemanticPuppet::VersionRange.parse(version_range)
171
+ constraint.include?(puppet_version)
172
+ else
173
+ true
174
+ end
175
+ end
176
+
177
+ def clone_repo(scm, remote, target, _subdir = nil, ref = nil, branch = nil, flags = nil)
178
+ args = []
179
+ case scm
180
+ when 'hg'
181
+ args.push('clone')
182
+ args.push('-b', branch) if branch
183
+ args.push(flags) if flags
184
+ args.push(remote, target)
185
+ when 'git'
186
+ args.push('clone')
187
+ args.push('--depth 1') unless ref
188
+ args.push('-b', branch) if branch
189
+ args.push(flags) if flags
190
+ args.push(remote, target)
191
+ else
192
+ raise "Unfortunately #{scm} is not supported yet"
193
+ end
194
+ result = system("#{scm} #{args.flatten.join ' '}")
195
+ unless File.exist?(target)
196
+ raise "Failed to clone #{scm} repository #{remote} into #{target}"
197
+ end
198
+
199
+ result
200
+ end
201
+
202
+ def update_repo(scm, target)
203
+ args = case scm
204
+ when 'hg'
205
+ ['pull']
206
+ when 'git'
207
+ ['fetch'].tap do |git_args|
208
+ git_args << '--unshallow' if shallow_git_repo?
209
+ end
210
+ else
211
+ raise "Unfortunately #{scm} is not supported yet"
212
+ end
213
+ system("#{scm} #{args.flatten.join(' ')}", chdir: target)
214
+ end
215
+
216
+ def shallow_git_repo?
217
+ File.file?(File.join('.git', 'shallow'))
218
+ end
219
+
220
+ def revision(scm, target, ref)
221
+ args = []
222
+ case scm
223
+ when 'hg'
224
+ args.push('update', '--clean', '-r', ref)
225
+ when 'git'
226
+ args.push('reset', '--hard', ref)
227
+ else
228
+ raise "Unfortunately #{scm} is not supported yet"
229
+ end
230
+ result = system("#{scm} #{args.flatten.join ' '}", chdir: target)
231
+ raise "Invalid ref #{ref} for #{target}" unless result
232
+ end
233
+
234
+ def valid_repo?(scm, target, remote)
235
+ return false unless File.directory?(target)
236
+ return true if scm == 'hg'
237
+
238
+ return true if git_remote_url(target) == remote
239
+
240
+ warn "Git remote for #{target} has changed, recloning repository"
241
+ FileUtils.rm_rf(target)
242
+ false
243
+ end
244
+
245
+ def git_remote_url(target)
246
+ output, status = Open3.capture2e('git', '--git-dir', File.join(target, '.git'), 'ls-remote', '--get-url', 'origin')
247
+ status.success? ? output.strip : nil
248
+ end
249
+
250
+ def remove_subdirectory(target, subdir)
251
+ unless subdir.nil?
252
+ Dir.mktmpdir do |tmpdir|
253
+ FileUtils.mv(Dir.glob("#{target}/#{subdir}/{.[^\.]*,*}"), tmpdir)
254
+ FileUtils.rm_rf("#{target}/#{subdir}")
255
+ FileUtils.mv(Dir.glob("#{tmpdir}/{.[^\.]*,*}"), target.to_s)
256
+ end
257
+ end
258
+ end
259
+
260
+ # creates a logger so we can log events with certain levels
261
+ def logger
262
+ unless @logger
263
+ require 'logger'
264
+ level = if ENV['ENABLE_LOGGER']
265
+ Logger::DEBUG
266
+ else
267
+ Logger::INFO
268
+ end
269
+ @logger = Logger.new($stderr)
270
+ @logger.level = level
271
+ end
272
+ @logger
273
+ end
274
+
275
+ def module_working_directory
276
+ # The problem with the relative path is that PMT doesn't expand the path properly and so passing in a relative path here
277
+ # becomes something like C:\somewhere\backslashes/spec/fixtures/work-dir on Windows, and then PMT barfs itself.
278
+ # This has been reported as https://tickets.puppetlabs.com/browse/PUP-4884
279
+ File.expand_path(ENV['MODULE_WORKING_DIR'] || 'spec/fixtures/work-dir')
280
+ end
281
+
282
+ # returns the current thread count that is currently active
283
+ # a status of false or nil means the thread completed
284
+ # so when anything else we count that as a active thread
285
+ # @return [Integer] - current thread count
286
+ def current_thread_count(items)
287
+ active_threads = items.find_all do |_item, opts|
288
+ if opts[:thread]
289
+ opts[:thread].status
290
+ else
291
+ false
292
+ end
293
+ end
294
+ logger.debug "Current thread count #{active_threads.count}"
295
+ active_threads.count
296
+ end
297
+
298
+ # @summary Set a limit on the amount threads used, defaults to 10
299
+ # MAX_FIXTURE_THREAD_COUNT can be used to set this limit
300
+ # @return [Integer] - returns the max_thread_count
301
+ def max_thread_limit
302
+ @max_thread_limit ||= (ENV['MAX_FIXTURE_THREAD_COUNT'] || 10).to_i
303
+ end
304
+
305
+ # @param items [Hash] - a hash of either repositories or forge modules
306
+ # @param [Block] - the method you wish to use to download the item
307
+ def download_items(items)
308
+ items.each do |remote, opts|
309
+ # get the current active threads that are alive
310
+ count = current_thread_count(items)
311
+ if count < max_thread_limit
312
+ logger.debug "New Thread started for #{remote}"
313
+ # start up a new thread and store it in the opts hash
314
+ opts[:thread] = Thread.new do
315
+ yield(remote, opts)
316
+ end
317
+ else
318
+ # the last thread started should be the longest wait
319
+ item, item_opts = items.find_all { |_i, o| o.key?(:thread) }.last
320
+ logger.debug "Waiting on #{item}"
321
+ item_opts[:thread].join # wait for the thread to finish
322
+ # now that we waited lets try again
323
+ redo
324
+ end
325
+ end
326
+ # wait for all the threads to finish
327
+ items.each { |_remote, opts| opts[:thread].join }
328
+ end
329
+
330
+ # @param target [String] - the target directory
331
+ # @param link [String] - the name of the link you wish to create
332
+ # works on windows and linux
333
+ def setup_symlink(target, link)
334
+ link = link['target']
335
+ return if File.symlink?(link)
336
+
337
+ logger.info("Creating symlink from #{link} to #{target}")
338
+ if windows?
339
+ target = File.join(File.dirname(link), target) unless Pathname.new(target).absolute?
340
+ if Dir.respond_to?(:create_junction)
341
+ Dir.create_junction(link, target)
342
+ else
343
+ system("call mklink /J \"#{link.tr('/', '\\')}\" \"#{target.tr('/', '\\')}\"")
344
+ end
345
+ else
346
+ FileUtils.ln_sf(target, link)
347
+ end
348
+ end
349
+
350
+ # @return [Boolean] - returns true if the module was downloaded successfully, false otherwise
351
+ # @param [String] - the remote url or namespace/name of the module to download
352
+ # @param [Hash] - list of options such as version, branch, ref
353
+ def download_repository(remote, opts)
354
+ scm = 'git'
355
+ target = opts['target']
356
+ subdir = opts['subdir']
357
+ ref = opts['ref']
358
+ scm = opts['scm'] if opts['scm']
359
+ branch = opts['branch'] if opts['branch']
360
+ flags = opts['flags']
361
+ if valid_repo?(scm, target, remote)
362
+ update_repo(scm, target)
363
+ else
364
+ clone_repo(scm, remote, target, subdir, ref, branch, flags)
365
+ end
366
+ revision(scm, target, ref) if ref
367
+ remove_subdirectory(target, subdir) if subdir
368
+ end
369
+
370
+ # @return [String] - the spec/fixtures/modules directory in the module root folder
371
+ def module_target_dir
372
+ @module_target_dir ||= File.expand_path('spec/fixtures/modules')
373
+ end
374
+
375
+ # @return [Boolean] - returns true if the module was downloaded successfully, false otherwise
376
+ # @param [String] - the remote url or namespace/name of the module to download
377
+ # @param [Hash] - list of options such as version
378
+ def download_module(remote, opts)
379
+ ref = ''
380
+ flags = ''
381
+ if opts.instance_of?(String)
382
+ target = opts
383
+ elsif opts.instance_of?(Hash)
384
+ target = opts['target']
385
+ ref = " --version #{opts['ref']}" unless opts['ref'].nil?
386
+ flags = " #{opts['flags']}" if opts['flags']
387
+ end
388
+
389
+ return false if File.directory?(target) && (ref.empty? || opts['ref'] == module_version(target))
390
+
391
+ # The PMT cannot handle multi threaded runs due to cache directory collisons
392
+ # so we randomize the directory instead.
393
+ # Does working_dir even need to be passed?
394
+ Dir.mktmpdir do |working_dir|
395
+ command = "puppet module install#{ref}#{flags} --ignore-dependencies" \
396
+ ' --force' \
397
+ " --module_working_dir \"#{working_dir}\"" \
398
+ " --target-dir \"#{module_target_dir}\" \"#{remote}\""
399
+
400
+ unless system(command)
401
+ raise "Failed to install module #{remote} to #{module_target_dir}"
402
+ end
403
+ end
404
+ $CHILD_STATUS.success?
405
+ end
406
+ end
407
+
408
+ include PuppetlabsSpecHelper::Tasks::FixtureHelpers # DSL include # rubocop:disable Style/MixinUsage
409
+
410
+ desc 'Create the fixtures directory'
411
+ task :spec_prep do
412
+ # Ruby only sets File::ALT_SEPARATOR on Windows and Rubys standard library
413
+ # uses this to check for Windows
414
+ if windows?
415
+ begin
416
+ require 'win32/dir'
417
+ rescue LoadError
418
+ warn 'win32-dir gem not installed, falling back to executing mklink directly'
419
+ end
420
+ end
421
+
422
+ # git has a race condition creating that directory, that would lead to aborted clone operations
423
+ FileUtils.mkdir_p('spec/fixtures/modules')
424
+
425
+ symlinks.each { |target, link| setup_symlink(target, link) }
426
+
427
+ download_items(repositories) { |remote, opts| download_repository(remote, opts) }
428
+
429
+ download_items(forge_modules) { |remote, opts| download_module(remote, opts) }
430
+
431
+ FileUtils.mkdir_p('spec/fixtures/manifests')
432
+ FileUtils.touch('spec/fixtures/manifests/site.pp')
433
+ end
434
+
435
+ desc 'Clean up the fixtures directory'
436
+ task :spec_clean do
437
+ repositories.each do |_remote, opts|
438
+ target = opts['target']
439
+ FileUtils.rm_rf(target)
440
+ end
441
+
442
+ forge_modules.each do |_remote, opts|
443
+ target = opts['target']
444
+ FileUtils.rm_rf(target)
445
+ end
446
+
447
+ FileUtils.rm_rf(module_working_directory)
448
+
449
+ Rake::Task[:spec_clean_symlinks].invoke
450
+
451
+ if File.zero?('spec/fixtures/manifests/site.pp')
452
+ FileUtils.rm_f('spec/fixtures/manifests/site.pp')
453
+ end
454
+ end
455
+
456
+ desc 'Clean up any fixture symlinks'
457
+ task :spec_clean_symlinks do
458
+ fixtures('symlinks').each do |_source, opts|
459
+ target = opts['target']
460
+ FileUtils.rm_f(target)
461
+ end
462
+ end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PuppetlabsSpecHelper
2
- VERSION = "2.6.2"
4
+ VERSION = '4.0.1'
3
5
 
4
6
  # compat for pre-1.2.0 users; deprecated
5
7
  module Version
@@ -1,4 +1,6 @@
1
- $:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
2
4
 
3
5
  require 'puppetlabs_spec_helper/puppet_spec_helper'
4
6
 
@@ -1,33 +1,38 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'puppetlabs_spec_helper/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "puppetlabs_spec_helper"
8
+ spec.name = 'puppetlabs_spec_helper'
8
9
  spec.version = PuppetlabsSpecHelper::VERSION
9
- spec.authors = ["Puppet, Inc.", "Community Contributors"]
10
- spec.email = ["modules-team@puppet.com"]
10
+ spec.authors = ['Puppet, Inc.', 'Community Contributors']
11
+ spec.email = ['modules-team@puppet.com']
11
12
 
12
- spec.summary = %q{Standard tasks and configuration for module spec tests.}
13
- spec.description = %q{Contains rake tasks and a standard spec_helper for running spec tests on puppet modules.}
14
- spec.homepage = "http://github.com/puppetlabs/puppetlabs_spec_helper"
13
+ spec.summary = 'Standard tasks and configuration for module spec tests.'
14
+ spec.description = 'Contains rake tasks and a standard spec_helper for running spec tests on puppet modules.'
15
+ spec.homepage = 'http://github.com/puppetlabs/puppetlabs_spec_helper'
16
+ spec.license = 'Apache-2.0'
15
17
 
16
18
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
19
+ spec.bindir = 'exe'
18
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
21
+ spec.require_paths = ['lib']
22
+
23
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.4')
20
24
 
21
- spec.add_runtime_dependency "mocha", "~> 1.0"
22
- spec.add_runtime_dependency "puppet-lint", "~> 2.0"
23
- spec.add_runtime_dependency "puppet-syntax", "~> 2.0"
24
- spec.add_runtime_dependency "rspec-puppet", "~> 2.0"
25
+ spec.add_runtime_dependency 'mocha', '~> 1.0'
26
+ spec.add_runtime_dependency 'pathspec', '>= 0.2.1', '< 1.1.0'
27
+ spec.add_runtime_dependency 'puppet-lint', '~> 2.0'
28
+ spec.add_runtime_dependency 'puppet-syntax', ['>= 2.0', '< 4']
29
+ spec.add_runtime_dependency 'rspec-puppet', '~> 2.0'
25
30
 
26
- spec.add_development_dependency "bundler", "~> 1.12"
27
- spec.add_development_dependency "pry"
28
- spec.add_development_dependency "puppet"
29
- spec.add_development_dependency "rake", "~> 10.0"
30
- spec.add_development_dependency "rspec", "~> 3.0"
31
- spec.add_development_dependency "yard"
32
- spec.add_development_dependency "gettext-setup", "~> 0.29"
31
+ spec.add_development_dependency 'bundler'
32
+ spec.add_development_dependency 'fakefs', ['>= 0.13.3', '< 2']
33
+ spec.add_development_dependency 'pry'
34
+ spec.add_development_dependency 'puppet'
35
+ spec.add_development_dependency 'rake', ['>= 10.0', '< 14']
36
+ spec.add_development_dependency 'rspec', '~> 3.0'
37
+ spec.add_development_dependency 'yard'
33
38
  end
@@ -1,4 +1,6 @@
1
- $:.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
2
4
 
3
5
  require 'puppetlabs_spec_helper/puppetlabs_spec_helper'
4
6