r10k 3.9.0 → 3.10.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +1 -1
  3. data/.github/workflows/rspec_tests.yml +1 -1
  4. data/.github/workflows/stale.yml +19 -0
  5. data/CHANGELOG.mkd +24 -0
  6. data/doc/dynamic-environments/configuration.mkd +13 -6
  7. data/integration/Rakefile +1 -1
  8. data/integration/tests/user_scenario/basic_workflow/negative/neg_specify_deleted_forge_module.rb +3 -9
  9. data/integration/tests/user_scenario/basic_workflow/single_env_purge_unmanaged_modules.rb +8 -14
  10. data/lib/r10k/action/base.rb +10 -0
  11. data/lib/r10k/action/deploy/display.rb +42 -9
  12. data/lib/r10k/action/deploy/environment.rb +70 -41
  13. data/lib/r10k/action/deploy/module.rb +51 -29
  14. data/lib/r10k/action/puppetfile/check.rb +3 -1
  15. data/lib/r10k/action/puppetfile/install.rb +20 -23
  16. data/lib/r10k/action/puppetfile/purge.rb +8 -2
  17. data/lib/r10k/action/runner.rb +11 -6
  18. data/lib/r10k/content_synchronizer.rb +83 -0
  19. data/lib/r10k/deployment.rb +1 -1
  20. data/lib/r10k/environment/base.rb +21 -1
  21. data/lib/r10k/environment/git.rb +0 -3
  22. data/lib/r10k/environment/svn.rb +4 -6
  23. data/lib/r10k/environment/with_modules.rb +18 -10
  24. data/lib/r10k/git/cache.rb +1 -1
  25. data/lib/r10k/initializers.rb +7 -0
  26. data/lib/r10k/module.rb +1 -1
  27. data/lib/r10k/module/base.rb +17 -1
  28. data/lib/r10k/module/forge.rb +29 -19
  29. data/lib/r10k/module/git.rb +23 -14
  30. data/lib/r10k/module/local.rb +1 -0
  31. data/lib/r10k/module/svn.rb +12 -9
  32. data/lib/r10k/module_loader/puppetfile.rb +195 -0
  33. data/lib/r10k/module_loader/puppetfile/dsl.rb +37 -0
  34. data/lib/r10k/puppetfile.rb +111 -202
  35. data/lib/r10k/settings.rb +3 -0
  36. data/lib/r10k/source/base.rb +14 -0
  37. data/lib/r10k/source/git.rb +19 -6
  38. data/lib/r10k/source/hash.rb +1 -3
  39. data/lib/r10k/source/svn.rb +4 -2
  40. data/lib/r10k/util/cleaner.rb +21 -0
  41. data/lib/r10k/util/purgeable.rb +70 -8
  42. data/lib/r10k/version.rb +1 -1
  43. data/locales/r10k.pot +67 -71
  44. data/spec/fixtures/unit/action/r10k_forge_auth.yaml +4 -0
  45. data/spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml +3 -0
  46. data/spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_subdir_2/ignored_1 +0 -0
  47. data/spec/fixtures/unit/util/purgeable/managed_two/.hidden/unmanaged_3 +0 -0
  48. data/spec/r10k-mocks/mock_source.rb +1 -1
  49. data/spec/shared-examples/puppetfile-action.rb +7 -7
  50. data/spec/unit/action/deploy/display_spec.rb +32 -6
  51. data/spec/unit/action/deploy/environment_spec.rb +85 -48
  52. data/spec/unit/action/deploy/module_spec.rb +163 -31
  53. data/spec/unit/action/puppetfile/check_spec.rb +2 -2
  54. data/spec/unit/action/puppetfile/install_spec.rb +31 -10
  55. data/spec/unit/action/puppetfile/purge_spec.rb +25 -5
  56. data/spec/unit/action/runner_spec.rb +49 -25
  57. data/spec/unit/git/cache_spec.rb +14 -0
  58. data/spec/unit/module/forge_spec.rb +23 -14
  59. data/spec/unit/module/git_spec.rb +8 -8
  60. data/spec/unit/module_loader/puppetfile_spec.rb +330 -0
  61. data/spec/unit/module_spec.rb +22 -5
  62. data/spec/unit/puppetfile_spec.rb +123 -203
  63. data/spec/unit/settings_spec.rb +6 -2
  64. data/spec/unit/util/purgeable_spec.rb +40 -14
  65. metadata +12 -2
@@ -218,42 +218,66 @@ describe R10K::Action::Runner do
218
218
  end
219
219
 
220
220
  describe "configuration authorization" do
221
- context "when license is not present" do
222
- before(:each) do
223
- expect(R10K::Util::License).to receive(:load).and_return(nil)
221
+ context "settings auth" do
222
+ it "sets the configured token as the forge authorization header" do
223
+ options = { config: "spec/fixtures/unit/action/r10k_forge_auth.yaml" }
224
+ runner = described_class.new(options, %w[args yes], action_class)
225
+
226
+ expect(PuppetForge).to receive(:host=).with('http://private-forge.com')
227
+ expect(PuppetForge::Connection).to receive(:authorization=).with('faketoken')
228
+ expect(PuppetForge::Connection).to receive(:authorization).and_return('faketoken')
229
+ expect(R10K::Util::License).not_to receive(:load)
230
+ runner.setup_settings
231
+ runner.setup_authorization
224
232
  end
225
233
 
226
- it "does not set authorization header on connection class" do
227
- expect(PuppetForge::Connection).not_to receive(:authorization=)
228
- runner.setup_authorization
234
+ it 'errors if no custom forge URL is set' do
235
+ options = { config: "spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml" }
236
+ runner = described_class.new(options, %w[args yes], action_class)
237
+ expect(PuppetForge::Connection).not_to receive(:authorization=).with('faketoken')
238
+
239
+ expect { runner.setup_settings }.to raise_error(R10K::Error, /Cannot specify a Forge auth/)
229
240
  end
230
241
  end
231
242
 
232
- context "when license is present but invalid" do
233
- before(:each) do
234
- expect(R10K::Util::License).to receive(:load).and_raise(R10K::Error.new('invalid license'))
235
- end
243
+ context "license auth" do
244
+ context "when license is not present" do
245
+ before(:each) do
246
+ expect(R10K::Util::License).to receive(:load).and_return(nil)
247
+ end
236
248
 
237
- it "issues warning to logger" do
238
- expect(runner.logger).to receive(:warn).with(/invalid license/)
239
- runner.setup_authorization
249
+ it "does not set authorization header on connection class" do
250
+ expect(PuppetForge::Connection).not_to receive(:authorization=)
251
+ runner.setup_authorization
252
+ end
240
253
  end
241
254
 
242
- it "does not set authorization header on connection class" do
243
- expect(PuppetForge::Connection).not_to receive(:authorization=)
244
- runner.setup_authorization
245
- end
246
- end
255
+ context "when license is present but invalid" do
256
+ before(:each) do
257
+ expect(R10K::Util::License).to receive(:load).and_raise(R10K::Error.new('invalid license'))
258
+ end
247
259
 
248
- context "when license is present and valid" do
249
- before(:each) do
250
- mock_license = double('pe-license', :authorization_token => 'test token')
251
- expect(R10K::Util::License).to receive(:load).and_return(mock_license)
260
+ it "issues warning to logger" do
261
+ expect(runner.logger).to receive(:warn).with(/invalid license/)
262
+ runner.setup_authorization
263
+ end
264
+
265
+ it "does not set authorization header on connection class" do
266
+ expect(PuppetForge::Connection).not_to receive(:authorization=)
267
+ runner.setup_authorization
268
+ end
252
269
  end
253
270
 
254
- it "sets authorization header on connection class" do
255
- expect(PuppetForge::Connection).to receive(:authorization=).with('test token')
256
- runner.setup_authorization
271
+ context "when license is present and valid" do
272
+ before(:each) do
273
+ mock_license = double('pe-license', :authorization_token => 'test token')
274
+ expect(R10K::Util::License).to receive(:load).and_return(mock_license)
275
+ end
276
+
277
+ it "sets authorization header on connection class" do
278
+ expect(PuppetForge::Connection).to receive(:authorization=).with('test token')
279
+ runner.setup_authorization
280
+ end
257
281
  end
258
282
  end
259
283
  end
@@ -62,4 +62,18 @@ describe R10K::Git::Cache do
62
62
  subject.cached?
63
63
  end
64
64
  end
65
+
66
+ describe "dirname sanitization" do
67
+ it 'sanitizes cache directory name' do
68
+ expect(subject.sanitized_dirname).to eq('git---some-git-remote')
69
+ end
70
+
71
+ context 'with username and password' do
72
+ subject { subclass.new('https://"user:pa$$w0rd:@some/git/remote') }
73
+
74
+ it 'sanitizes cache directory name' do
75
+ expect(subject.sanitized_dirname).to eq('https---some-git-remote')
76
+ end
77
+ end
78
+ end
65
79
  end
@@ -11,15 +11,15 @@ describe R10K::Module::Forge do
11
11
 
12
12
  describe "implementing the Puppetfile spec" do
13
13
  it "should implement 'branan/eight_hundred', '8.0.0'" do
14
- expect(described_class).to be_implement('branan/eight_hundred', '8.0.0')
14
+ expect(described_class).to be_implement('branan/eight_hundred', { version: '8.0.0' })
15
15
  end
16
16
 
17
17
  it "should implement 'branan-eight_hundred', '8.0.0'" do
18
- expect(described_class).to be_implement('branan-eight_hundred', '8.0.0')
18
+ expect(described_class).to be_implement('branan-eight_hundred', { version: '8.0.0' })
19
19
  end
20
20
 
21
21
  it "should fail with an invalid title" do
22
- expect(described_class).to_not be_implement('branan!eight_hundred', '8.0.0')
22
+ expect(described_class).to_not be_implement('branan!eight_hundred', { version: '8.0.0' })
23
23
  end
24
24
  end
25
25
 
@@ -30,7 +30,7 @@ describe R10K::Module::Forge do
30
30
  end
31
31
 
32
32
  describe "setting attributes" do
33
- subject { described_class.new('branan/eight_hundred', '/moduledir', '8.0.0') }
33
+ subject { described_class.new('branan/eight_hundred', '/moduledir', { version: '8.0.0' }) }
34
34
 
35
35
  it "sets the name" do
36
36
  expect(subject.name).to eq 'eight_hundred'
@@ -50,7 +50,7 @@ describe R10K::Module::Forge do
50
50
  end
51
51
 
52
52
  describe "properties" do
53
- subject { described_class.new('branan/eight_hundred', fixture_modulepath, '8.0.0') }
53
+ subject { described_class.new('branan/eight_hundred', fixture_modulepath, { version: '8.0.0' }) }
54
54
 
55
55
  it "sets the module type to :forge" do
56
56
  expect(subject.properties).to include(:type => :forge)
@@ -67,7 +67,7 @@ describe R10K::Module::Forge do
67
67
  end
68
68
 
69
69
  context "when a module is deprecated" do
70
- subject { described_class.new('puppetlabs/corosync', fixture_modulepath, :latest) }
70
+ subject { described_class.new('puppetlabs/corosync', fixture_modulepath, { version: :latest }) }
71
71
 
72
72
  it "warns on sync if module is not already insync" do
73
73
  allow(subject).to receive(:status).and_return(:absent)
@@ -77,6 +77,7 @@ describe R10K::Module::Forge do
77
77
  logger_dbl = double(Log4r::Logger)
78
78
  allow_any_instance_of(described_class).to receive(:logger).and_return(logger_dbl)
79
79
 
80
+ allow(logger_dbl).to receive(:info).with(/Deploying module to.*/)
80
81
  expect(logger_dbl).to receive(:warn).with(/puppet forge module.*puppetlabs-corosync.*has been deprecated/i)
81
82
 
82
83
  subject.sync
@@ -88,6 +89,7 @@ describe R10K::Module::Forge do
88
89
  logger_dbl = double(Log4r::Logger)
89
90
  allow_any_instance_of(described_class).to receive(:logger).and_return(logger_dbl)
90
91
 
92
+ allow(logger_dbl).to receive(:info).with(/Deploying module to.*/)
91
93
  expect(logger_dbl).to_not receive(:warn).with(/puppet forge module.*puppetlabs-corosync.*has been deprecated/i)
92
94
 
93
95
  subject.sync
@@ -96,20 +98,27 @@ describe R10K::Module::Forge do
96
98
 
97
99
  describe '#expected_version' do
98
100
  it "returns an explicitly given expected version" do
99
- subject = described_class.new('branan/eight_hundred', fixture_modulepath, '8.0.0')
101
+ subject = described_class.new('branan/eight_hundred', fixture_modulepath, { version: '8.0.0' })
100
102
  expect(subject.expected_version).to eq '8.0.0'
101
103
  end
102
104
 
103
105
  it "uses the latest version from the forge when the version is :latest" do
104
- subject = described_class.new('branan/eight_hundred', fixture_modulepath, :latest)
105
- expect(subject.v3_module).to receive_message_chain(:current_release, :version).and_return('8.8.8')
106
+ subject = described_class.new('branan/eight_hundred', fixture_modulepath, { version: :latest })
107
+ release = double("Module Release", version: '8.8.8')
108
+ expect(subject.v3_module).to receive(:current_release).and_return(release).twice
106
109
  expect(subject.expected_version).to eq '8.8.8'
107
110
  end
111
+
112
+ it "throws when there are no available versions" do
113
+ subject = described_class.new('branan/eight_hundred', fixture_modulepath, { version: :latest })
114
+ expect(subject.v3_module).to receive(:current_release).and_return(nil)
115
+ expect { subject.expected_version }.to raise_error(PuppetForge::ReleaseNotFound)
116
+ end
108
117
  end
109
118
 
110
119
  describe "determining the status" do
111
120
 
112
- subject { described_class.new('branan/eight_hundred', fixture_modulepath, '8.0.0') }
121
+ subject { described_class.new('branan/eight_hundred', fixture_modulepath, { version: '8.0.0' }) }
113
122
 
114
123
  it "is :absent if the module directory is absent" do
115
124
  allow(subject).to receive(:exist?).and_return false
@@ -154,7 +163,7 @@ describe R10K::Module::Forge do
154
163
  end
155
164
 
156
165
  describe "#sync" do
157
- subject { described_class.new('branan/eight_hundred', fixture_modulepath, '8.0.0') }
166
+ subject { described_class.new('branan/eight_hundred', fixture_modulepath, { version: '8.0.0' }) }
158
167
 
159
168
  it 'does nothing when the module is in sync' do
160
169
  allow(subject).to receive(:status).and_return :insync
@@ -186,7 +195,7 @@ describe R10K::Module::Forge do
186
195
 
187
196
  describe '#install' do
188
197
  it 'installs the module from the forge' do
189
- subject = described_class.new('branan/eight_hundred', fixture_modulepath, '8.0.0')
198
+ subject = described_class.new('branan/eight_hundred', fixture_modulepath, { version: '8.0.0' })
190
199
  release = instance_double('R10K::Forge::ModuleRelease')
191
200
  expect(R10K::Forge::ModuleRelease).to receive(:new).with('branan-eight_hundred', '8.0.0').and_return(release)
192
201
  expect(release).to receive(:install).with(subject.path)
@@ -196,7 +205,7 @@ describe R10K::Module::Forge do
196
205
 
197
206
  describe '#uninstall' do
198
207
  it 'removes the module path' do
199
- subject = described_class.new('branan/eight_hundred', fixture_modulepath, '8.0.0')
208
+ subject = described_class.new('branan/eight_hundred', fixture_modulepath, { version: '8.0.0' })
200
209
  expect(FileUtils).to receive(:rm_rf).with(subject.path.to_s)
201
210
  subject.uninstall
202
211
  end
@@ -204,7 +213,7 @@ describe R10K::Module::Forge do
204
213
 
205
214
  describe '#reinstall' do
206
215
  it 'uninstalls and then installs the module' do
207
- subject = described_class.new('branan/eight_hundred', fixture_modulepath, '8.0.0')
216
+ subject = described_class.new('branan/eight_hundred', fixture_modulepath, { version: '8.0.0' })
208
217
  expect(subject).to receive(:uninstall)
209
218
  expect(subject).to receive(:install)
210
219
  subject.reinstall
@@ -119,14 +119,6 @@ describe R10K::Module::Git do
119
119
  allow(mock_repo).to receive(:head).and_return('abc123')
120
120
  end
121
121
 
122
- context "when option is unrecognized" do
123
- let(:opts) { { unrecognized: true } }
124
-
125
- it "raises an error" do
126
- expect { test_module(opts) }.to raise_error(ArgumentError, /cannot handle option 'unrecognized'/)
127
- end
128
- end
129
-
130
122
  describe "desired ref" do
131
123
  context "when no desired ref is given" do
132
124
  it "defaults to master" do
@@ -210,6 +202,14 @@ describe R10K::Module::Git do
210
202
  expect(mod.desired_ref).to eq(:control_branch)
211
203
  end
212
204
 
205
+ it "warns control branch may be unresolvable" do
206
+ logger = double("logger")
207
+ allow_any_instance_of(described_class).to receive(:logger).and_return(logger)
208
+ expect(logger).to receive(:warn).with(/Cannot track control repo branch.*boolean.*/)
209
+
210
+ test_module(branch: :control_branch)
211
+ end
212
+
213
213
  context "when default ref is provided and resolvable" do
214
214
  it "uses default ref" do
215
215
  expect(mock_repo).to receive(:resolve).with('default').and_return('abc123')
@@ -0,0 +1,330 @@
1
+ require 'spec_helper'
2
+ require 'r10k/module_loader/puppetfile'
3
+
4
+ describe R10K::ModuleLoader::Puppetfile do
5
+ describe 'initial parameters' do
6
+ describe 'honor' do
7
+ let(:options) do
8
+ {
9
+ basedir: '/test/basedir/env',
10
+ forge: 'localforge.internal.corp',
11
+ overrides: { modules: { deploy_modules: true } },
12
+ environment: R10K::Environment::Git.new('env',
13
+ '/test/basedir/',
14
+ 'env',
15
+ { remote: 'git://foo/remote',
16
+ ref: 'env' })
17
+ }
18
+ end
19
+
20
+ subject { R10K::ModuleLoader::Puppetfile.new(**options) }
21
+
22
+ describe 'the moduledir' do
23
+ it 'respects absolute paths' do
24
+ absolute_options = options.merge({moduledir: '/opt/puppetlabs/special/modules'})
25
+ puppetfile = R10K::ModuleLoader::Puppetfile.new(**absolute_options)
26
+ expect(puppetfile.instance_variable_get(:@moduledir)).to eq('/opt/puppetlabs/special/modules')
27
+ end
28
+
29
+ it 'roots the moduledir in the basepath if a relative path is specified' do
30
+ relative_options = options.merge({moduledir: 'my/special/modules'})
31
+ puppetfile = R10K::ModuleLoader::Puppetfile.new(**relative_options)
32
+ expect(puppetfile.instance_variable_get(:@moduledir)).to eq('/test/basedir/env/my/special/modules')
33
+ end
34
+ end
35
+
36
+ describe 'the Puppetfile' do
37
+ it 'respects absolute paths' do
38
+ absolute_options = options.merge({puppetfile: '/opt/puppetlabs/special/Puppetfile'})
39
+ puppetfile = R10K::ModuleLoader::Puppetfile.new(**absolute_options)
40
+ expect(puppetfile.instance_variable_get(:@puppetfile)).to eq('/opt/puppetlabs/special/Puppetfile')
41
+ end
42
+
43
+ it 'roots the Puppetfile in the basepath if a relative path is specified' do
44
+ relative_options = options.merge({puppetfile: 'Puppetfile.global'})
45
+ puppetfile = R10K::ModuleLoader::Puppetfile.new(**relative_options)
46
+ expect(puppetfile.instance_variable_get(:@puppetfile)).to eq('/test/basedir/env/Puppetfile.global')
47
+ end
48
+ end
49
+
50
+ it 'the forge' do
51
+ expect(subject.instance_variable_get(:@forge)).to eq('localforge.internal.corp')
52
+ end
53
+
54
+ it 'the overrides' do
55
+ expect(subject.instance_variable_get(:@overrides)).to eq({ modules: { deploy_modules: true }})
56
+ end
57
+
58
+ it 'the environment' do
59
+ expect(subject.instance_variable_get(:@environment).name).to eq('env')
60
+ end
61
+ end
62
+
63
+ describe 'sane defaults' do
64
+ subject { R10K::ModuleLoader::Puppetfile.new(basedir: '/test/basedir') }
65
+
66
+ it 'has a moduledir rooted in the basedir' do
67
+ expect(subject.instance_variable_get(:@moduledir)).to eq('/test/basedir/modules')
68
+ end
69
+
70
+ it 'has a Puppetfile rooted in the basedir' do
71
+ expect(subject.instance_variable_get(:@puppetfile)).to eq('/test/basedir/Puppetfile')
72
+ end
73
+
74
+ it 'uses the public forge' do
75
+ expect(subject.instance_variable_get(:@forge)).to eq('forgeapi.puppetlabs.com')
76
+ end
77
+
78
+ it 'creates an empty overrides' do
79
+ expect(subject.instance_variable_get(:@overrides)).to eq({})
80
+ end
81
+
82
+ it 'does not require an environment' do
83
+ expect(subject.instance_variable_get(:@environment)).to eq(nil)
84
+ end
85
+ end
86
+ end
87
+
88
+ describe 'adding modules' do
89
+ let(:basedir) { '/test/basedir' }
90
+
91
+ subject { R10K::ModuleLoader::Puppetfile.new(basedir: basedir) }
92
+
93
+ it 'should transform Forge modules with a string arg to have a version key' do
94
+ expect(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, hash_including(version: '1.2.3'), anything).and_call_original
95
+
96
+ expect { subject.add_module('puppet/test_module', '1.2.3') }.to change { subject.modules }
97
+ expect(subject.modules.collect(&:name)).to include('test_module')
98
+ end
99
+
100
+ it 'should not accept Forge modules with a version comparison' do
101
+ expect(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, hash_including(version: '< 1.2.0'), anything).and_call_original
102
+
103
+ expect {
104
+ subject.add_module('puppet/test_module', '< 1.2.0')
105
+ }.to raise_error(RuntimeError, /module puppet\/test_module.*doesn't have an implementation/i)
106
+
107
+ expect(subject.modules.collect(&:name)).not_to include('test_module')
108
+ end
109
+
110
+ it 'should accept non-Forge modules with a hash arg' do
111
+ module_opts = { git: 'git@example.com:puppet/test_module.git' }
112
+
113
+ expect(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, module_opts, anything).and_call_original
114
+
115
+ expect { subject.add_module('puppet/test_module', module_opts) }.to change { subject.modules }
116
+ expect(subject.modules.collect(&:name)).to include('test_module')
117
+ end
118
+
119
+ it 'should accept non-Forge modules with a valid relative :install_path option' do
120
+ module_opts = {
121
+ install_path: 'vendor',
122
+ git: 'git@example.com:puppet/test_module.git',
123
+ }
124
+
125
+ expect(R10K::Module).to receive(:new).with('puppet/test_module', File.join(basedir, 'vendor'), module_opts, anything).and_call_original
126
+
127
+ expect { subject.add_module('puppet/test_module', module_opts) }.to change { subject.modules }
128
+ expect(subject.modules.collect(&:name)).to include('test_module')
129
+ end
130
+
131
+ it 'should accept non-Forge modules with a valid absolute :install_path option' do
132
+ install_path = File.join(basedir, 'vendor')
133
+
134
+ module_opts = {
135
+ install_path: install_path,
136
+ git: 'git@example.com:puppet/test_module.git',
137
+ }
138
+
139
+ expect(R10K::Module).to receive(:new).with('puppet/test_module', install_path, module_opts, anything).and_call_original
140
+
141
+ expect { subject.add_module('puppet/test_module', module_opts) }.to change { subject.modules }
142
+ expect(subject.modules.collect(&:name)).to include('test_module')
143
+ end
144
+
145
+ it 'should reject non-Forge modules with an invalid relative :install_path option' do
146
+ module_opts = {
147
+ install_path: '../../vendor',
148
+ git: 'git@example.com:puppet/test_module.git',
149
+ }
150
+
151
+ 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 }
152
+ end
153
+
154
+ it 'should reject non-Forge modules with an invalid absolute :install_path option' do
155
+ module_opts = {
156
+ install_path: '/tmp/mydata/vendor',
157
+ git: 'git@example.com:puppet/test_module.git',
158
+ }
159
+
160
+ 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 }
161
+ end
162
+
163
+ it 'should disable and not add modules that conflict with the environment' do
164
+ env = instance_double('R10K::Environment::Base')
165
+ mod = instance_double('R10K::Module::Base', name: 'conflict', origin: :puppetfile, 'origin=': nil)
166
+ loader = R10K::ModuleLoader::Puppetfile.new(basedir: basedir, environment: env)
167
+ allow(env).to receive(:'module_conflicts?').with(mod).and_return(true)
168
+
169
+ expect(R10K::Module).to receive(:new).with('conflict', anything, anything, anything).and_return(mod)
170
+ expect { loader.add_module('conflict', {}) }.not_to change { loader.modules }
171
+ end
172
+ end
173
+
174
+ describe '#purge_exclusions' do
175
+ let(:managed_dirs) { ['dir1', 'dir2'] }
176
+ subject { R10K::ModuleLoader::Puppetfile.new(basedir: '/test/basedir') }
177
+
178
+ it 'includes managed_directories' do
179
+ expect(subject.send(:determine_purge_exclusions, managed_dirs)).to match_array(managed_dirs)
180
+ end
181
+
182
+ context 'when belonging to an environment' do
183
+ let(:env_contents) { ['env1', 'env2' ] }
184
+ let(:env) { double(:environment, desired_contents: env_contents) }
185
+
186
+ subject { R10K::ModuleLoader::Puppetfile.new(basedir: '/test/basedir', environment: env) }
187
+
188
+ it "includes environment's desired_contents" do
189
+ expect(subject.send(:determine_purge_exclusions, managed_dirs)).to match_array(managed_dirs + env_contents)
190
+ end
191
+ end
192
+ end
193
+
194
+ describe '#managed_directories' do
195
+
196
+ let(:basedir) { '/test/basedir' }
197
+ subject { R10K::ModuleLoader::Puppetfile.new(basedir: basedir) }
198
+
199
+ before do
200
+ allow(subject).to receive(:puppetfile_content).and_return('')
201
+ end
202
+
203
+ it 'returns an array of paths that #purge! will operate within' do
204
+ expect(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, hash_including(version: '1.2.3'), anything).and_call_original
205
+ subject.add_module('puppet/test_module', '1.2.3')
206
+ subject.load
207
+
208
+ expect(subject.modules.length).to be 1
209
+ expect(subject.managed_directories).to match_array([subject.moduledir])
210
+ end
211
+
212
+ context "with a module with install_path == ''" do
213
+ it "basedir isn't in the list of paths to purge" do
214
+ module_opts = { install_path: '', git: 'git@example.com:puppet/test_module.git' }
215
+
216
+ expect(R10K::Module).to receive(:new).with('puppet/test_module', basedir, module_opts, anything).and_call_original
217
+ subject.add_module('puppet/test_module', module_opts)
218
+ subject.load
219
+
220
+ expect(subject.modules.length).to be 1
221
+ expect(subject.managed_directories).to be_empty
222
+ end
223
+ end
224
+ end
225
+
226
+ describe 'evaluating a Puppetfile' do
227
+ def expect_wrapped_error(error, pf_path, error_type)
228
+ expect(error).to be_a_kind_of(R10K::Error)
229
+ expect(error.message).to eq("Failed to evaluate #{pf_path}")
230
+ expect(error.original).to be_a_kind_of(error_type)
231
+ end
232
+
233
+ subject { described_class.new(basedir: @path) }
234
+
235
+ it 'wraps and re-raises syntax errors' do
236
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'invalid-syntax')
237
+ pf_path = File.join(@path, 'Puppetfile')
238
+ expect {
239
+ subject.load
240
+ }.to raise_error do |e|
241
+ expect_wrapped_error(e, pf_path, SyntaxError)
242
+ end
243
+ end
244
+
245
+ it 'wraps and re-raises load errors' do
246
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'load-error')
247
+ pf_path = File.join(@path, 'Puppetfile')
248
+ expect {
249
+ subject.load
250
+ }.to raise_error do |e|
251
+ expect_wrapped_error(e, pf_path, LoadError)
252
+ end
253
+ end
254
+
255
+ it 'wraps and re-raises argument errors' do
256
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'argument-error')
257
+ pf_path = File.join(@path, 'Puppetfile')
258
+ expect {
259
+ subject.load
260
+ }.to raise_error do |e|
261
+ expect_wrapped_error(e, pf_path, ArgumentError)
262
+ end
263
+ end
264
+
265
+ it 'rejects Puppetfiles with duplicate module names' do
266
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'duplicate-module-error')
267
+ pf_path = File.join(@path, 'Puppetfile')
268
+ expect {
269
+ subject.load
270
+ }.to raise_error(R10K::Error, /Puppetfiles cannot contain duplicate module names/i)
271
+ end
272
+
273
+ it 'wraps and re-raises name errors' do
274
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'name-error')
275
+ pf_path = File.join(@path, 'Puppetfile')
276
+ expect {
277
+ subject.load
278
+ }.to raise_error do |e|
279
+ expect_wrapped_error(e, pf_path, NameError)
280
+ end
281
+ end
282
+
283
+ it 'accepts a forge module with a version' do
284
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-with-version')
285
+ pf_path = File.join(@path, 'Puppetfile')
286
+ expect { subject.load }.not_to raise_error
287
+ end
288
+
289
+ describe 'setting a custom moduledir' do
290
+ it 'allows setting an absolute moduledir' do
291
+ @path = '/fake/basedir'
292
+ allow(subject).to receive(:puppetfile_content).and_return('moduledir "/fake/moduledir"')
293
+ subject.load
294
+ expect(subject.instance_variable_get(:@moduledir)).to eq('/fake/moduledir')
295
+ end
296
+
297
+ it 'roots relative moduledirs in the basedir' do
298
+ @path = '/fake/basedir'
299
+ allow(subject).to receive(:puppetfile_content).and_return('moduledir "my/moduledir"')
300
+ subject.load
301
+ expect(subject.instance_variable_get(:@moduledir)).to eq(File.join(@path, 'my/moduledir'))
302
+ end
303
+ end
304
+
305
+ it 'accepts a forge module without a version' do
306
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'valid-forge-without-version')
307
+ pf_path = File.join(@path, 'Puppetfile')
308
+ expect { subject.load }.not_to raise_error
309
+ end
310
+
311
+ it 'creates a git module and applies the default branch specified in the Puppetfile' do
312
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'default-branch-override')
313
+ pf_path = File.join(@path, 'Puppetfile')
314
+ expect { subject.load }.not_to raise_error
315
+ git_module = subject.modules[0]
316
+ expect(git_module.default_ref).to eq 'here_lies_the_default_branch'
317
+ end
318
+
319
+ it 'creates a git module and applies the provided default_branch_override' do
320
+ @path = File.join(PROJECT_ROOT, 'spec', 'fixtures', 'unit', 'puppetfile', 'default-branch-override')
321
+ pf_path = File.join(@path, 'Puppetfile')
322
+ default_branch_override = 'default_branch_override_name'
323
+ subject.default_branch_override = default_branch_override
324
+ expect { subject.load }.not_to raise_error
325
+ git_module = subject.modules[0]
326
+ expect(git_module.default_override_ref).to eq default_branch_override
327
+ expect(git_module.default_ref).to eq 'here_lies_the_default_branch'
328
+ end
329
+ end
330
+ end