r10k 3.9.1 → 3.11.0

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