r10k 3.9.0 → 3.10.0

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