r10k 3.9.1 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec_tests.yml +1 -1
  3. data/.travis.yml +0 -10
  4. data/CHANGELOG.mkd +28 -0
  5. data/README.mkd +6 -0
  6. data/doc/dynamic-environments/configuration.mkd +21 -0
  7. data/doc/puppetfile.mkd +15 -1
  8. data/integration/Rakefile +3 -1
  9. data/integration/tests/user_scenario/basic_workflow/negative/neg_specify_deleted_forge_module.rb +3 -9
  10. data/integration/tests/user_scenario/basic_workflow/single_env_purge_unmanaged_modules.rb +21 -25
  11. data/integration/tests/user_scenario/complex_workflow/multi_env_add_change_remove.rb +3 -3
  12. data/integration/tests/user_scenario/complex_workflow/multi_env_remove_re-add.rb +3 -3
  13. data/integration/tests/user_scenario/complex_workflow/multi_env_unamanaged.rb +3 -3
  14. data/lib/r10k/action/base.rb +6 -3
  15. data/lib/r10k/action/deploy/display.rb +6 -3
  16. data/lib/r10k/action/deploy/environment.rb +15 -4
  17. data/lib/r10k/action/deploy/module.rb +37 -8
  18. data/lib/r10k/action/runner.rb +45 -10
  19. data/lib/r10k/cli/deploy.rb +4 -0
  20. data/lib/r10k/git.rb +3 -0
  21. data/lib/r10k/git/cache.rb +1 -1
  22. data/lib/r10k/git/rugged/credentials.rb +77 -0
  23. data/lib/r10k/git/stateful_repository.rb +1 -0
  24. data/lib/r10k/initializers.rb +10 -0
  25. data/lib/r10k/module/base.rb +37 -0
  26. data/lib/r10k/module/forge.rb +7 -2
  27. data/lib/r10k/module/git.rb +2 -1
  28. data/lib/r10k/module/svn.rb +2 -1
  29. data/lib/r10k/module_loader/puppetfile.rb +206 -0
  30. data/lib/r10k/module_loader/puppetfile/dsl.rb +37 -0
  31. data/lib/r10k/puppetfile.rb +83 -160
  32. data/lib/r10k/settings.rb +47 -2
  33. data/lib/r10k/settings/definition.rb +1 -1
  34. data/lib/r10k/source/base.rb +10 -0
  35. data/lib/r10k/source/git.rb +5 -0
  36. data/lib/r10k/source/svn.rb +4 -0
  37. data/lib/r10k/util/purgeable.rb +70 -8
  38. data/lib/r10k/version.rb +1 -1
  39. data/locales/r10k.pot +129 -57
  40. data/r10k.gemspec +2 -0
  41. data/spec/fixtures/unit/action/r10k_forge_auth.yaml +4 -0
  42. data/spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml +3 -0
  43. data/spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_subdir_2/ignored_1 +0 -0
  44. data/spec/fixtures/unit/util/purgeable/managed_two/.hidden/unmanaged_3 +0 -0
  45. data/spec/unit/action/deploy/environment_spec.rb +25 -0
  46. data/spec/unit/action/deploy/module_spec.rb +216 -14
  47. data/spec/unit/action/runner_spec.rb +129 -25
  48. data/spec/unit/git/cache_spec.rb +14 -0
  49. data/spec/unit/git/rugged/credentials_spec.rb +29 -0
  50. data/spec/unit/git/stateful_repository_spec.rb +5 -0
  51. data/spec/unit/module/base_spec.rb +46 -0
  52. data/spec/unit/module/forge_spec.rb +27 -1
  53. data/spec/unit/module/git_spec.rb +17 -8
  54. data/spec/unit/module/svn_spec.rb +18 -0
  55. data/spec/unit/module_loader/puppetfile_spec.rb +343 -0
  56. data/spec/unit/module_spec.rb +28 -0
  57. data/spec/unit/puppetfile_spec.rb +127 -191
  58. data/spec/unit/settings_spec.rb +24 -2
  59. data/spec/unit/util/purgeable_spec.rb +38 -6
  60. metadata +23 -2
@@ -76,4 +76,32 @@ describe R10K::Module do
76
76
  R10K::Module.new('bar!quux', '/modulepath', {version: ["NOPE NOPE NOPE NOPE!"]})
77
77
  }.to raise_error RuntimeError, /doesn't have an implementation/
78
78
  end
79
+
80
+ describe 'Given a set of initialization parameters for R10K::Module' do
81
+ [ ['name', {git: 'git url'}],
82
+ ['name', {type: 'git', source: 'git url'}],
83
+ ['name', {svn: 'svn url'}],
84
+ ['name', {type: 'svn', source: 'svn url'}],
85
+ ['namespace-name', {version: '8.0.0'}],
86
+ ['namespace-name', {type: 'forge', version: '8.0.0'}]
87
+ ].each do |(name, options)|
88
+ it 'can handle the default_branch_override option' do
89
+ expect {
90
+ obj = R10K::Module.new(name, '/modulepath', options.merge({default_branch_override: 'foo'}))
91
+ expect(obj).to be_a_kind_of(R10K::Module::Base)
92
+ }.not_to raise_error
93
+ end
94
+ describe 'the exclude_spec setting' do
95
+ it 'sets the exclude_spec instance variable' do
96
+ obj = R10K::Module.new(name, '/modulepath', options.merge({exclude_spec: true}))
97
+ expect(obj.instance_variable_get("@exclude_spec")).to eq(true)
98
+ end
99
+ it 'can be overridden by the overrides map' do
100
+ options = options.merge({exclude_spec: false, overrides: {modules: {exclude_spec: true}}})
101
+ obj = R10K::Module.new(name, '/modulepath', options)
102
+ expect(obj.instance_variable_get("@exclude_spec")).to eq(true)
103
+ end
104
+ end
105
+ end
106
+ end
79
107
  end
@@ -10,7 +10,7 @@ describe R10K::Puppetfile do
10
10
  )
11
11
  end
12
12
 
13
- describe "a custom puppetfile Puppetfile.r10k" do
13
+ describe "a custom puppetfile_name" do
14
14
  it "is the basedir joined with '/Puppetfile.r10k' path" do
15
15
  expect(subject.puppetfile_path).to eq '/some/nonexistent/basedir/Puppetfile.r10k'
16
16
  end
@@ -18,6 +18,25 @@ describe R10K::Puppetfile do
18
18
 
19
19
  end
20
20
 
21
+ describe R10K::Puppetfile do
22
+
23
+ describe "a custom relative puppetfile_path" do
24
+ it "is the basedir joined with the puppetfile_path" do
25
+ relative_subject = described_class.new('/some/nonexistent/basedir',
26
+ {puppetfile_path: 'relative/Puppetfile'})
27
+ expect(relative_subject.puppetfile_path).to eq '/some/nonexistent/basedir/relative/Puppetfile'
28
+ end
29
+ end
30
+
31
+ describe "a custom absolute puppetfile_path" do
32
+ it "is the puppetfile_path as given" do
33
+ absolute_subject = described_class.new('/some/nonexistent/basedir',
34
+ {puppetfile_path: '/some/absolute/custom/Puppetfile'})
35
+ expect(absolute_subject.puppetfile_path).to eq '/some/absolute/custom/Puppetfile'
36
+ end
37
+ end
38
+ end
39
+
21
40
  describe R10K::Puppetfile do
22
41
 
23
42
  subject do
@@ -67,230 +86,147 @@ describe R10K::Puppetfile do
67
86
  end
68
87
  end
69
88
 
70
- describe "adding modules" do
71
- it "should transform Forge modules with a string arg to have a version key" do
72
- allow(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, hash_including(version: '1.2.3'), anything).and_call_original
73
-
74
- expect { subject.add_module('puppet/test_module', '1.2.3') }.to change { subject.modules }
75
- expect(subject.modules.collect(&:name)).to include('test_module')
76
- end
77
-
78
- it "should not accept Forge modules with a version comparison" do
79
- allow(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, hash_including(version: '< 1.2.0'), anything).and_call_original
80
-
81
- expect {
82
- subject.add_module('puppet/test_module', '< 1.2.0')
83
- }.to raise_error(RuntimeError, /module puppet\/test_module.*doesn't have an implementation/i)
84
-
85
- expect(subject.modules.collect(&:name)).not_to include('test_module')
86
- end
89
+ describe "loading a Puppetfile" do
90
+ context 'using load' do
91
+ it "returns the loaded content" do
92
+ path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-with-version')
93
+ subject = described_class.new(path, {})
87
94
 
88
- it "should accept non-Forge modules with a hash arg" do
89
- module_opts = { git: 'git@example.com:puppet/test_module.git' }
95
+ loaded_content = subject.load
96
+ expect(loaded_content).to be_an_instance_of(Hash)
90
97
 
91
- allow(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, module_opts, anything).and_call_original
92
-
93
- expect { subject.add_module('puppet/test_module', module_opts) }.to change { subject.modules }
94
- expect(subject.modules.collect(&:name)).to include('test_module')
95
- end
96
-
97
- it "should accept non-Forge modules with a valid relative :install_path option" do
98
- module_opts = {
99
- install_path: 'vendor',
100
- git: 'git@example.com:puppet/test_module.git',
101
- }
102
-
103
- allow(R10K::Module).to receive(:new).with('puppet/test_module', File.join(subject.basedir, 'vendor'), module_opts, anything).and_call_original
104
-
105
- expect { subject.add_module('puppet/test_module', module_opts) }.to change { subject.modules }
106
- expect(subject.modules.collect(&:name)).to include('test_module')
107
- end
108
-
109
- it "should accept non-Forge modules with a valid absolute :install_path option" do
110
- install_path = File.join(subject.basedir, 'vendor')
111
-
112
- module_opts = {
113
- install_path: install_path,
114
- git: 'git@example.com:puppet/test_module.git',
115
- }
116
-
117
- allow(R10K::Module).to receive(:new).with('puppet/test_module', install_path, module_opts, anything).and_call_original
118
-
119
- expect { subject.add_module('puppet/test_module', module_opts) }.to change { subject.modules }
120
- expect(subject.modules.collect(&:name)).to include('test_module')
121
- end
122
-
123
- it "should reject non-Forge modules with an invalid relative :install_path option" do
124
- module_opts = {
125
- install_path: '../../vendor',
126
- git: 'git@example.com:puppet/test_module.git',
127
- }
98
+ has_some_data = loaded_content.values.none?(&:empty?)
99
+ expect(has_some_data).to be true
100
+ end
128
101
 
129
- allow(R10K::Module).to receive(:new).with('puppet/test_module', File.join(subject.basedir, 'vendor'), module_opts, anything).and_call_original
102
+ it "handles a relative basedir" do
103
+ path = File.join('spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-with-version')
104
+ subject = described_class.new(path, {})
130
105
 
131
- expect { subject.add_module('puppet/test_module', module_opts) }.to raise_error(R10K::Error, /cannot manage content.*is not within/i).and not_change { subject.modules }
132
- end
106
+ loaded_content = subject.load
107
+ expect(loaded_content).to be_an_instance_of(Hash)
133
108
 
134
- it "should reject non-Forge modules with an invalid absolute :install_path option" do
135
- module_opts = {
136
- install_path: '/tmp/mydata/vendor',
137
- git: 'git@example.com:puppet/test_module.git',
138
- }
109
+ has_some_data = loaded_content.values.none?(&:empty?)
110
+ expect(has_some_data).to be true
111
+ end
139
112
 
140
- allow(R10K::Module).to receive(:new).with('puppet/test_module', File.join(subject.basedir, 'vendor'), module_opts, anything).and_call_original
113
+ it "is idempotent" do
114
+ path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-with-version')
115
+ subject = described_class.new(path, {})
141
116
 
142
- expect { subject.add_module('puppet/test_module', module_opts) }.to raise_error(R10K::Error, /cannot manage content.*is not within/i).and not_change { subject.modules }
143
- end
117
+ expect(subject.loader).to receive(:load).and_call_original.once
144
118
 
145
- it "should disable and not add modules that conflict with the environment" do
146
- env = instance_double('R10K::Environment::Base')
147
- mod = instance_double('R10K::Module::Base', name: 'conflict', origin: :puppetfile)
148
- allow(mod).to receive(:origin=).and_return(nil)
149
- allow(subject).to receive(:environment).and_return(env)
150
- allow(env).to receive(:'module_conflicts?').with(mod).and_return(true)
119
+ loaded_content1 = subject.load
120
+ expect(subject.loaded?).to be true
121
+ loaded_content2 = subject.load
151
122
 
152
- allow(R10K::Module).to receive(:new).with('test', anything, anything, anything).and_return(mod)
153
- expect { subject.add_module('test', {}) }.not_to change { subject.modules }
154
- end
155
- end
156
-
157
- describe "#purge_exclusions" do
158
- let(:managed_dirs) { ['dir1', 'dir2'] }
123
+ expect(loaded_content2).to eq(loaded_content1)
124
+ end
159
125
 
160
- before(:each) do
161
- allow(subject).to receive(:managed_directories).and_return(managed_dirs)
126
+ it "returns nil if Puppetfile doesn't exist" do
127
+ path = '/rando/path/that/wont/exist'
128
+ subject = described_class.new(path, {})
129
+ expect(subject.load).to eq nil
130
+ end
162
131
  end
163
132
 
164
- it "includes managed_directories" do
165
- expect(subject.purge_exclusions).to match_array(managed_dirs)
166
- end
133
+ context 'using load!' do
134
+ it "returns the loaded content" do
135
+ path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-with-version')
136
+ subject = described_class.new(path, {})
167
137
 
168
- context "when belonging to an environment" do
169
- let(:env_contents) { ['env1', 'env2' ] }
138
+ loaded_content = subject.load!
139
+ expect(loaded_content).to be_an_instance_of(Hash)
170
140
 
171
- before(:each) do
172
- mock_env = double(:environment, desired_contents: env_contents)
173
- allow(subject).to receive(:environment).and_return(mock_env)
141
+ has_some_data = loaded_content.values.none?(&:empty?)
142
+ expect(has_some_data).to be true
174
143
  end
175
144
 
176
- it "includes environment's desired_contents" do
177
- expect(subject.purge_exclusions).to match_array(managed_dirs + env_contents)
145
+ it "raises if Puppetfile doesn't exist" do
146
+ path = '/rando/path/that/wont/exist'
147
+ subject = described_class.new(path, {})
148
+ expect {
149
+ subject.load!
150
+ }.to raise_error(/No such file or directory.*\/rando\/path\/.*/)
178
151
  end
179
152
  end
180
153
  end
181
154
 
182
- describe '#managed_directories' do
183
- it 'returns an array of paths that can be purged' do
184
- allow(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, hash_including(version: '1.2.3'), anything).and_call_original
155
+ describe 'default_branch_override' do
156
+ it 'is passed correctly to module loader init' do
157
+ # This path doesn't matter so long as it has a Puppetfile within it
158
+ path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-with-version')
159
+ subject = described_class.new(path, {overrides: {environments: {default_branch_override: 'foo'}}})
185
160
 
186
- subject.add_module('puppet/test_module', '1.2.3')
187
- expect(subject.managed_directories).to match_array(["/some/nonexistent/basedir/modules"])
188
- end
161
+ repo = instance_double('R10K::Git::StatefulRepository')
162
+ allow(repo).to receive(:resolve).with('foo').and_return(true)
163
+ allow(R10K::Git::StatefulRepository).to receive(:new).and_return(repo)
189
164
 
190
- context 'with a module with install_path == \'\'' do
191
- it 'basedir isn\'t in the list of paths to purge' do
192
- module_opts = { install_path: '', git: 'git@example.com:puppet/test_module.git' }
165
+ allow(subject.loader).to receive(:puppetfile_content).and_return <<-EOPF
166
+ # Track control branch and fall-back to main if no matching branch.
167
+ mod 'hieradata',
168
+ :git => 'git@git.example.com:organization/hieradata.git',
169
+ :branch => :control_branch,
170
+ :default_branch => 'main'
171
+ EOPF
193
172
 
194
- allow(R10K::Module).to receive(:new).with('puppet/test_module', subject.basedir, module_opts, anything).and_call_original
173
+ expect(subject.logger).not_to receive(:warn).
174
+ with(/Mismatch between passed and initialized.*preferring passed value/)
195
175
 
196
- subject.add_module('puppet/test_module', module_opts)
197
- expect(subject.managed_directories).to be_empty
198
- end
199
- end
200
- end
176
+ subject.load
201
177
 
202
- describe "evaluating a Puppetfile" do
203
- def expect_wrapped_error(orig, pf_path, wrapped_error)
204
- expect(orig).to be_a_kind_of(R10K::Error)
205
- expect(orig.message).to eq("Failed to evaluate #{pf_path}")
206
- expect(orig.original).to be_a_kind_of(wrapped_error)
178
+ loaded_module = subject.modules.first
179
+ expect(loaded_module.version).to eq('foo')
207
180
  end
208
181
 
209
- it "wraps and re-raises syntax errors" do
210
- path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'invalid-syntax')
211
- pf_path = File.join(path, 'Puppetfile')
212
- subject = described_class.new(path, {})
213
- expect {
214
- subject.load!
215
- }.to raise_error do |e|
216
- expect_wrapped_error(e, pf_path, SyntaxError)
217
- end
218
- end
182
+ it 'overrides module loader init if needed' do
183
+ # This path doesn't matter so long as it has a Puppetfile within it
184
+ path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-with-version')
185
+ subject = described_class.new(path, {overrides: {environments: {default_branch_override: 'foo'}}})
219
186
 
220
- it "wraps and re-raises load errors" do
221
- path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'load-error')
222
- pf_path = File.join(path, 'Puppetfile')
223
- subject = described_class.new(path, {})
224
- expect {
225
- subject.load!
226
- }.to raise_error do |e|
227
- expect_wrapped_error(e, pf_path, LoadError)
228
- end
229
- end
187
+ repo = instance_double('R10K::Git::StatefulRepository')
188
+ allow(repo).to receive(:resolve).with('bar').and_return(true)
189
+ allow(R10K::Git::StatefulRepository).to receive(:new).and_return(repo)
230
190
 
231
- it "wraps and re-raises argument errors" do
232
- path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'argument-error')
233
- pf_path = File.join(path, 'Puppetfile')
234
- subject = described_class.new(path, {})
235
- expect {
236
- subject.load!
237
- }.to raise_error do |e|
238
- expect_wrapped_error(e, pf_path, ArgumentError)
239
- end
240
- end
191
+ allow(subject.loader).to receive(:puppetfile_content).and_return <<-EOPF
192
+ # Track control branch and fall-back to main if no matching branch.
193
+ mod 'hieradata',
194
+ :git => 'git@git.example.com:organization/hieradata.git',
195
+ :branch => :control_branch,
196
+ :default_branch => 'main'
197
+ EOPF
241
198
 
242
- it "rejects Puppetfiles with duplicate module names" do
243
- path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'duplicate-module-error')
244
- pf_path = File.join(path, 'Puppetfile')
245
- subject = described_class.new(path, {})
246
- expect {
247
- subject.load!
248
- }.to raise_error(R10K::Error, /Puppetfiles cannot contain duplicate module names/i)
249
- end
199
+ expect(subject.logger).to receive(:warn).
200
+ with(/Mismatch between passed and initialized.*preferring passed value/)
250
201
 
251
- it "wraps and re-raises name errors" do
252
- path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'name-error')
253
- pf_path = File.join(path, 'Puppetfile')
254
- subject = described_class.new(path, {})
255
- expect {
256
- subject.load!
257
- }.to raise_error do |e|
258
- expect_wrapped_error(e, pf_path, NameError)
259
- end
202
+ subject.load('bar')
203
+ loaded_module = subject.modules.first
204
+ expect(loaded_module.version).to eq('bar')
260
205
  end
261
206
 
262
- it "accepts a forge module with a version" do
207
+ it 'does not warn if passed and initialized default_branch_overrides match' do
208
+ # This path doesn't matter so long as it has a Puppetfile within it
263
209
  path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-with-version')
264
- pf_path = File.join(path, 'Puppetfile')
265
- subject = described_class.new(path, {})
266
- expect { subject.load! }.not_to raise_error
267
- end
268
-
269
- it "accepts a forge module without a version" do
270
- path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-without-version')
271
- pf_path = File.join(path, 'Puppetfile')
272
- subject = described_class.new(path, {})
273
- expect { subject.load! }.not_to raise_error
274
- end
275
-
276
- it "creates a git module and applies the default branch sepcified in the Puppetfile" do
277
- path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'default-branch-override')
278
- pf_path = File.join(path, 'Puppetfile')
279
- subject = described_class.new(path, {})
280
- expect { subject.load! }.not_to raise_error
281
- git_module = subject.modules[0]
282
- expect(git_module.default_ref).to eq 'here_lies_the_default_branch'
283
- end
284
-
285
- it "creates a git module and applies the provided default_branch_override" do
286
- path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'default-branch-override')
287
- pf_path = File.join(path, 'Puppetfile')
288
- subject = described_class.new(path, {})
289
- default_branch_override = 'default_branch_override_name'
290
- expect { subject.load!(default_branch_override) }.not_to raise_error
291
- git_module = subject.modules[0]
292
- expect(git_module.default_override_ref).to eq default_branch_override
293
- expect(git_module.default_ref).to eq "here_lies_the_default_branch"
210
+ subject = described_class.new(path, {overrides: {environments: {default_branch_override: 'foo'}}})
211
+
212
+ repo = instance_double('R10K::Git::StatefulRepository')
213
+ allow(repo).to receive(:resolve).with('foo').and_return(true)
214
+ allow(R10K::Git::StatefulRepository).to receive(:new).and_return(repo)
215
+
216
+ allow(subject.loader).to receive(:puppetfile_content).and_return <<-EOPF
217
+ # Track control branch and fall-back to main if no matching branch.
218
+ mod 'hieradata',
219
+ :git => 'git@git.example.com:organization/hieradata.git',
220
+ :branch => :control_branch,
221
+ :default_branch => 'main'
222
+ EOPF
223
+
224
+ expect(subject.logger).not_to receive(:warn).
225
+ with(/Mismatch between passed and initialized.*preferring passed value/)
226
+
227
+ subject.load('foo')
228
+ loaded_module = subject.modules.first
229
+ expect(loaded_module.version).to eq('foo')
294
230
  end
295
231
  end
296
232
 
@@ -95,6 +95,24 @@ describe R10K::Settings do
95
95
  describe "deploy settings" do
96
96
  subject { described_class.deploy_settings }
97
97
 
98
+ describe 'exclude_spec' do
99
+ it 'is false by default' do
100
+ expect(subject.evaluate({})[:exclude_spec]).to eq(false)
101
+ end
102
+ it 'can be set to true' do
103
+ expect(subject.evaluate({"exclude_spec" => true})[:exclude_spec]).to eq(true)
104
+ end
105
+ it "raises an error for non-boolean values" do
106
+ expect {
107
+ subject.evaluate({"exclude_spec" => 'invalid_string'})
108
+ }.to raise_error do |err|
109
+ expect(err.message).to match(/Validation failed for 'deploy' settings group/)
110
+ expect(err.errors.size).to eq 1
111
+ expect(err.errors[:exclude_spec]).to be_a_kind_of(ArgumentError)
112
+ expect(err.errors[:exclude_spec].message).to match(/`exclude_spec` can only be a boolean value, not 'invalid_string'/)
113
+ end
114
+ end
115
+ end
98
116
  describe "write_lock" do
99
117
  it "accepts a string with a reason for the write lock" do
100
118
  output = subject.evaluate("write_lock" => "No maintenance window active, code freeze till 2038-01-19")
@@ -250,8 +268,12 @@ describe R10K::Settings do
250
268
 
251
269
  describe "forge settings" do
252
270
  it "passes settings through to the forge settings" do
253
- output = subject.evaluate("forge" => {"baseurl" => "https://forge.tessier-ashpool.freeside", "proxy" => "https://proxy.tessier-ashpool.freesize:3128"})
254
- expect(output[:forge]).to eq(:baseurl => "https://forge.tessier-ashpool.freeside", :proxy => "https://proxy.tessier-ashpool.freesize:3128")
271
+ output = subject.evaluate("forge" => {"baseurl" => "https://forge.tessier-ashpool.freeside",
272
+ "proxy" => "https://proxy.tessier-ashpool.freesize:3128",
273
+ "authorization_token" => "faketoken"})
274
+ expect(output[:forge]).to eq(:baseurl => "https://forge.tessier-ashpool.freeside",
275
+ :proxy => "https://proxy.tessier-ashpool.freesize:3128",
276
+ :authorization_token => "faketoken")
255
277
  end
256
278
  end
257
279
  end
@@ -19,6 +19,7 @@ RSpec.describe R10K::Util::Purgeable do
19
19
  'spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/subdir_new_1',
20
20
  'spec/fixtures/unit/util/purgeable/managed_two/expected_2',
21
21
  'spec/fixtures/unit/util/purgeable/managed_two/new_2',
22
+ 'spec/fixtures/unit/util/purgeable/managed_two/.hidden',
22
23
  ]
23
24
  end
24
25
 
@@ -98,7 +99,13 @@ RSpec.describe R10K::Util::Purgeable do
98
99
 
99
100
  describe '#current_contents' do
100
101
  it 'collects contents of all managed directories recursively' do
101
- expect(subject.current_contents(recurse)).to contain_exactly(/\/expected_1/, /\/expected_2/, /\/unmanaged_1/, /\/unmanaged_2/, /\/managed_subdir_1/, /\/subdir_expected_1/, /\/subdir_unmanaged_1/)
102
+ expect(subject.current_contents(recurse)).
103
+ to contain_exactly(/\/expected_1/, /\/expected_2/,
104
+ /\/unmanaged_1/, /\/unmanaged_2/,
105
+ /\/managed_subdir_1/,
106
+ /\/subdir_expected_1/, /\/subdir_unmanaged_1/,
107
+ /\/managed_subdir_2/, /\/ignored_1/,
108
+ /\/\.hidden/)
102
109
  end
103
110
  end
104
111
 
@@ -114,7 +121,8 @@ RSpec.describe R10K::Util::Purgeable do
114
121
  let(:whitelist) { [] }
115
122
 
116
123
  it 'collects current_contents that should not exist recursively' do
117
- expect(subject.stale_contents(recurse, exclusions, whitelist)).to contain_exactly(/\/unmanaged_1/, /\/unmanaged_2/, /\/subdir_unmanaged_1/)
124
+ expect(subject.stale_contents(recurse, exclusions, whitelist)).
125
+ to contain_exactly(/\/unmanaged_1/, /\/unmanaged_2/, /\/subdir_unmanaged_1/, /\/ignored_1/)
118
126
  end
119
127
  end
120
128
 
@@ -124,7 +132,17 @@ RSpec.describe R10K::Util::Purgeable do
124
132
 
125
133
  it 'collects current_contents that should not exist except whitelisted items' do
126
134
  expect(subject.logger).to receive(:debug).with(/unmanaged_1.*whitelist match/i)
127
- expect(subject.stale_contents(recurse, exclusions, whitelist)).to contain_exactly(/\/unmanaged_2/, /\/subdir_unmanaged_1/)
135
+
136
+ expect(subject.stale_contents(recurse, exclusions, whitelist)).
137
+ to contain_exactly(/\/unmanaged_2/, /\/subdir_unmanaged_1/, /\/ignored_1/)
138
+ end
139
+
140
+ it 'does not collect contents that match recursive globbed whitelist items as intermediate values' do
141
+ recursive_whitelist = ['**/managed_subdir_1/**/*']
142
+ expect(subject.logger).not_to receive(:debug).with(/ignored_1/)
143
+
144
+ expect(subject.stale_contents(recurse, exclusions, recursive_whitelist)).
145
+ to contain_exactly(/\/unmanaged_2/, /\/managed_one\/unmanaged_1/)
128
146
  end
129
147
  end
130
148
 
@@ -134,7 +152,17 @@ RSpec.describe R10K::Util::Purgeable do
134
152
 
135
153
  it 'collects current_contents that should not exist except excluded items' do
136
154
  expect(subject.logger).to receive(:debug2).with(/unmanaged_2.*internal exclusion match/i)
137
- expect(subject.stale_contents(recurse, exclusions, whitelist)).to contain_exactly(/\/unmanaged_1/, /\/subdir_unmanaged_1/)
155
+
156
+ expect(subject.stale_contents(recurse, exclusions, whitelist)).
157
+ to contain_exactly(/\/unmanaged_1/, /\/subdir_unmanaged_1/, /\/ignored_1/)
158
+ end
159
+
160
+ it 'does not collect contents that match recursive globbed exclusion items as intermediate values' do
161
+ recursive_exclusions = ['**/managed_subdir_1/**/*']
162
+ expect(subject.logger).not_to receive(:debug).with(/ignored_1/)
163
+
164
+ expect(subject.stale_contents(recurse, recursive_exclusions, whitelist)).
165
+ to contain_exactly(/\/unmanaged_2/, /\/managed_one\/unmanaged_1/)
138
166
  end
139
167
  end
140
168
  end
@@ -179,7 +207,11 @@ RSpec.describe R10K::Util::Purgeable do
179
207
  end
180
208
 
181
209
  context "recursive whitelist glob" do
182
- let(:whitelist) { managed_directories.collect { |dir| File.join(dir, "**", "*unmanaged*") } }
210
+ let(:whitelist) do
211
+ managed_directories.flat_map do |dir|
212
+ [File.join(dir, "**", "*unmanaged*"), File.join(dir, "**", "managed_subdir_2")]
213
+ end
214
+ end
183
215
  let(:purge_opts) { { recurse: true, whitelist: whitelist } }
184
216
 
185
217
  describe '#purge!' do
@@ -214,7 +246,7 @@ RSpec.describe R10K::Util::Purgeable do
214
246
  context "when class does not implement #purge_exclusions" do
215
247
  describe '#purge!' do
216
248
  it 'purges normally' do
217
- expect(FileUtils).to receive(:rm_r).at_least(3).times
249
+ expect(FileUtils).to receive(:rm_r).at_least(4).times
218
250
 
219
251
  subject.purge!(purge_opts)
220
252
  end