r10k 3.4.1 → 3.7.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +4 -1
  3. data/.github/workflows/docker.yml +25 -1
  4. data/.github/workflows/rspec_tests.yml +81 -0
  5. data/.travis.yml +19 -8
  6. data/CHANGELOG.mkd +46 -6
  7. data/Gemfile +1 -1
  8. data/README.mkd +13 -4
  9. data/azure-pipelines.yml +4 -2
  10. data/doc/dynamic-environments/configuration.mkd +49 -3
  11. data/doc/faq.mkd +6 -1
  12. data/doc/puppetfile.mkd +2 -0
  13. data/docker/Makefile +19 -3
  14. data/docker/r10k/Dockerfile +22 -8
  15. data/docker/r10k/release.Dockerfile +23 -4
  16. data/integration/tests/git_source/git_source_repeated_remote.rb +68 -0
  17. data/integration/tests/user_scenario/complex_workflow/multi_env_add_change_remove.rb +1 -1
  18. data/integration/tests/user_scenario/complex_workflow/multi_env_remove_re-add.rb +1 -1
  19. data/integration/tests/user_scenario/complex_workflow/multi_env_unamanaged.rb +1 -1
  20. data/lib/r10k/action/deploy/environment.rb +6 -1
  21. data/lib/r10k/action/deploy/module.rb +2 -1
  22. data/lib/r10k/action/runner.rb +5 -4
  23. data/lib/r10k/cli/deploy.rb +1 -0
  24. data/lib/r10k/environment/base.rb +1 -1
  25. data/lib/r10k/forge/module_release.rb +2 -2
  26. data/lib/r10k/git/cache.rb +12 -4
  27. data/lib/r10k/git/stateful_repository.rb +4 -0
  28. data/lib/r10k/initializers.rb +1 -0
  29. data/lib/r10k/module/base.rb +8 -0
  30. data/lib/r10k/module/git.rb +4 -0
  31. data/lib/r10k/puppetfile.rb +26 -6
  32. data/lib/r10k/settings.rb +12 -1
  33. data/lib/r10k/source.rb +1 -0
  34. data/lib/r10k/source/exec.rb +51 -0
  35. data/lib/r10k/source/git.rb +22 -2
  36. data/lib/r10k/source/hash.rb +32 -8
  37. data/lib/r10k/version.rb +1 -1
  38. data/locales/r10k.pot +33 -10
  39. data/spec/shared-examples/subprocess-runner.rb +11 -5
  40. data/spec/unit/action/deploy/environment_spec.rb +9 -0
  41. data/spec/unit/action/deploy/module_spec.rb +15 -2
  42. data/spec/unit/action/puppetfile/install_spec.rb +4 -1
  43. data/spec/unit/action/runner_spec.rb +2 -2
  44. data/spec/unit/forge/module_release_spec.rb +14 -10
  45. data/spec/unit/git/cache_spec.rb +10 -0
  46. data/spec/unit/git/rugged/credentials_spec.rb +1 -1
  47. data/spec/unit/git_spec.rb +3 -3
  48. data/spec/unit/puppetfile_spec.rb +67 -2
  49. data/spec/unit/settings_spec.rb +12 -0
  50. data/spec/unit/source/exec_spec.rb +81 -0
  51. data/spec/unit/source/git_spec.rb +49 -1
  52. data/spec/unit/source/hash_spec.rb +54 -0
  53. data/spec/unit/source/yaml_spec.rb +42 -0
  54. metadata +8 -2
@@ -3,6 +3,16 @@ require 'r10k/git/cache'
3
3
 
4
4
  describe R10K::Git::Cache do
5
5
 
6
+ describe 'the default cache_root' do
7
+ it 'is in the right location in linux', unless: R10K::Util::Platform.windows? do
8
+ expect(described_class.defaults[:cache_root]).to match(/\.r10k\/git/)
9
+ end
10
+
11
+ it 'is in the right location for windows', if: R10K::Util::Platform.windows? do
12
+ expect(described_class.defaults[:cache_root]).to match(/[^.]r10k\/git/)
13
+ end
14
+ end
15
+
6
16
  let(:subclass) do
7
17
  Class.new(described_class) do
8
18
  def self.bare_repository
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe R10K::Git::Rugged::Credentials, :unless => R10K::Util::Platform.jruby? do
3
+ describe R10K::Git::Rugged::Credentials, :unless => R10K::Util::Platform.jruby? || R10K::Util::Platform.windows? do
4
4
  before(:all) do
5
5
  require 'r10k/git/rugged/credentials'
6
6
  require 'rugged/credentials'
@@ -11,7 +11,7 @@ describe R10K::Git do
11
11
  expect(described_class.default_name).to eq :shellgit
12
12
  end
13
13
 
14
- context 'under c-based rubies', :unless => R10K::Util::Platform.jruby? do
14
+ context 'under c-based rubies with rugged available', :unless => R10K::Util::Platform.jruby? || R10K::Util::Platform.windows? do
15
15
  it 'returns rugged when the git executable is absent and the rugged library is present' do
16
16
  expect(R10K::Features).to receive(:available?).with(:shellgit).and_return false
17
17
  expect(R10K::Features).to receive(:available?).with(:rugged).and_return true
@@ -53,7 +53,7 @@ describe R10K::Git do
53
53
  }.to raise_error(R10K::Error, "Git provider 'shellgit' is not functional.")
54
54
  end
55
55
 
56
- context 'under c-based rubies', :unless => R10K::Util::Platform.jruby? do
56
+ context 'under c-based rubies with rugged available', :unless => R10K::Util::Platform.jruby? || R10K::Util::Platform.windows? do
57
57
  it "sets the current provider if the provider exists and is functional" do
58
58
  expect(R10K::Features).to receive(:available?).with(:rugged).and_return true
59
59
  described_class.provider = :rugged
@@ -71,7 +71,7 @@ describe R10K::Git do
71
71
  end
72
72
 
73
73
  describe "retrieving the current provider" do
74
- context 'under c-based rubies', :unless => R10K::Util::Platform.jruby? do
74
+ context 'under c-based rubies', :unless => R10K::Util::Platform.jruby? || R10K::Util::Platform.windows? do
75
75
  it "uses the default if a provider has not been set" do
76
76
  expect(described_class).to receive(:default_name).and_return :rugged
77
77
  expect(described_class.provider).to eq(R10K::Git::Rugged)
@@ -127,6 +127,25 @@ describe R10K::Puppetfile do
127
127
 
128
128
  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 }
129
129
  end
130
+
131
+ it "groups modules by vcs cache location" do
132
+ module_opts = { install_path: File.join(subject.basedir, 'vendor') }
133
+ opts1 = module_opts.merge(git: 'git@example.com:puppet/test_module.git')
134
+ opts2 = module_opts.merge(git: 'git@example.com:puppet/test_module_c.git')
135
+ sanitized_name1 = "git@example.com-puppet-test_module.git"
136
+ sanitized_name2 = "git@example.com-puppet-test_module_c.git"
137
+
138
+ subject.add_module('puppet/test_module_a', opts1)
139
+ subject.add_module('puppet/test_module_b', opts1)
140
+ subject.add_module('puppet/test_module_c', opts2)
141
+ subject.add_module('puppet/test_module_d', '1.2.3')
142
+
143
+ mods_by_cachedir = subject.modules_by_vcs_cachedir
144
+
145
+ expect(mods_by_cachedir[:none].length).to be 1
146
+ expect(mods_by_cachedir[sanitized_name1].length).to be 2
147
+ expect(mods_by_cachedir[sanitized_name2].length).to be 1
148
+ end
130
149
  end
131
150
 
132
151
  describe "#purge_exclusions" do
@@ -154,6 +173,26 @@ describe R10K::Puppetfile do
154
173
  end
155
174
  end
156
175
 
176
+ describe '#managed_directories' do
177
+ it 'returns an array of paths that can be purged' do
178
+ allow(R10K::Module).to receive(:new).with('puppet/test_module', subject.moduledir, '1.2.3', anything).and_call_original
179
+
180
+ subject.add_module('puppet/test_module', '1.2.3')
181
+ expect(subject.managed_directories).to match_array(["/some/nonexistent/basedir/modules"])
182
+ end
183
+
184
+ context 'with a module with install_path == \'\'' do
185
+ it 'basedir isn\'t in the list of paths to purge' do
186
+ module_opts = { install_path: '', git: 'git@example.com:puppet/test_module.git' }
187
+
188
+ allow(R10K::Module).to receive(:new).with('puppet/test_module', subject.basedir, module_opts, anything).and_call_original
189
+
190
+ subject.add_module('puppet/test_module', module_opts)
191
+ expect(subject.managed_directories).to be_empty
192
+ end
193
+ end
194
+ end
195
+
157
196
  describe "evaluating a Puppetfile" do
158
197
  def expect_wrapped_error(orig, pf_path, wrapped_error)
159
198
  expect(orig).to be_a_kind_of(R10K::Error)
@@ -268,7 +307,7 @@ describe R10K::Puppetfile do
268
307
  mod2 = spy('module')
269
308
  expect(mod2).to receive(:accept).with(visitor)
270
309
 
271
- expect(subject).to receive(:modules).and_return([mod1, mod2])
310
+ expect(subject).to receive(:modules_by_vcs_cachedir).and_return({none: [mod1, mod2]})
272
311
  subject.accept(visitor)
273
312
  end
274
313
 
@@ -289,12 +328,38 @@ describe R10K::Puppetfile do
289
328
  mod2 = spy('module')
290
329
  expect(mod2).to receive(:accept).with(visitor)
291
330
 
292
- expect(subject).to receive(:modules).and_return([mod1, mod2])
331
+ expect(subject).to receive(:modules_by_vcs_cachedir).and_return({none: [mod1, mod2]})
293
332
 
294
333
  expect(Thread).to receive(:new).exactly(pool_size).and_call_original
295
334
  expect(Queue).to receive(:new).and_call_original
296
335
 
297
336
  subject.accept(visitor)
298
337
  end
338
+
339
+ it "Creates queues of modules grouped by cachedir" do
340
+ visitor = spy('visitor')
341
+ expect(visitor).to receive(:visit) do |type, other, &block|
342
+ expect(type).to eq :puppetfile
343
+ expect(other).to eq subject
344
+ block.call
345
+ end
346
+
347
+ mod1 = spy('module1')
348
+ mod2 = spy('module2')
349
+ mod3 = spy('module3')
350
+ mod4 = spy('module4')
351
+ mod5 = spy('module5')
352
+ mod6 = spy('module6')
353
+
354
+ expect(subject).to receive(:modules_by_vcs_cachedir)
355
+ .and_return({:none => [mod1, mod2],
356
+ "foo-cachedir" => [mod3, mod4],
357
+ "bar-cachedir" => [mod5, mod6]})
358
+
359
+ queue = subject.modules_queue(visitor)
360
+ expect(queue.length).to be 4
361
+ queue_array = 4.times.map { queue.pop }
362
+ expect(queue_array).to match_array([[mod1], [mod2], [mod3, mod4], [mod5, mod6]])
363
+ end
299
364
  end
300
365
  end
@@ -129,6 +129,18 @@ describe R10K::Settings do
129
129
  expect { subject.evaluate('puppet_path' => '/nonexistent') }.to raise_error(R10K::Settings::Collection::ValidationError)
130
130
  end
131
131
  end
132
+
133
+ describe 'puppet_conf' do
134
+ it 'when file raises no error' do
135
+ allow(File).to receive(:readable?).with('/nonexistent').and_return(true)
136
+ expect { subject.evaluate('puppet_conf' => '/nonexistent') }.not_to raise_error
137
+ end
138
+
139
+ it 'when not file raises error' do
140
+ allow(File).to receive(:readable?).with('/nonexistent').and_return(false)
141
+ expect { subject.evaluate('puppet_conf' => '/nonexistent') }.to raise_error(R10K::Settings::Collection::ValidationError)
142
+ end
143
+ end
132
144
  end
133
145
 
134
146
  describe "global settings" do
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+ require 'r10k/source'
3
+ require 'json'
4
+ require 'yaml'
5
+
6
+ describe R10K::Source::Exec do
7
+
8
+ let(:environments_hash) do
9
+ {
10
+ 'production' => {
11
+ 'remote' => 'https://git.example.com/puppet/control-repo.git',
12
+ 'ref' => 'release-141',
13
+ 'modules' => {
14
+ 'puppetlabs-stdlib' => '6.1.0',
15
+ 'puppetlabs-ntp' => '8.1.0',
16
+ 'example-myapp1' => {
17
+ 'git' => 'https://git.example.com/puppet/example-myapp1.git',
18
+ 'ref' => 'v1.3.0'
19
+ }
20
+ }
21
+ },
22
+ 'development' => {
23
+ 'remote' => 'https://git.example.com/puppet/control-repo.git',
24
+ 'ref' => 'master',
25
+ 'modules' => {
26
+ 'puppetlabs-stdlib' => '6.1.0',
27
+ 'puppetlabs-ntp' => '8.1.0',
28
+ 'example-myapp1' => {
29
+ 'git' => 'https://git.example.com/puppet/example-myapp1.git',
30
+ 'ref' => 'v1.3.1'
31
+ }
32
+ }
33
+ }
34
+ }
35
+ end
36
+
37
+ describe 'initialize' do
38
+ context 'with a valid command' do
39
+ context 'that produces valid output' do
40
+ it 'accepts json' do
41
+ allow_any_instance_of(R10K::Util::Subprocess)
42
+ .to receive(:execute)
43
+ .and_return(double('result', stdout: environments_hash.to_json))
44
+
45
+ source = described_class.new('execsource', '/some/nonexistent/dir', command: '/path/to/command')
46
+ expect(source.environments.map(&:name)).to contain_exactly('production', 'development')
47
+ end
48
+
49
+ it 'accepts yaml' do
50
+ allow_any_instance_of(R10K::Util::Subprocess)
51
+ .to receive(:execute)
52
+ .and_return(double('result', stdout: environments_hash.to_yaml))
53
+
54
+ source = described_class.new('execsource', '/some/nonexistent/dir', command: '/path/to/command')
55
+ expect(source.environments.map(&:name)).to contain_exactly('production', 'development')
56
+ end
57
+
58
+ end
59
+
60
+ context 'that produces invalid output' do
61
+ it 'raises an error for non-json, non-yaml data' do
62
+ allow_any_instance_of(R10K::Util::Subprocess)
63
+ .to receive(:execute)
64
+ .and_return(double('result', stdout: "one:\ntwo\n"))
65
+
66
+ source = described_class.new('execsource', '/some/nonexistent/dir', command: '/path/to/command')
67
+ expect { source.environments }.to raise_error(/Error parsing command output/)
68
+ end
69
+
70
+ it 'raises an error for yaml data that is not a hash' do
71
+ allow_any_instance_of(R10K::Util::Subprocess)
72
+ .to receive(:execute)
73
+ .and_return(double('result', stdout: "[one, two]"))
74
+
75
+ source = described_class.new('execsource', '/some/nonexistent/dir', command: '/path/to/command')
76
+ expect { source.environments }.to raise_error(R10K::Error, /Environment source execsource.*did not return valid environment data.*one.*two.*/m)
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -93,9 +93,57 @@ describe R10K::Source::Git do
93
93
  let(:ignore_prefixes) { ['dev', 'test'] }
94
94
 
95
95
  it "filters branches" do
96
- expect(subject.filter_branches(branches, ignore_prefixes)).to eq(['master', 'production', 'not_dev_test_me'])
96
+ expect(subject.filter_branches_by_regexp(branches, ignore_prefixes)).to eq(['master', 'production', 'not_dev_test_me'])
97
97
  end
98
98
  end
99
+
100
+ describe "filtering branches with command" do
101
+ let(:branches) { ['master', 'development', 'production'] }
102
+ if R10K::Util::Platform.windows?
103
+ let(:filter_command) { 'powershell.exe if ($env:R10K_BRANCH.equals(\"development\")) {exit 1} else {exit 0}' }
104
+ else
105
+ let(:filter_command) { 'sh -c "[ $R10K_BRANCH != development ]"' }
106
+ end
107
+
108
+ it "filters branches" do
109
+ expect(subject.filter_branches_by_command(branches, filter_command)).to eq(['master', 'production'])
110
+ end
111
+ end
112
+
113
+ describe "generate_environments respects filter_command setting" do
114
+ before do
115
+ allow(subject.cache).to receive(:branches).and_return ['master', 'development', 'production']
116
+ if R10K::Util::Platform.windows?
117
+ subject.instance_variable_set(:@filter_command, 'powershell.exe if ($env:R10K_BRANCH.equals(\"master\")) {exit 1} else {exit 0}')
118
+ else
119
+ subject.instance_variable_set(:@filter_command, '[ $R10K_BRANCH != master ]')
120
+ end
121
+ end
122
+
123
+ let(:environments) { subject.generate_environments }
124
+
125
+ it "creates an environment for each branch not filtered by filter_command" do
126
+ expect(subject.generate_environments.size).to eq(2)
127
+ end
128
+ end
129
+
130
+ describe "generate_environments respects filter_command setting and name" do
131
+ before do
132
+ allow(subject.cache).to receive(:branches).and_return ['master', 'development', 'production']
133
+ if R10K::Util::Platform.windows?
134
+ subject.instance_variable_set(:@filter_command, 'powershell.exe if ($env:R10K_NAME.equals(\"mysource\")) {exit 0} else {exit 1}')
135
+ else
136
+ subject.instance_variable_set(:@filter_command, '[ $R10K_NAME = mysource ]')
137
+ end
138
+ end
139
+
140
+ let(:environments) { subject.generate_environments }
141
+
142
+ it "creates an environment for each branch not filtered by filter_command" do
143
+ expect(subject.generate_environments.size).to eq(3)
144
+ end
145
+ end
146
+
99
147
  end
100
148
 
101
149
  describe R10K::Source::Git, "handling invalid branch names" do
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'r10k/source'
3
+
4
+ describe R10K::Source::Hash do
5
+
6
+ describe '.valid_environments_hash?' do
7
+ it "rejects strings" do
8
+ expect(R10K::Source::Hash.valid_environments_hash?('200 OK'))
9
+ .to eq false
10
+ end
11
+ end
12
+
13
+ let(:environments_hash) do
14
+ {
15
+ 'production' => {
16
+ 'remote' => 'https://git.example.com/puppet/control-repo.git',
17
+ 'ref' => 'release-141',
18
+ 'modules' => {
19
+ 'puppetlabs-stdlib' => '6.1.0',
20
+ 'puppetlabs-ntp' => '8.1.0',
21
+ 'example-myapp1' => {
22
+ 'git' => 'https://git.example.com/puppet/example-myapp1.git',
23
+ 'ref' => 'v1.3.0'
24
+ }
25
+ }
26
+ },
27
+ 'development' => {
28
+ 'remote' => 'https://git.example.com/puppet/control-repo.git',
29
+ 'ref' => 'master',
30
+ 'modules' => {
31
+ 'puppetlabs-stdlib' => '6.1.0',
32
+ 'puppetlabs-ntp' => '8.1.0',
33
+ 'example-myapp1' => {
34
+ 'git' => 'https://git.example.com/puppet/example-myapp1.git',
35
+ 'ref' => 'v1.3.1'
36
+ }
37
+ }
38
+ }
39
+ }
40
+ end
41
+
42
+ describe "with a prefix" do
43
+ subject do
44
+ described_class.new('hashsource', '/some/nonexistent/dir',
45
+ prefix: 'prefixed', environments: environments_hash)
46
+ end
47
+
48
+ it "prepends environment names with a prefix" do
49
+ environments = subject.environments
50
+ expect(environments[0].dirname).to eq 'prefixed_production'
51
+ expect(environments[1].dirname).to eq 'prefixed_development'
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+ require 'r10k/source'
3
+
4
+ describe R10K::Source::Yaml do
5
+
6
+ let(:environments_hash) do
7
+ {
8
+ 'production' => {
9
+ 'remote' => 'https://git.example.com/puppet/control-repo.git',
10
+ 'ref' => 'release-141',
11
+ 'modules' => {
12
+ 'puppetlabs-stdlib' => '6.1.0',
13
+ 'puppetlabs-ntp' => '8.1.0',
14
+ 'example-myapp1' => {
15
+ 'git' => 'https://git.example.com/puppet/example-myapp1.git',
16
+ 'ref' => 'v1.3.0'
17
+ }
18
+ }
19
+ },
20
+ 'development' => {
21
+ 'remote' => 'https://git.example.com/puppet/control-repo.git',
22
+ 'ref' => 'master',
23
+ 'modules' => {
24
+ 'puppetlabs-stdlib' => '6.1.0',
25
+ 'puppetlabs-ntp' => '8.1.0',
26
+ 'example-myapp1' => {
27
+ 'git' => 'https://git.example.com/puppet/example-myapp1.git',
28
+ 'ref' => 'v1.3.1'
29
+ }
30
+ }
31
+ }
32
+ }
33
+ end
34
+
35
+ describe "with valid yaml file" do
36
+ it "produces environments" do
37
+ allow(YAML).to receive(:load_file).with('/envs.yaml').and_return(environments_hash)
38
+ source = described_class.new('yamlsource', '/some/nonexistent/dir', config: '/envs.yaml')
39
+ expect(source.environments.map(&:name)).to contain_exactly('production', 'development')
40
+ end
41
+ end
42
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: r10k
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.1
4
+ version: 3.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrien Thebo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-06 00:00:00.000000000 Z
11
+ date: 2020-11-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colored2
@@ -204,6 +204,7 @@ files:
204
204
  - ".github/pull_request_template.md"
205
205
  - ".github/workflows/docker.yml"
206
206
  - ".github/workflows/release.yml"
207
+ - ".github/workflows/rspec_tests.yml"
207
208
  - ".gitignore"
208
209
  - ".travis.yml"
209
210
  - CHANGELOG.mkd
@@ -282,6 +283,7 @@ files:
282
283
  - integration/tests/command_line/negative/neg_invalid_cmd_line_arg.rb
283
284
  - integration/tests/git_source/HTTP_proxy_and_git_source.rb
284
285
  - integration/tests/git_source/git_source_git.rb
286
+ - integration/tests/git_source/git_source_repeated_remote.rb
285
287
  - integration/tests/git_source/git_source_ssh.rb
286
288
  - integration/tests/git_source/git_source_submodule.rb
287
289
  - integration/tests/git_source/negative/neg_git_broken_remote.rb
@@ -408,6 +410,7 @@ files:
408
410
  - lib/r10k/settings/uri_definition.rb
409
411
  - lib/r10k/source.rb
410
412
  - lib/r10k/source/base.rb
413
+ - lib/r10k/source/exec.rb
411
414
  - lib/r10k/source/git.rb
412
415
  - lib/r10k/source/hash.rb
413
416
  - lib/r10k/source/svn.rb
@@ -535,8 +538,11 @@ files:
535
538
  - spec/unit/settings/uri_definition_spec.rb
536
539
  - spec/unit/settings_spec.rb
537
540
  - spec/unit/source/base_spec.rb
541
+ - spec/unit/source/exec_spec.rb
538
542
  - spec/unit/source/git_spec.rb
543
+ - spec/unit/source/hash_spec.rb
539
544
  - spec/unit/source/svn_spec.rb
545
+ - spec/unit/source/yaml_spec.rb
540
546
  - spec/unit/source_spec.rb
541
547
  - spec/unit/svn/remote_spec.rb
542
548
  - spec/unit/svn/working_dir_spec.rb