puppetlabs-onceover 5.0.3 → 5.0.5

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +4 -0
  3. data/.rubocop_todo.yml +35 -15
  4. data/CHANGELOG.md +398 -3
  5. data/Gemfile +21 -1
  6. data/lib/puppetlabs-onceover/cli/plugins.rb +3 -3
  7. data/lib/puppetlabs-onceover/controlrepo.rb +8 -7
  8. data/lib/puppetlabs-onceover/rake_tasks.rb +2 -2
  9. data/lib/puppetlabs-onceover/vendored_modules.rb +29 -32
  10. data/lib/puppetlabs-onceover/version.rb +1 -1
  11. data/spec/fixtures/controlrepos/deploy_test/Puppetfile +3 -0
  12. data/spec/fixtures/controlrepos/deploy_test/a_dir/nested_file.txt +1 -0
  13. data/spec/fixtures/controlrepos/deploy_test/a_symlink +1 -0
  14. data/spec/fixtures/controlrepos/deploy_test/environment.conf +1 -0
  15. data/spec/fixtures/controlrepos/deploy_test/regular_file.txt +1 -0
  16. data/spec/puppetlabs-onceover/beaker_spec.rb +293 -0
  17. data/spec/puppetlabs-onceover/class_spec.rb +89 -0
  18. data/spec/puppetlabs-onceover/cli/run_spec.rb +80 -0
  19. data/spec/puppetlabs-onceover/cli/show_spec.rb +67 -0
  20. data/spec/puppetlabs-onceover/cli_spec.rb +79 -0
  21. data/spec/puppetlabs-onceover/controlrepo_spec.rb +787 -5
  22. data/spec/puppetlabs-onceover/deploy_spec.rb +246 -0
  23. data/spec/puppetlabs-onceover/group_spec.rb +121 -0
  24. data/spec/puppetlabs-onceover/logger_spec.rb +30 -0
  25. data/spec/puppetlabs-onceover/node_spec.rb +124 -0
  26. data/spec/puppetlabs-onceover/rake_tasks_spec.rb +179 -0
  27. data/spec/puppetlabs-onceover/rspec/formatters_spec.rb +361 -0
  28. data/spec/puppetlabs-onceover/runner_spec.rb +307 -0
  29. data/spec/puppetlabs-onceover/test_spec.rb +177 -0
  30. data/spec/puppetlabs-onceover/testconfig_spec.rb +451 -0
  31. data/spec/puppetlabs-onceover/vendored_modules_spec.rb +293 -0
  32. data/spec/puppetlabs-onceover/version_spec.rb +10 -0
  33. data/spec/spec_helper.rb +8 -0
  34. metadata +23 -2
@@ -0,0 +1,293 @@
1
+ require 'spec_helper'
2
+ require 'tmpdir'
3
+ require 'fileutils'
4
+ require 'multi_json'
5
+ require 'git'
6
+ require 'puppetlabs-onceover/controlrepo' # pulls in `include PuppetlabsOnceover::Logger` at top-level
7
+ require 'puppetlabs-onceover/vendored_modules'
8
+
9
+ describe PuppetlabsOnceover::VendoredModules do
10
+ let(:tempdir) { Dir.mktmpdir }
11
+ let(:spec_dir) { Dir.mktmpdir }
12
+ let(:repo_double) { double('repo', tempdir: tempdir, spec_dir: spec_dir) }
13
+ let(:puppet_version) { Puppet.version }
14
+ let(:core_modules) { described_class::CORE_MODULES }
15
+
16
+ def populate_cache(cachedir, mods: core_modules, version: puppet_version, tag: 'v1.2.3')
17
+ FileUtils.mkdir_p(cachedir)
18
+ mods.each do |mod|
19
+ File.write(File.join(cachedir, "#{mod}-puppet_agent-#{version}.json"), MultiJson.dump([{ 'name' => tag }]))
20
+ end
21
+ end
22
+
23
+ after do
24
+ FileUtils.rm_rf(tempdir)
25
+ FileUtils.rm_rf(spec_dir)
26
+ end
27
+
28
+ describe '#initialize' do
29
+ context 'when the cache is already populated (cache-hit branch, no network access)' do
30
+ before { populate_cache(File.join(tempdir, 'vendored_modules')) }
31
+
32
+ subject(:vm) { described_class.new(repo: repo_double) }
33
+
34
+ it 'resolves a vendored reference for every core module' do
35
+ expect(vm.vendored_references.size).to eq(core_modules.size)
36
+ end
37
+
38
+ it 'builds the url and ref from the cached tag' do
39
+ ref = vm.vendored_references.first
40
+ expect(ref['url']).to eq("https://github.com/puppetlabs/puppetlabs-#{core_modules.first}.git")
41
+ expect(ref['ref']).to eq('refs/tags/v1.2.3')
42
+ end
43
+
44
+ it 'starts with an empty missing_vendored list' do
45
+ expect(vm.missing_vendored).to eq([])
46
+ end
47
+ end
48
+
49
+ context 'when the cachedir does not exist yet' do
50
+ it 'creates it via FileUtils.mkdir_p' do
51
+ cachedir = File.join(tempdir, 'vendored_modules')
52
+ expect(File.directory?(cachedir)).to be false
53
+ allow_any_instance_of(described_class).to receive(:remote_tags).and_return([{ 'name' => 'v9.9.9' }])
54
+ described_class.new(repo: repo_double)
55
+ expect(File.directory?(cachedir)).to be true
56
+ end
57
+ end
58
+
59
+ context 'when opts[:cachedir] is explicitly provided' do
60
+ it 'uses it instead of the repo tempdir default' do
61
+ custom_cachedir = Dir.mktmpdir
62
+ populate_cache(custom_cachedir)
63
+ vm = described_class.new(repo: repo_double, cachedir: custom_cachedir)
64
+ expect(vm.vendored_references.size).to eq(core_modules.size)
65
+ FileUtils.remove_entry(custom_cachedir)
66
+ end
67
+ end
68
+
69
+ context 'when opts[:force_update] is true' do
70
+ it 'always calls remote_tags, ignoring any existing cache' do
71
+ populate_cache(File.join(tempdir, 'vendored_modules'))
72
+ expect_any_instance_of(described_class).to receive(:remote_tags).at_least(:once).and_return([{ 'name' => 'v5.0.0' }])
73
+ described_class.new(repo: repo_double, force_update: true)
74
+ end
75
+ end
76
+
77
+ context 'when running against Puppet < 6' do
78
+ it 'raises' do
79
+ allow(Puppet).to receive(:version).and_return('5.5.10')
80
+ populate_cache(File.join(tempdir, 'vendored_modules'), version: '5.5.10')
81
+ expect { described_class.new(repo: repo_double) }.to raise_error(/only applies to puppet versions >= 6/)
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#component_cache' do
87
+ subject(:vm) do
88
+ populate_cache(File.join(tempdir, 'vendored_modules'))
89
+ described_class.new(repo: repo_double)
90
+ end
91
+
92
+ let(:component) { core_modules.first } # augeas_core
93
+ let(:desired_name) { "#{component}-puppet_agent-#{puppet_version}.json" }
94
+
95
+ def touch_files(dir, names)
96
+ FileUtils.mkdir_p(dir)
97
+ names.each { |n| FileUtils.touch(File.join(dir, n)) }
98
+ end
99
+
100
+ it 'falls back to the default cachedir file when there is no manual_vendored_dir' do
101
+ vm.instance_variable_set(:@manual_vendored_dir, File.join(tempdir, 'does-not-exist'))
102
+ result = vm.component_cache(component)
103
+ expect(result).to eq(File.join(vm.instance_variable_get(:@cachedir), desired_name))
104
+ end
105
+
106
+ it 'falls back to the default cachedir file when manual dir has zero matches' do
107
+ manual_dir = Dir.mktmpdir
108
+ touch_files(manual_dir, ['some_other_component-puppet_agent-1.0.0.json'])
109
+ vm.instance_variable_set(:@manual_vendored_dir, manual_dir)
110
+ result = vm.component_cache(component)
111
+ expect(result).to eq(File.join(vm.instance_variable_get(:@cachedir), desired_name))
112
+ FileUtils.remove_entry(manual_dir)
113
+ end
114
+
115
+ it 'uses the single manual cache file when exactly one match is found' do
116
+ manual_dir = Dir.mktmpdir
117
+ touch_files(manual_dir, ["#{component}-puppet_agent-9.0.0.json"])
118
+ vm.instance_variable_set(:@manual_vendored_dir, manual_dir)
119
+ result = vm.component_cache(component)
120
+ expect(result).to eq(File.join(manual_dir, "#{component}-puppet_agent-9.0.0.json"))
121
+ FileUtils.remove_entry(manual_dir)
122
+ end
123
+
124
+ it 'uses the exact version match when multiple manual caches exist and one matches exactly' do
125
+ manual_dir = Dir.mktmpdir
126
+ touch_files(manual_dir, [desired_name, "#{component}-puppet_agent-1.0.0.json"])
127
+ vm.instance_variable_set(:@manual_vendored_dir, manual_dir)
128
+ result = vm.component_cache(component)
129
+ expect(result).to eq(File.join(manual_dir, desired_name))
130
+ FileUtils.remove_entry(manual_dir)
131
+ end
132
+
133
+ it 'uses the latest major-version match when no exact match exists' do
134
+ major = vm.instance_variable_get(:@puppet_major_version).to_s
135
+ manual_dir = Dir.mktmpdir
136
+ touch_files(manual_dir, ["#{component}-puppet_agent-#{major}.1.0.json", "#{component}-puppet_agent-1.0.0.json"])
137
+ vm.instance_variable_set(:@manual_vendored_dir, manual_dir)
138
+ result = vm.component_cache(component)
139
+ expect(result).to eq(File.join(manual_dir, "#{component}-puppet_agent-#{major}.1.0.json"))
140
+ FileUtils.remove_entry(manual_dir)
141
+ end
142
+
143
+ it 'falls back to the latest supplied version when neither exact nor major-version matches exist' do
144
+ # Fixed, deliberately-unrealistic majors (the CI matrix runs this same
145
+ # spec against both puppet 8.x and 9.x, so hardcoding "9.0.0" would
146
+ # accidentally match the major-version-match branch instead of this
147
+ # fallback branch when the suite runs under puppet 9). The production
148
+ # major-version check is a plain substring match
149
+ # (`s["...puppet_agent-#{major}"]`), so "9#{major}.0.0"-style values
150
+ # are NOT safe either -- "99.0.0" still contains "...puppet_agent-9".
151
+ # 500/501 can't collide with any realistic puppet major version.
152
+ lower = "500.0.0"
153
+ higher = "501.0.0"
154
+ manual_dir = Dir.mktmpdir
155
+ touch_files(manual_dir, ["#{component}-puppet_agent-#{lower}.json", "#{component}-puppet_agent-#{higher}.json"])
156
+ vm.instance_variable_set(:@manual_vendored_dir, manual_dir)
157
+ result = vm.component_cache(component)
158
+ expect(result).to eq(File.join(manual_dir, "#{component}-puppet_agent-#{higher}.json"))
159
+ FileUtils.remove_entry(manual_dir)
160
+ end
161
+
162
+ it 'skips the manual dir entirely when force_update is true' do
163
+ manual_dir = Dir.mktmpdir
164
+ touch_files(manual_dir, ["#{component}-puppet_agent-9.0.0.json"])
165
+ vm.instance_variable_set(:@manual_vendored_dir, manual_dir)
166
+ vm.instance_variable_set(:@force_update, true)
167
+ result = vm.component_cache(component)
168
+ expect(result).to eq(File.join(vm.instance_variable_get(:@cachedir), desired_name))
169
+ FileUtils.remove_entry(manual_dir)
170
+ end
171
+ end
172
+
173
+ describe '#version_from_file' do
174
+ subject(:vm) do
175
+ populate_cache(File.join(tempdir, 'vendored_modules'))
176
+ described_class.new(repo: repo_double)
177
+ end
178
+
179
+ it 'parses the embedded puppet_agent version out of the filename' do
180
+ expect(vm.version_from_file('/some/path/augeas_core-puppet_agent-8.13.0.json')).to eq(Gem::Version.new('8.13.0'))
181
+ end
182
+ end
183
+
184
+ describe '#puppetfile_missing_vendored' do
185
+ subject(:vm) do
186
+ populate_cache(File.join(tempdir, 'vendored_modules'))
187
+ described_class.new(repo: repo_double)
188
+ end
189
+
190
+ it 'records core modules that are absent from the Puppetfile' do
191
+ existing_module = double('r10k_module', name: core_modules.first)
192
+ puppetfile = double('puppetfile')
193
+ allow(puppetfile).to receive(:load)
194
+ allow(puppetfile).to receive(:modules).and_return([existing_module])
195
+
196
+ vm.puppetfile_missing_vendored(puppetfile)
197
+
198
+ expect(vm.missing_vendored.size).to eq(core_modules.size - 1)
199
+ missing_names = vm.missing_vendored.map { |h| h.keys.first }
200
+ expect(missing_names).not_to include("puppetlabs-#{core_modules.first}")
201
+ expect(missing_names).to include("puppetlabs-#{core_modules[1]}")
202
+ end
203
+
204
+ it 'rewrites ssh urls to https for the missing entries' do
205
+ puppetfile = double('puppetfile')
206
+ allow(puppetfile).to receive(:load)
207
+ allow(puppetfile).to receive(:modules).and_return([])
208
+
209
+ vm.puppetfile_missing_vendored(puppetfile)
210
+
211
+ entry = vm.missing_vendored.first.values.first
212
+ expect(entry[:git]).to start_with('https://github.com/')
213
+ expect(entry[:git]).not_to include('git@github.com:')
214
+ end
215
+ end
216
+
217
+ describe '#query_or_cache' do
218
+ subject(:vm) do
219
+ populate_cache(File.join(tempdir, 'vendored_modules'))
220
+ described_class.new(repo: repo_double)
221
+ end
222
+
223
+ it 'reads from the cache file when it already exists' do
224
+ cache_file = File.join(Dir.mktmpdir, 'cached.json')
225
+ File.write(cache_file, MultiJson.dump([{ 'name' => 'v1.0.0' }]))
226
+ expect(vm).not_to receive(:remote_tags)
227
+ result = vm.query_or_cache('https://github.com/puppetlabs/x.git', cache_file)
228
+ expect(result).to eq([{ 'name' => 'v1.0.0' }])
229
+ end
230
+
231
+ it 'calls remote_tags and writes the cache when the file does not exist' do
232
+ cache_file = File.join(Dir.mktmpdir, 'missing.json')
233
+ allow(vm).to receive(:remote_tags).and_return([{ 'name' => 'v2.0.0' }])
234
+ result = vm.query_or_cache('https://github.com/puppetlabs/x.git', cache_file)
235
+ expect(result).to eq([{ 'name' => 'v2.0.0' }])
236
+ expect(File.exist?(cache_file)).to be true
237
+ expect(MultiJson.load(File.read(cache_file))).to eq([{ 'name' => 'v2.0.0' }])
238
+ end
239
+ end
240
+
241
+ describe '#remote_tags' do
242
+ subject(:vm) do
243
+ populate_cache(File.join(tempdir, 'vendored_modules'))
244
+ described_class.new(repo: repo_double)
245
+ end
246
+
247
+ it 'returns tags sorted newest version first' do
248
+ allow(Git).to receive(:ls_remote).and_return(
249
+ 'tags' => {
250
+ 'v1.0.0' => { ref: 'refs/tags/v1.0.0', sha: 'a' },
251
+ 'v2.0.1' => { ref: 'refs/tags/v2.0.1', sha: 'b' },
252
+ 'v2.0.1^{}' => { ref: 'refs/tags/v2.0.1^{}', sha: 'c' },
253
+ '1.5.0' => { ref: 'refs/tags/1.5.0', sha: 'd' }
254
+ }
255
+ )
256
+
257
+ result = vm.remote_tags('https://github.com/puppetlabs/puppetlabs-augeas_core.git')
258
+ expect(result).to eq([{ 'name' => 'v2.0.1' }, { 'name' => '1.5.0' }, { 'name' => 'v1.0.0' }])
259
+ end
260
+
261
+ it 'skips non-version-shaped tags' do
262
+ allow(Git).to receive(:ls_remote).and_return(
263
+ 'tags' => {
264
+ 'v1.0.0' => { ref: 'refs/tags/v1.0.0', sha: 'a' },
265
+ 'not-a-version' => { ref: 'refs/tags/not-a-version', sha: 'b' }
266
+ }
267
+ )
268
+
269
+ result = vm.remote_tags('https://github.com/puppetlabs/puppetlabs-augeas_core.git')
270
+ expect(result).to eq([{ 'name' => 'v1.0.0' }])
271
+ end
272
+
273
+ it 'returns an empty array when the remote has no tags' do
274
+ allow(Git).to receive(:ls_remote).and_return({})
275
+
276
+ result = vm.remote_tags('https://github.com/puppetlabs/puppetlabs-augeas_core.git')
277
+ expect(result).to eq([])
278
+ end
279
+ end
280
+
281
+ describe '#read_json_dump and #write_json_dump' do
282
+ subject(:vm) do
283
+ populate_cache(File.join(tempdir, 'vendored_modules'))
284
+ described_class.new(repo: repo_double)
285
+ end
286
+
287
+ it 'round-trips json data through a file' do
288
+ file = File.join(Dir.mktmpdir, 'roundtrip.json')
289
+ vm.write_json_dump(file, [{ 'name' => 'v1.0.0' }])
290
+ expect(vm.read_json_dump(file)).to eq([{ 'name' => 'v1.0.0' }])
291
+ end
292
+ end
293
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+ require 'puppetlabs-onceover/version'
3
+
4
+ describe PuppetlabsOnceover do
5
+ it 'defines a VERSION constant' do
6
+ # Not a literal version string -- release-prep bumps this file on every
7
+ # release, which would otherwise break this test every single time.
8
+ expect(PuppetlabsOnceover::VERSION).to match(/\A\d+\.\d+\.\d+\z/)
9
+ end
10
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,11 @@
1
+ if ENV['COVERAGE']
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ skip '/spec/'
5
+ skip '/vendor/'
6
+ end
7
+ end
8
+
1
9
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
2
10
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
11
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: puppetlabs-onceover
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.3
4
+ version: 5.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-09-03 00:00:00.000000000 Z
11
+ date: 2026-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: backticks
@@ -381,6 +381,11 @@ files:
381
381
  - spec/fixtures/controlrepos/control_branch/environment.conf
382
382
  - spec/fixtures/controlrepos/custom_puppetfile/Puppetfile.custom
383
383
  - spec/fixtures/controlrepos/custom_puppetfile/environment.conf
384
+ - spec/fixtures/controlrepos/deploy_test/Puppetfile
385
+ - spec/fixtures/controlrepos/deploy_test/a_dir/nested_file.txt
386
+ - spec/fixtures/controlrepos/deploy_test/a_symlink
387
+ - spec/fixtures/controlrepos/deploy_test/environment.conf
388
+ - spec/fixtures/controlrepos/deploy_test/regular_file.txt
384
389
  - spec/fixtures/controlrepos/factsets/environment.conf
385
390
  - spec/fixtures/controlrepos/factsets/site/profile/manifests/base.pp
386
391
  - spec/fixtures/controlrepos/factsets/site/role/manifests/example.pp
@@ -561,7 +566,23 @@ files:
561
566
  - spec/fixtures/controlrepos/windows/site/role/manifests/groups.pp
562
567
  - spec/fixtures/controlrepos/windows/site/role/manifests/users.pp
563
568
  - spec/fixtures/controlrepos/windows/spec/onceover.yaml
569
+ - spec/puppetlabs-onceover/beaker_spec.rb
570
+ - spec/puppetlabs-onceover/class_spec.rb
571
+ - spec/puppetlabs-onceover/cli/run_spec.rb
572
+ - spec/puppetlabs-onceover/cli/show_spec.rb
573
+ - spec/puppetlabs-onceover/cli_spec.rb
564
574
  - spec/puppetlabs-onceover/controlrepo_spec.rb
575
+ - spec/puppetlabs-onceover/deploy_spec.rb
576
+ - spec/puppetlabs-onceover/group_spec.rb
577
+ - spec/puppetlabs-onceover/logger_spec.rb
578
+ - spec/puppetlabs-onceover/node_spec.rb
579
+ - spec/puppetlabs-onceover/rake_tasks_spec.rb
580
+ - spec/puppetlabs-onceover/rspec/formatters_spec.rb
581
+ - spec/puppetlabs-onceover/runner_spec.rb
582
+ - spec/puppetlabs-onceover/test_spec.rb
583
+ - spec/puppetlabs-onceover/testconfig_spec.rb
584
+ - spec/puppetlabs-onceover/vendored_modules_spec.rb
585
+ - spec/puppetlabs-onceover/version_spec.rb
565
586
  - spec/spec_helper.rb
566
587
  - templates/.fixtures.yml.erb
567
588
  - templates/Gemfile.erb