puppetlabs-onceover 5.0.3 → 5.0.4

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.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.rubocop_todo.yml +35 -15
  4. data/CHANGELOG.md +387 -3
  5. data/Gemfile +1 -0
  6. data/lib/puppetlabs-onceover/cli/plugins.rb +3 -3
  7. data/lib/puppetlabs-onceover/controlrepo.rb +8 -7
  8. data/lib/puppetlabs-onceover/version.rb +1 -1
  9. data/spec/fixtures/controlrepos/deploy_test/Puppetfile +3 -0
  10. data/spec/fixtures/controlrepos/deploy_test/a_dir/nested_file.txt +1 -0
  11. data/spec/fixtures/controlrepos/deploy_test/a_symlink +1 -0
  12. data/spec/fixtures/controlrepos/deploy_test/environment.conf +1 -0
  13. data/spec/fixtures/controlrepos/deploy_test/regular_file.txt +1 -0
  14. data/spec/puppetlabs-onceover/beaker_spec.rb +293 -0
  15. data/spec/puppetlabs-onceover/class_spec.rb +89 -0
  16. data/spec/puppetlabs-onceover/cli/run_spec.rb +80 -0
  17. data/spec/puppetlabs-onceover/cli/show_spec.rb +67 -0
  18. data/spec/puppetlabs-onceover/cli_spec.rb +79 -0
  19. data/spec/puppetlabs-onceover/controlrepo_spec.rb +787 -5
  20. data/spec/puppetlabs-onceover/deploy_spec.rb +246 -0
  21. data/spec/puppetlabs-onceover/group_spec.rb +121 -0
  22. data/spec/puppetlabs-onceover/logger_spec.rb +30 -0
  23. data/spec/puppetlabs-onceover/node_spec.rb +124 -0
  24. data/spec/puppetlabs-onceover/rake_tasks_spec.rb +173 -0
  25. data/spec/puppetlabs-onceover/rspec/formatters_spec.rb +361 -0
  26. data/spec/puppetlabs-onceover/runner_spec.rb +307 -0
  27. data/spec/puppetlabs-onceover/test_spec.rb +177 -0
  28. data/spec/puppetlabs-onceover/testconfig_spec.rb +451 -0
  29. data/spec/puppetlabs-onceover/vendored_modules_spec.rb +302 -0
  30. data/spec/puppetlabs-onceover/version_spec.rb +10 -0
  31. data/spec/spec_helper.rb +8 -0
  32. metadata +22 -1
@@ -1,18 +1,800 @@
1
1
  require 'spec_helper'
2
2
  require 'puppetlabs-onceover/controlrepo'
3
+ require 'r10k/puppetfile'
4
+ require 'tmpdir'
5
+ require 'fileutils'
6
+ require 'yaml'
3
7
 
4
8
  describe "PuppetlabsOnceover::Controlrepo" do
9
+ # TestConfig.new (used by #spec_tests) instantiates PuppetlabsOnceover::Class/Node/Group/Test,
10
+ # each of which keep a process-global @@all array. Reset them around every example here too,
11
+ # so this file doesn't leak state into (or pick up state from) other spec files' examples.
12
+ before(:each) do
13
+ PuppetlabsOnceover::Class.class_variable_set(:@@all, []) if defined?(PuppetlabsOnceover::Class)
14
+ PuppetlabsOnceover::Node.class_variable_set(:@@all, []) if defined?(PuppetlabsOnceover::Node)
15
+ PuppetlabsOnceover::Group.class_variable_set(:@@all, []) if defined?(PuppetlabsOnceover::Group)
16
+ PuppetlabsOnceover::Test.class_variable_set(:@@all, []) if defined?(PuppetlabsOnceover::Test)
17
+ end
18
+
19
+ let(:basic_path) { File.expand_path('spec/fixtures/controlrepos/basic') }
20
+ let(:minimal_path) { File.expand_path('spec/fixtures/controlrepos/minimal') }
21
+ let(:factsets_path) { File.expand_path('spec/fixtures/controlrepos/factsets') }
22
+
5
23
  context "in a barebones controlrepo" do
6
24
  before do
7
- @repo = PuppetlabsOnceover::Controlrepo.new(
8
- {
9
- path: 'spec/fixtures/controlrepos/minimal'
10
- }
11
- )
25
+ @repo = PuppetlabsOnceover::Controlrepo.new(path: minimal_path)
12
26
  end
13
27
 
14
28
  context "without hiera.yaml" do
15
29
  it { expect(@repo.hiera_config_file_relative_path).to be_nil }
30
+ it { expect(@repo.hiera_config_file).to be_nil }
31
+ it { expect(@repo.hiera_config).to be_nil }
32
+ end
33
+ end
34
+
35
+ describe "#initialize" do
36
+ it "uses the given :path directly" do
37
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
38
+ expect(repo.root).to eq(basic_path)
39
+ end
40
+
41
+ it "traverses upward from Dir.pwd looking for environment.conf when no :path is given" do
42
+ nested = File.join(basic_path, 'site', 'role', 'manifests')
43
+ Dir.chdir(nested) do
44
+ repo = PuppetlabsOnceover::Controlrepo.new
45
+ expect(repo.root).to eq(basic_path)
46
+ end
47
+ end
48
+
49
+ it "throws if it can never find an environment.conf above the cwd" do
50
+ Dir.mktmpdir do |tmp|
51
+ Dir.chdir(tmp) do
52
+ # `throw "..."` with a String label (not caught anywhere) surfaces as an
53
+ # UncaughtThrowError -- this is the real behavior of Controlrepo#initialize,
54
+ # not a mistake in this spec.
55
+ expect { PuppetlabsOnceover::Controlrepo.new }.to raise_error(UncaughtThrowError)
56
+ end
57
+ end
58
+ end
59
+
60
+ it "merges options found in an onceover.yaml when present" do
61
+ repo = PuppetlabsOnceover::Controlrepo.new(path: File.expand_path('spec/fixtures/controlrepos/function_mocking'))
62
+ expect(repo.opts[:debug]).to eq(true)
63
+ end
64
+
65
+ it "sets the logger level to debug when :debug option is truthy" do
66
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path, debug: true)
67
+ expect(repo.opts[:debug]).to eq(true)
68
+ end
69
+
70
+ it "expands a relative :manifest option against root" do
71
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path, manifest: 'site/role/manifests/example.pp')
72
+ expect(repo.manifest).to eq(File.expand_path('site/role/manifests/example.pp', basic_path))
73
+ end
74
+
75
+ it "leaves manifest nil when not set anywhere" do
76
+ repo = PuppetlabsOnceover::Controlrepo.new(path: minimal_path)
77
+ expect(repo.manifest).to be_nil
78
+ end
79
+
80
+ it "respects the ONCEOVER_YAML env var" do
81
+ custom_yaml = File.expand_path('spec/fixtures/controlrepos/function_mocking/spec/onceover.yaml')
82
+ begin
83
+ ENV['ONCEOVER_YAML'] = custom_yaml
84
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
85
+ expect(repo.onceover_yaml).to eq(custom_yaml)
86
+ ensure
87
+ ENV.delete('ONCEOVER_YAML')
88
+ end
89
+ end
90
+
91
+ it "accepts explicit :facts_files" do
92
+ f = File.expand_path('spec/fixtures/controlrepos/factsets/spec/factsets/centos_with_env.json')
93
+ repo = PuppetlabsOnceover::Controlrepo.new(path: factsets_path, facts_files: [f])
94
+ expect(repo.facts_files).to eq([f])
95
+ end
96
+
97
+ it "accepts a custom :role_regex and :profile_regex" do
98
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path, role_regex: '^role::', profile_regex: '^profile::')
99
+ expect(repo.role_regex).to eq(/^role::/)
100
+ expect(repo.profile_regex).to eq(/^profile::/)
101
+ end
102
+ end
103
+
104
+ describe "class-level delegation methods" do
105
+ before do
106
+ @repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
107
+ end
108
+
109
+ it "delegates .root" do
110
+ expect(PuppetlabsOnceover::Controlrepo.root).to eq(@repo.root)
111
+ end
112
+
113
+ it "delegates .puppetfile" do
114
+ expect(PuppetlabsOnceover::Controlrepo.puppetfile).to eq(@repo.puppetfile)
115
+ end
116
+
117
+ it "delegates .facts_files" do
118
+ expect(PuppetlabsOnceover::Controlrepo.facts_files).to eq(@repo.facts_files)
119
+ end
120
+
121
+ it "delegates .classes" do
122
+ expect(PuppetlabsOnceover::Controlrepo.classes).to eq(@repo.classes)
123
+ end
124
+
125
+ it "delegates .roles" do
126
+ expect(PuppetlabsOnceover::Controlrepo.roles).to eq(@repo.roles)
127
+ end
128
+
129
+ it "delegates .profiles" do
130
+ expect(PuppetlabsOnceover::Controlrepo.profiles).to eq(@repo.profiles)
131
+ end
132
+
133
+ it "delegates .config" do
134
+ expect(PuppetlabsOnceover::Controlrepo.config).to eq(@repo.config)
135
+ end
136
+
137
+ it "delegates .facts" do
138
+ repo = PuppetlabsOnceover::Controlrepo.new(path: factsets_path)
139
+ expect(PuppetlabsOnceover::Controlrepo.facts).to eq(repo.facts(nil, 'values'))
140
+ end
141
+
142
+ it "delegates .trusted_facts" do
143
+ repo = PuppetlabsOnceover::Controlrepo.new(path: factsets_path)
144
+ expect(PuppetlabsOnceover::Controlrepo.trusted_facts).to eq(repo.facts(nil, 'trusted'))
145
+ end
146
+
147
+ it "delegates .trusted_external_facts" do
148
+ repo = PuppetlabsOnceover::Controlrepo.new(path: factsets_path)
149
+ expect(PuppetlabsOnceover::Controlrepo.trusted_external_facts).to eq(repo.facts(nil, 'trusted_external'))
150
+ end
151
+
152
+ it "delegates .hiera_config_file" do
153
+ expect(PuppetlabsOnceover::Controlrepo.hiera_config_file).to eq(@repo.hiera_config_file)
154
+ end
155
+ end
156
+
157
+ describe "#to_s" do
158
+ it "renders a human readable summary" do
159
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
160
+ output = repo.to_s
161
+ expect(output).to include('puppetfile')
162
+ expect(output).to include('roles')
163
+ expect(output).to include('profiles')
164
+ end
165
+ end
166
+
167
+ describe "#roles and #profiles" do
168
+ it "finds roles matching the default role_regex" do
169
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
170
+ expect(repo.roles).to include('role::webserver', 'role::database_server', 'role::example')
171
+ end
172
+
173
+ it "finds profiles matching the default profile_regex" do
174
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
175
+ expect(repo.profiles).to include('profile::example', 'profile::base')
176
+ end
177
+ end
178
+
179
+ describe "#classes / private #get_classes / #find_classname" do
180
+ it "removes interpolated ($) modulepath entries before scanning" do
181
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
182
+ classes = repo.classes
183
+ expect(classes).not_to be_empty
184
+ end
185
+
186
+ it "#find_classname returns nil for a file with no class definition" do
187
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
188
+ Dir.mktmpdir do |dir|
189
+ file = File.join(dir, 'nope.pp')
190
+ File.write(file, "# just a comment\n")
191
+ expect(repo.send(:find_classname, file)).to be_nil
192
+ end
193
+ end
194
+
195
+ it "#find_classname rescues ArgumentError from an invalid byte sequence and keeps scanning" do
196
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
197
+ Dir.mktmpdir do |dir|
198
+ file = File.join(dir, 'bad_encoding.pp')
199
+ # An invalid UTF-8 byte sequence makes `line =~ regex` raise ArgumentError.
200
+ File.binwrite(file, "# \xFF\xFE bad line\n")
201
+ expect { repo.send(:find_classname, file) }.not_to raise_error
202
+ expect(repo.send(:find_classname, file)).to be_nil
203
+ end
204
+ end
205
+
206
+ it "#find_classname supports namespaced classes" do
207
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
208
+ Dir.mktmpdir do |dir|
209
+ file = File.join(dir, 'ns.pp')
210
+ File.write(file, "class role::deep::nested {\n}\n")
211
+ expect(repo.send(:find_classname, file)).to eq('role::deep::nested')
212
+ end
213
+ end
214
+
215
+ it "#get_classes finds classes recursively under a directory" do
216
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
217
+ classes = repo.send(:get_classes, File.join(basic_path, 'site'))
218
+ expect(classes).to include('role::webserver')
16
219
  end
17
220
  end
221
+
222
+ describe "#facts" do
223
+ let(:repo) { PuppetlabsOnceover::Controlrepo.new(path: factsets_path) }
224
+
225
+ it "returns an array of fact hashes" do
226
+ expect(repo.facts).to be_an(Array)
227
+ expect(repo.facts).not_to be_empty
228
+ end
229
+
230
+ it "raises if filter is not a Hash" do
231
+ expect { repo.facts('not-a-hash') }.to raise_error(RuntimeError, /Filter param must be a hash/)
232
+ end
233
+
234
+ it "filters facts by a matching top-level key/value" do
235
+ all = repo.facts
236
+ sample = all.first
237
+ key, value = sample.first
238
+ filtered = repo.facts({ key => value })
239
+ expect(filtered).not_to be_empty
240
+ end
241
+
242
+ it "returns an empty array when the filter matches nothing" do
243
+ filtered = repo.facts({ 'this_key_does_not_exist_anywhere' => 'nope' })
244
+ expect(filtered).to eq([])
245
+ end
246
+
247
+ it "supports nested hash filters via #keypair_is_in_hash" do
248
+ expect(repo.send(:keypair_is_in_hash, { 'a' => { 'b' => 'c' } }, 'a', { 'b' => 'c' })).to eq(true)
249
+ expect(repo.send(:keypair_is_in_hash, { 'a' => { 'b' => 'c' } }, 'a', { 'b' => 'nope' })).to eq(false)
250
+ expect(repo.send(:keypair_is_in_hash, {}, 'missing', 'value')).to eq(false)
251
+ end
252
+
253
+ it "falls back to trusted/trusted_external top level keys when 'values' key is absent" do
254
+ repo2 = PuppetlabsOnceover::Controlrepo.new(path: factsets_path)
255
+ expect { repo2.facts(nil, 'trusted') }.not_to raise_error
256
+ end
257
+ end
258
+
259
+ describe "#read_facts (private)" do
260
+ let(:repo) { PuppetlabsOnceover::Controlrepo.new(path: factsets_path) }
261
+
262
+ it "parses valid JSON" do
263
+ f = Dir[File.join(factsets_path, 'spec', 'factsets', '*.json')].first
264
+ expect(repo.send(:read_facts, f)).to be_a(Hash)
265
+ end
266
+
267
+ it "raises a friendly error for invalid JSON" do
268
+ Dir.mktmpdir do |dir|
269
+ bad = File.join(dir, 'bad.json')
270
+ File.write(bad, "{not json")
271
+ expect { repo.send(:read_facts, bad) }.to raise_error(RuntimeError, /Could not parse/)
272
+ end
273
+ end
274
+ end
275
+
276
+ describe "#config" do
277
+ it "parses modulepath, strips comments, and handles values containing '='" do
278
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
279
+ config = repo.config
280
+ expect(config['modulepath']).to eq(['site', 'modules', '$basemodulepath'])
281
+ expect(config['value_with_equals']).to eq("'foo=bar'")
282
+ end
283
+
284
+ it "raises a friendly error when modulepath is missing from environment.conf" do
285
+ Dir.mktmpdir do |dir|
286
+ File.write(File.join(dir, 'environment.conf'), "config_version = 'x'\n")
287
+ expect { PuppetlabsOnceover::Controlrepo.new(path: dir) }.to raise_error(RuntimeError, /modulepath was not found/)
288
+ end
289
+ end
290
+ end
291
+
292
+ describe "#hiera_config_file / #hiera_config_file_relative_path / #hiera_config / #hiera_config=" do
293
+ it "prefers hiera.yaml in spec_dir over root" do
294
+ Dir.mktmpdir do |dir|
295
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
296
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules\n")
297
+ File.write(File.join(dir, 'spec', 'hiera.yaml'), "---\nversion: 5\n")
298
+ File.write(File.join(dir, 'hiera.yaml'), "---\nversion: 3\n")
299
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
300
+ expect(repo.hiera_config_file).to eq(File.expand_path('spec/hiera.yaml', dir))
301
+ expect(repo.hiera_config['version']).to eq(5)
302
+ expect(repo.hiera_config_file_relative_path).to eq('spec/hiera.yaml')
303
+ end
304
+ end
305
+
306
+ it "falls back to hiera.yaml at root when not present in spec_dir" do
307
+ Dir.mktmpdir do |dir|
308
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
309
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules\n")
310
+ File.write(File.join(dir, 'hiera.yaml'), "---\nversion: 3\n")
311
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
312
+ expect(repo.hiera_config_file).to eq(File.expand_path('hiera.yaml', dir))
313
+ expect(repo.hiera_config['version']).to eq(3)
314
+ end
315
+ end
316
+
317
+ it "returns nil from #hiera_config and prints a warning when no hiera.yaml exists anywhere" do
318
+ repo = PuppetlabsOnceover::Controlrepo.new(path: minimal_path)
319
+ expect { expect(repo.hiera_config).to be_nil }.to output(/WARNING: Could not find hiera config file/).to_stdout
320
+ end
321
+
322
+ it "#hiera_config= writes the given data back to hiera_config_file" do
323
+ Dir.mktmpdir do |dir|
324
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
325
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules\n")
326
+ File.write(File.join(dir, 'spec', 'hiera.yaml'), "---\nversion: 5\n")
327
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
328
+ repo.hiera_config = { 'version' => 5, 'defaults' => { 'datadir' => 'newdata' } }
329
+ expect(YAML.load_file(repo.hiera_config_file)['defaults']['datadir']).to eq('newdata')
330
+ end
331
+ end
332
+ end
333
+
334
+ describe "#hiera_data" do
335
+ it "finds a single hiera-ish data directory" do
336
+ Dir.mktmpdir do |dir|
337
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules\n")
338
+ FileUtils.mkdir_p(File.join(dir, 'hieradata'))
339
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
340
+ expect(repo.hiera_data).to eq(File.expand_path('hieradata', dir))
341
+ end
342
+ end
343
+
344
+ it "raises when there is more than one candidate hiera data directory" do
345
+ Dir.mktmpdir do |dir|
346
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules\n")
347
+ FileUtils.mkdir_p(File.join(dir, 'hieradata'))
348
+ FileUtils.mkdir_p(File.join(dir, 'hiera_data_2'))
349
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
350
+ expect { repo.hiera_data }.to raise_error(RuntimeError, /too many directories/)
351
+ end
352
+ end
353
+ end
354
+
355
+ describe "#r10k_config_file / #r10k_config / #r10k_config=" do
356
+ it "prefers r10k.yaml in spec_dir over root" do
357
+ Dir.mktmpdir do |dir|
358
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
359
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules\n")
360
+ File.write(File.join(dir, 'spec', 'r10k.yaml'), "---\ncachedir: /tmp/spec-cache\n")
361
+ File.write(File.join(dir, 'r10k.yaml'), "---\ncachedir: /tmp/root-cache\n")
362
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
363
+ expect(repo.r10k_config_file).to eq(File.expand_path('spec/r10k.yaml', dir))
364
+ expect(repo.r10k_config['cachedir']).to eq('/tmp/spec-cache')
365
+ end
366
+ end
367
+
368
+ it "falls back to r10k.yaml at root" do
369
+ Dir.mktmpdir do |dir|
370
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
371
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules\n")
372
+ File.write(File.join(dir, 'r10k.yaml'), "---\ncachedir: /tmp/root-cache\n")
373
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
374
+ expect(repo.r10k_config_file).to eq(File.expand_path('r10k.yaml', dir))
375
+ end
376
+ end
377
+
378
+ it "returns nil when neither exists" do
379
+ repo = PuppetlabsOnceover::Controlrepo.new(path: minimal_path)
380
+ expect(repo.r10k_config_file).to be_nil
381
+ end
382
+
383
+ it "#r10k_config= writes data back out" do
384
+ Dir.mktmpdir do |dir|
385
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
386
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules\n")
387
+ File.write(File.join(dir, 'spec', 'r10k.yaml'), "---\ncachedir: /tmp/a\n")
388
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
389
+ repo.r10k_config = { 'cachedir' => '/tmp/b' }
390
+ expect(YAML.load_file(repo.r10k_config_file)['cachedir']).to eq('/tmp/b')
391
+ end
392
+ end
393
+ end
394
+
395
+ describe "#temp_manifest" do
396
+ it "returns whatever @manifest currently is" do
397
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path, manifest: 'site/role/manifests/example.pp')
398
+ expect(repo.temp_manifest).to eq(repo.manifest)
399
+ end
400
+ end
401
+
402
+ describe "#print_puppetfile_table" do
403
+ it "prints a table for both forge and git modules, out-of-date and up-to-date" do
404
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
405
+
406
+ puppetfile = R10K::Puppetfile.new(basic_path)
407
+ puppetfile.load!
408
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
409
+ puppetfile.modules.find { |m| m.is_a?(R10K::Module::Git) }
410
+
411
+ current_release = double('release', version: '9.9.9')
412
+ v3_module = double('v3_module', current_release: current_release, endorsement: 'supported', superseded_by: nil)
413
+ allow(forge_mod).to receive(:v3_module).and_return(v3_module)
414
+
415
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
416
+ allow(puppetfile).to receive(:load!)
417
+
418
+ expect { repo.print_puppetfile_table }.to output(/Full Name/).to_stdout
419
+ end
420
+
421
+ it "captures a RuntimeError raised while assessing a module and reports it in the error table" do
422
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
423
+
424
+ puppetfile = R10K::Puppetfile.new(basic_path)
425
+ puppetfile.load!
426
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
427
+
428
+ call_count = 0
429
+ allow(forge_mod).to receive(:v3_module) do
430
+ call_count += 1
431
+ raise 'boom' if call_count == 1
432
+
433
+ double('v3_module', current_release: double('release', version: '4.11.0'))
434
+ end
435
+ allow(forge_mod).to receive(:expected_version).and_return('4.11.0')
436
+
437
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
438
+ allow(puppetfile).to receive(:load!)
439
+ allow(puppetfile).to receive(:modules).and_return([forge_mod])
440
+
441
+ expect { repo.print_puppetfile_table }.to output(/Issue assessing module/).to_stdout
442
+ end
443
+
444
+ it "marks major/minor/tiny/patchlevel version differences appropriately" do
445
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
446
+ puppetfile = R10K::Puppetfile.new(basic_path)
447
+ puppetfile.load!
448
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
449
+ allow(forge_mod).to receive(:expected_version).and_return('1.0.0')
450
+ current_release = double('release', version: '2.0.0')
451
+ v3_module = double('v3_module', current_release: current_release, endorsement: 'supported', superseded_by: { slug: 'newmod' })
452
+ allow(forge_mod).to receive(:v3_module).and_return(v3_module)
453
+
454
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
455
+ allow(puppetfile).to receive(:load!)
456
+ allow(puppetfile).to receive(:modules).and_return([forge_mod])
457
+
458
+ expect { repo.print_puppetfile_table }.to output(/Major/).to_stdout
459
+ end
460
+
461
+ it "marks a minor version difference" do
462
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
463
+ puppetfile = R10K::Puppetfile.new(basic_path)
464
+ puppetfile.load!
465
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
466
+ allow(forge_mod).to receive(:expected_version).and_return('1.0.0')
467
+ v3_module = double('v3_module', current_release: double('release', version: '1.1.0'), endorsement: 'supported', superseded_by: nil)
468
+ allow(forge_mod).to receive(:v3_module).and_return(v3_module)
469
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
470
+ allow(puppetfile).to receive(:load!)
471
+ allow(puppetfile).to receive(:modules).and_return([forge_mod])
472
+ expect { repo.print_puppetfile_table }.to output(/Minor/).to_stdout
473
+ end
474
+
475
+ it "marks a tiny version difference" do
476
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
477
+ puppetfile = R10K::Puppetfile.new(basic_path)
478
+ puppetfile.load!
479
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
480
+ allow(forge_mod).to receive(:expected_version).and_return('1.0.0')
481
+ v3_module = double('v3_module', current_release: double('release', version: '1.0.1'), endorsement: 'supported', superseded_by: nil)
482
+ allow(forge_mod).to receive(:v3_module).and_return(v3_module)
483
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
484
+ allow(puppetfile).to receive(:load!)
485
+ allow(puppetfile).to receive(:modules).and_return([forge_mod])
486
+ expect { repo.print_puppetfile_table }.to output(/Tiny/).to_stdout
487
+ end
488
+
489
+ it "marks a patchlevel version difference" do
490
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
491
+ puppetfile = R10K::Puppetfile.new(basic_path)
492
+ puppetfile.load!
493
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
494
+ allow(forge_mod).to receive(:expected_version).and_return('1.0.0-0')
495
+ v3_module = double('v3_module', current_release: double('release', version: '1.0.0-1'), endorsement: 'supported', superseded_by: nil)
496
+ allow(forge_mod).to receive(:v3_module).and_return(v3_module)
497
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
498
+ allow(puppetfile).to receive(:load!)
499
+ allow(puppetfile).to receive(:modules).and_return([forge_mod])
500
+ expect { repo.print_puppetfile_table }.to output(/PatchLevel/).to_stdout
501
+ end
502
+
503
+ it "marks a patchlevel_minor version difference" do
504
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
505
+ puppetfile = R10K::Puppetfile.new(basic_path)
506
+ puppetfile.load!
507
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
508
+ allow(forge_mod).to receive(:expected_version).and_return('1.0.0-0-0')
509
+ v3_module = double('v3_module', current_release: double('release', version: '1.0.0-0-1'), endorsement: 'supported', superseded_by: nil)
510
+ allow(forge_mod).to receive(:v3_module).and_return(v3_module)
511
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
512
+ allow(puppetfile).to receive(:load!)
513
+ allow(puppetfile).to receive(:modules).and_return([forge_mod])
514
+ expect { repo.print_puppetfile_table }.to output(/PatchLevel_minor/).to_stdout
515
+ end
516
+
517
+ it "marks 'No' when versions are identical" do
518
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
519
+ puppetfile = R10K::Puppetfile.new(basic_path)
520
+ puppetfile.load!
521
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
522
+ allow(forge_mod).to receive(:expected_version).and_return('1.0.0')
523
+ v3_module = double('v3_module', current_release: double('release', version: '1.0.0'), endorsement: 'supported', superseded_by: nil)
524
+ allow(forge_mod).to receive(:v3_module).and_return(v3_module)
525
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
526
+ allow(puppetfile).to receive(:load!)
527
+ allow(puppetfile).to receive(:modules).and_return([forge_mod])
528
+ expect { repo.print_puppetfile_table }.to output(/No/).to_stdout
529
+ end
530
+ end
531
+
532
+ describe "#update_puppetfile" do
533
+ it "rewrites the expected version of forge modules in place" do
534
+ Dir.mktmpdir do |dir|
535
+ FileUtils.cp_r(Dir.glob(File.join(basic_path, '*')), dir)
536
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
537
+
538
+ puppetfile = R10K::Puppetfile.new(dir)
539
+ puppetfile.load!
540
+ forge_mod = puppetfile.modules.find { |m| m.is_a?(R10K::Module::Forge) }
541
+ allow(forge_mod).to receive(:owner).and_return('puppetlabs')
542
+ allow(forge_mod).to receive(:name).and_return('stdlib')
543
+ allow(forge_mod).to receive(:expected_version).and_return('4.11.0')
544
+ v3_module = double('v3_module', current_release: double('release', version: '9.9.9'))
545
+ allow(forge_mod).to receive(:v3_module).and_return(v3_module)
546
+ allow(puppetfile).to receive(:modules).and_return([forge_mod])
547
+
548
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
549
+ allow(puppetfile).to receive(:load!)
550
+
551
+ expect { repo.update_puppetfile }.to output(/changed/).to_stdout
552
+ expect(File.read(repo.puppetfile)).to include('9.9.9')
553
+ end
554
+ end
555
+ end
556
+
557
+ describe "#fixtures" do
558
+ it "renders a .fixtures.yml with symlinks, forge_modules and repositories" do
559
+ Dir.mktmpdir do |dir|
560
+ FileUtils.cp_r(Dir.glob(File.join(basic_path, '*')), dir)
561
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
562
+
563
+ puppetfile = R10K::Puppetfile.new(dir)
564
+ puppetfile.load!
565
+
566
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
567
+ allow(puppetfile).to receive(:load).and_return(true)
568
+
569
+ # The `code_dirs.each { |dir| Dir["#{dir}/*"] }` loop in #fixtures uses the
570
+ # modulepath entries as-is (unexpanded, unlike #classes), so it only finds
571
+ # anything when the process cwd is the controlrepo root.
572
+ result = nil
573
+ Dir.chdir(dir) { result = repo.fixtures }
574
+ expect(result).to include('forge_modules')
575
+ expect(result).to include('repositories')
576
+ expect(result).to include('symlinks')
577
+ end
578
+ end
579
+
580
+ it "treats a Hash expected_version (local path) as a symlink" do
581
+ Dir.mktmpdir do |dir|
582
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules:$basemodulepath\n")
583
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
584
+
585
+ local_mod = double('mod')
586
+ allow(local_mod).to receive(:is_a?).with(R10K::Module::Forge).and_return(true)
587
+ allow(local_mod).to receive(:is_a?).with(R10K::Module::Git).and_return(false)
588
+ allow(local_mod).to receive(:to_s).and_return('localmod')
589
+ allow(local_mod).to receive(:name).and_return('localmod')
590
+ allow(local_mod).to receive(:expected_version).and_return({ path: '/some/local/path' })
591
+
592
+ puppetfile = double('puppetfile', load: true, modules: [local_mod])
593
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
594
+
595
+ result = repo.fixtures
596
+ expect(result).to include('symlinks')
597
+ expect(result).to include('/some/local/path')
598
+ end
599
+ end
600
+
601
+ it "raises if the Puppetfile cannot be loaded" do
602
+ Dir.mktmpdir do |dir|
603
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules:$basemodulepath\n")
604
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
605
+ puppetfile = double('puppetfile', load: false)
606
+ allow(R10K::Puppetfile).to receive(:new).and_return(puppetfile)
607
+ expect { repo.fixtures }.to raise_error(RuntimeError, /Could not load Puppetfile/)
608
+ end
609
+ end
610
+ end
611
+
612
+ describe "self.evaluate_template" do
613
+ after(:each) { PuppetlabsOnceover::Controlrepo.instance_variable_set(:@root, nil) }
614
+
615
+ it "renders the gem's default template when no custom one is present" do
616
+ repo = double('repo', roles: [], facts_files: [])
617
+ result = PuppetlabsOnceover::Controlrepo.evaluate_template('controlrepo.yaml.erb', binding)
618
+ expect(result).to include('classes:')
619
+ end
620
+
621
+ it "prefers a custom template under <root>/spec/templates when present" do
622
+ Dir.mktmpdir do |dir|
623
+ FileUtils.mkdir_p(File.join(dir, 'spec', 'templates'))
624
+ File.write(File.join(dir, 'spec', 'templates', 'my_template.erb'), "custom-<%= 1 + 1 %>\n")
625
+ PuppetlabsOnceover::Controlrepo.instance_variable_set(:@root, dir)
626
+
627
+ result = nil
628
+ expect { result = PuppetlabsOnceover::Controlrepo.evaluate_template('my_template.erb', binding) }.to output(/Using Custom/).to_stdout
629
+ expect(result).to include('custom-2')
630
+ end
631
+ end
632
+ end
633
+
634
+ describe "self.create_dirs_and_log" do
635
+ it "creates all missing intermediate directories and logs each one" do
636
+ Dir.mktmpdir do |dir|
637
+ target = File.join(dir, 'a', 'b', 'c')
638
+ Dir.chdir(dir) do
639
+ expect { PuppetlabsOnceover::Controlrepo.create_dirs_and_log(target) }.to output(/created/).to_stdout
640
+ end
641
+ expect(File.directory?(target)).to eq(true)
642
+ end
643
+ end
644
+
645
+ it "does nothing when the directory already exists" do
646
+ Dir.mktmpdir do |dir|
647
+ Dir.chdir(dir) do
648
+ out = capture_stdout { PuppetlabsOnceover::Controlrepo.create_dirs_and_log(dir) }
649
+ expect(out).to eq('')
650
+ end
651
+ end
652
+ end
653
+ end
654
+
655
+ describe "self.init_write_file" do
656
+ it "creates the file (and parent dirs) when it does not exist" do
657
+ Dir.mktmpdir do |dir|
658
+ Dir.chdir(dir) do
659
+ target = File.join(dir, 'nested', 'file.txt')
660
+ expect { PuppetlabsOnceover::Controlrepo.init_write_file('hello', target) }.to output(/created/).to_stdout
661
+ expect(File.read(target)).to eq('hello')
662
+ end
663
+ end
664
+ end
665
+
666
+ it "skips writing when the file already exists" do
667
+ Dir.mktmpdir do |dir|
668
+ Dir.chdir(dir) do
669
+ target = File.join(dir, 'file.txt')
670
+ File.write(target, 'original')
671
+ expect { PuppetlabsOnceover::Controlrepo.init_write_file('new content', target) }.to output(/skipped/).to_stdout
672
+ expect(File.read(target)).to eq('original')
673
+ end
674
+ end
675
+ end
676
+ end
677
+
678
+ describe "self.generate_onceover_yaml" do
679
+ it "renders a controlrepo.yaml.erb driven by the repo's roles and facts_files" do
680
+ repo = PuppetlabsOnceover::Controlrepo.new(path: basic_path)
681
+ result = PuppetlabsOnceover::Controlrepo.generate_onceover_yaml(repo)
682
+ expect(result).to include('classes:')
683
+ expect(result).to include('nodes:')
684
+ end
685
+ end
686
+
687
+ describe "self.init" do
688
+ it "writes out the standard set of config files and updates .gitignore" do
689
+ Dir.mktmpdir do |dir|
690
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
691
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules:$basemodulepath\n")
692
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
693
+
694
+ allow(Dir).to receive(:pwd).and_call_original
695
+
696
+ Dir.chdir(dir) do
697
+ expect { PuppetlabsOnceover::Controlrepo.init(repo) }.to output(/created/).to_stdout
698
+ end
699
+
700
+ expect(File.exist?(repo.onceover_yaml)).to eq(true)
701
+ expect(File.exist?(File.join(dir, 'Rakefile'))).to eq(true)
702
+ expect(File.exist?(File.join(dir, 'Gemfile'))).to eq(true)
703
+ expect(File.read(File.join(dir, '.gitignore'))).to include('.onceover')
704
+ end
705
+ end
706
+
707
+ it "appends to an existing .gitignore rather than clobbering it" do
708
+ Dir.mktmpdir do |dir|
709
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
710
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules:$basemodulepath\n")
711
+ File.write(File.join(dir, '.gitignore'), "vendor/\n")
712
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
713
+
714
+ Dir.chdir(dir) do
715
+ PuppetlabsOnceover::Controlrepo.init(repo)
716
+ end
717
+
718
+ content = File.read(File.join(dir, '.gitignore'))
719
+ expect(content).to include('vendor/')
720
+ expect(content).to include('.onceover')
721
+ end
722
+ end
723
+
724
+ it "does not touch .gitignore again if .onceover is already listed" do
725
+ Dir.mktmpdir do |dir|
726
+ FileUtils.mkdir_p(File.join(dir, 'spec'))
727
+ File.write(File.join(dir, 'environment.conf'), "modulepath = site:modules:$basemodulepath\n")
728
+ File.write(File.join(dir, '.gitignore'), ".onceover\n")
729
+ repo = PuppetlabsOnceover::Controlrepo.new(path: dir)
730
+
731
+ Dir.chdir(dir) do
732
+ out = capture_stdout { PuppetlabsOnceover::Controlrepo.init(repo) }
733
+ expect(out).not_to match(/changed .gitignore/)
734
+ end
735
+ end
736
+ end
737
+ end
738
+
739
+ describe "self.generate_nodesets (deprecated)" do
740
+ it "comments out hosts for which no vagrant box could be found (404)" do
741
+ repo = PuppetlabsOnceover::Controlrepo.new(path: factsets_path)
742
+
743
+ response = double('response', code: '404', body: nil)
744
+ http = double('http')
745
+ allow(http).to receive(:use_ssl=)
746
+ allow(http).to receive(:get).and_return(response)
747
+ allow(Net::HTTP).to receive(:new).and_return(http)
748
+
749
+ result = PuppetlabsOnceover::Controlrepo.generate_nodesets(repo)
750
+ expect(result).to include('HOSTS')
751
+ expect(result).to include('#')
752
+ end
753
+
754
+ it "includes a real box url when the API call succeeds" do
755
+ repo = PuppetlabsOnceover::Controlrepo.new(path: factsets_path)
756
+
757
+ box_body = {
758
+ 'current_version' => {
759
+ 'providers' => [
760
+ { 'name' => 'virtualbox', 'original_url' => 'https://example.com/box.box' }
761
+ ]
762
+ }
763
+ }.to_json
764
+
765
+ response = double('response', code: '200', body: box_body)
766
+ http = double('http')
767
+ allow(http).to receive(:use_ssl=)
768
+ allow(http).to receive(:get).and_return(response)
769
+ allow(Net::HTTP).to receive(:new).and_return(http)
770
+
771
+ result = PuppetlabsOnceover::Controlrepo.generate_nodesets(repo)
772
+ expect(result).to include('box_url')
773
+ expect(result).to include('https://example.com/box.box')
774
+ end
775
+ end
776
+
777
+ describe "#spec_tests" do
778
+ it "yields deduplicated, filtered test tuples built from onceover.yaml" do
779
+ repo_path = File.expand_path('spec/fixtures/controlrepos/function_mocking')
780
+ repo = PuppetlabsOnceover::Controlrepo.new(path: repo_path, onceover_yaml: File.join(repo_path, 'spec', 'onceover.yaml'))
781
+
782
+ yielded = []
783
+ repo.spec_tests do |class_name, node_name, _fact_set, _trusted_set, _trusted_external_set, _pre_condition|
784
+ yielded << [class_name, node_name]
785
+ end
786
+
787
+ expect(yielded).not_to be_empty
788
+ expect(yielded.map(&:first)).to all(match(/role::/))
789
+ end
790
+ end
791
+ end
792
+
793
+ def capture_stdout
794
+ old = $stdout
795
+ $stdout = StringIO.new
796
+ yield
797
+ $stdout.string
798
+ ensure
799
+ $stdout = old
18
800
  end