r10k 3.4.0 → 3.6.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +4 -0
  3. data/.github/workflows/docker.yml +25 -1
  4. data/.github/workflows/release.yml +36 -0
  5. data/.travis.yml +21 -8
  6. data/CHANGELOG.mkd +64 -4
  7. data/CODEOWNERS +1 -1
  8. data/Gemfile +1 -1
  9. data/README.mkd +13 -4
  10. data/azure-pipelines.yml +4 -2
  11. data/doc/dynamic-environments/configuration.mkd +41 -4
  12. data/doc/dynamic-environments/git-environments.mkd +1 -1
  13. data/doc/dynamic-environments/master-configuration.mkd +28 -58
  14. data/doc/faq.mkd +6 -1
  15. data/doc/puppetfile.mkd +2 -0
  16. data/docker/Makefile +19 -3
  17. data/docker/r10k/Dockerfile +22 -8
  18. data/docker/r10k/release.Dockerfile +23 -4
  19. data/integration/Rakefile +2 -2
  20. data/integration/tests/git_source/git_source_repeated_remote.rb +68 -0
  21. data/lib/r10k/action/deploy/environment.rb +5 -1
  22. data/lib/r10k/action/deploy/module.rb +5 -1
  23. data/lib/r10k/action/runner.rb +4 -4
  24. data/lib/r10k/cli/deploy.rb +1 -1
  25. data/lib/r10k/forge/module_release.rb +2 -2
  26. data/lib/r10k/git/cache.rb +1 -3
  27. data/lib/r10k/git/stateful_repository.rb +4 -0
  28. data/lib/r10k/module/base.rb +8 -0
  29. data/lib/r10k/module/git.rb +4 -0
  30. data/lib/r10k/puppetfile.rb +26 -6
  31. data/lib/r10k/settings.rb +1 -1
  32. data/lib/r10k/source.rb +1 -0
  33. data/lib/r10k/source/exec.rb +51 -0
  34. data/lib/r10k/source/git.rb +22 -2
  35. data/lib/r10k/source/hash.rb +32 -8
  36. data/lib/r10k/version.rb +4 -1
  37. data/locales/r10k.pot +33 -10
  38. data/r10k.gemspec +5 -1
  39. data/spec/unit/action/deploy/module_spec.rb +15 -2
  40. data/spec/unit/action/puppetfile/install_spec.rb +4 -1
  41. data/spec/unit/action/runner_spec.rb +2 -2
  42. data/spec/unit/forge/module_release_spec.rb +14 -10
  43. data/spec/unit/puppetfile_spec.rb +67 -2
  44. data/spec/unit/source/exec_spec.rb +81 -0
  45. data/spec/unit/source/git_spec.rb +37 -1
  46. data/spec/unit/source/hash_spec.rb +54 -0
  47. data/spec/unit/source/yaml_spec.rb +42 -0
  48. metadata +54 -16
  49. data/integration/scripts/README.mkd +0 -86
  50. data/integration/scripts/setup_r10k_env_centos5.sh +0 -23
  51. data/integration/scripts/setup_r10k_env_centos6.sh +0 -23
  52. data/integration/scripts/setup_r10k_env_rhel7.sh +0 -23
  53. data/integration/scripts/setup_r10k_env_sles11.sh +0 -23
  54. data/integration/scripts/setup_r10k_env_sles12.sh +0 -23
  55. data/integration/scripts/setup_r10k_env_ubuntu1004.sh +0 -23
  56. data/integration/scripts/setup_r10k_env_ubuntu1204.sh +0 -23
  57. data/integration/scripts/setup_r10k_env_ubuntu1404.sh +0 -23
@@ -160,7 +160,7 @@ module R10K
160
160
 
161
161
  Definition.new(:pool_size, {
162
162
  :desc => "The amount of threads used to concurrently install modules. The default value is 1: install one module at a time.",
163
- :default => 1,
163
+ :default => 4,
164
164
  :validate => lambda do |value|
165
165
  if !value.is_a?(Integer)
166
166
  raise ArgumentError, "The pool_size setting should be an integer, not a #{value.class}"
@@ -37,5 +37,6 @@ module R10K
37
37
  require 'r10k/source/svn'
38
38
  require 'r10k/source/yaml'
39
39
  require 'r10k/source/yamldir'
40
+ require 'r10k/source/exec'
40
41
  end
41
42
  end
@@ -0,0 +1,51 @@
1
+ require 'r10k/util/subprocess'
2
+ require 'json'
3
+ require 'yaml'
4
+
5
+ class R10K::Source::Exec < R10K::Source::Hash
6
+ R10K::Source.register(:exec, self)
7
+
8
+ def initialize(name, basedir, options = {})
9
+ unless @command = options[:command]
10
+ raise ConfigError, _('Environment source %{name} missing required parameter: command') % {name: name}
11
+ end
12
+
13
+ # We haven't set the environments option yet. We will do that by
14
+ # overloading the #environments method.
15
+ super(name, basedir, options)
16
+ end
17
+
18
+ def environments_hash
19
+ @environments_hash ||= set_environments_hash(run_environments_command)
20
+ end
21
+
22
+ private
23
+
24
+ def run_environments_command
25
+ subproc = R10K::Util::Subprocess.new([@command])
26
+ subproc.raise_on_fail = true
27
+ subproc.logger = self.logger
28
+ procresult = subproc.execute
29
+
30
+ begin
31
+ environments = JSON.parse(procresult.stdout)
32
+ rescue JSON::ParserError => json_err
33
+ begin
34
+ environments = YAML.safe_load(procresult.stdout)
35
+ rescue Psych::SyntaxError => yaml_err
36
+ raise R10K::Error, _("Error parsing command output for exec source %{name}:\n" \
37
+ "Not valid JSON: %{j_msg}\n" \
38
+ "Not valid YAML: %{y_msg}\n" \
39
+ "Stdout:\n%{out}") % {name: name, j_msg: json_err.message, y_msg: yaml_err.message, out: procresult.stdout}
40
+ end
41
+ end
42
+
43
+ unless R10K::Source::Hash.valid_environments_hash?(environments)
44
+ raise R10K::Error, _("Environment source %{name} command %{cmd} did not return valid environment data.\n" \
45
+ 'Returned: %{data}') % {name: name, cmd: @command, data: environments}
46
+ end
47
+
48
+ # Return the resulting environments hash
49
+ environments
50
+ end
51
+ end
@@ -41,6 +41,10 @@ class R10K::Source::Git < R10K::Source::Base
41
41
  # that will be deployed as environments.
42
42
  attr_reader :ignore_branch_prefixes
43
43
 
44
+ # @!attribute [r] filter_command
45
+ # @return [String] Command to run to filter branches
46
+ attr_reader :filter_command
47
+
44
48
  # Initialize the given source.
45
49
  #
46
50
  # @param name [String] The identifier for this source.
@@ -61,6 +65,7 @@ class R10K::Source::Git < R10K::Source::Base
61
65
  @remote = options[:remote]
62
66
  @invalid_branches = (options[:invalid_branches] || 'correct_and_warn')
63
67
  @ignore_branch_prefixes = options[:ignore_branch_prefixes]
68
+ @filter_command = options[:filter_command]
64
69
 
65
70
  @cache = R10K::Git.cache.generate(@remote)
66
71
  end
@@ -115,7 +120,7 @@ class R10K::Source::Git < R10K::Source::Base
115
120
  environments.map {|env| env.dirname }
116
121
  end
117
122
 
118
- def filter_branches(branches, ignore_prefixes)
123
+ def filter_branches_by_regexp(branches, ignore_prefixes)
119
124
  filter = Regexp.new("^#{Regexp.union(ignore_prefixes)}")
120
125
  branches = branches.reject do |branch|
121
126
  result = filter.match(branch)
@@ -127,14 +132,29 @@ class R10K::Source::Git < R10K::Source::Base
127
132
  branches
128
133
  end
129
134
 
135
+ def filter_branches_by_command(branches, command)
136
+ branches.select do |branch|
137
+ result = system({'GIT_DIR' => @cache.git_dir.to_s, 'R10K_BRANCH' => branch, 'R10K_NAME' => @name.to_s}, command)
138
+ unless result
139
+ logger.warn _("Branch `%{name}:%{branch}` filtered out by filter_command %{cmd}") % {name: @name, branch: branch, cmd: command}
140
+ end
141
+ result
142
+ end
143
+ end
144
+
130
145
  private
131
146
 
132
147
  def branch_names
133
148
  opts = {:prefix => @prefix, :invalid => @invalid_branches, :source => @name}
134
149
  branches = @cache.branches
135
150
  if @ignore_branch_prefixes && !@ignore_branch_prefixes.empty?
136
- branches = filter_branches(branches, @ignore_branch_prefixes)
151
+ branches = filter_branches_by_regexp(branches, @ignore_branch_prefixes)
152
+ end
153
+
154
+ if @filter_command && !@filter_command.empty?
155
+ branches = filter_branches_by_command(branches, @filter_command)
137
156
  end
157
+
138
158
  branches.map do |branch|
139
159
  R10K::Environment::Name.new(branch, opts)
140
160
  end
@@ -120,6 +120,16 @@
120
120
  #
121
121
  class R10K::Source::Hash < R10K::Source::Base
122
122
 
123
+ include R10K::Logging
124
+
125
+ # @param hash [Hash] A hash to validate.
126
+ # @return [Boolean] False if the hash is obviously invalid. A true return
127
+ # means _maybe_ it's valid.
128
+ def self.valid_environments_hash?(hash)
129
+ # TODO: more robust schema valiation
130
+ hash.is_a?(Hash)
131
+ end
132
+
123
133
  # @param name [String] The identifier for this source.
124
134
  # @param basedir [String] The base directory where the generated environments will be created.
125
135
  # @param options [Hash] An additional set of options for this source. The
@@ -131,19 +141,33 @@ class R10K::Source::Hash < R10K::Source::Base
131
141
  # @option options [Hash] :environments The hash definition of environments
132
142
  def initialize(name, basedir, options = {})
133
143
  super(name, basedir, options)
144
+ end
134
145
 
135
- @environments_hash = options.delete(:environments) || {}
136
-
137
- @environments_hash.keys.each do |name|
138
- # TODO: deal with names that aren't valid
139
- ::R10K::Util::SymbolizeKeys.symbolize_keys!(@environments_hash[name])
140
- @environments_hash[name][:basedir] = basedir
141
- @environments_hash[name][:dirname] = name
146
+ # Set the environment hash for the source. The environment hash is what the
147
+ # source uses to generate enviroments.
148
+ # @param hash [Hash] The hash to sanitize and use as the source's environments.
149
+ # Should be formatted for use with R10K::Environment#from_hash.
150
+ def set_environments_hash(hash)
151
+ @environments_hash = hash.reduce({}) do |memo,(name,opts)|
152
+ R10K::Util::SymbolizeKeys.symbolize_keys!(opts)
153
+ memo.merge({
154
+ name => opts.merge({
155
+ :basedir => @basedir,
156
+ :dirname => R10K::Environment::Name.new(name, {prefix: @prefix, source: @name}).dirname
157
+ })
158
+ })
142
159
  end
143
160
  end
144
161
 
162
+ # Return the sanitized environments hash for this source. The environments
163
+ # hash should contain objects formatted for use with R10K::Environment#from_hash.
164
+ # If the hash does not exist it will be built based on @options.
165
+ def environments_hash
166
+ @environments_hash ||= set_environments_hash(@options.fetch(:environments, {}))
167
+ end
168
+
145
169
  def environments
146
- @environments ||= @environments_hash.map do |name, hash|
170
+ @environments ||= environments_hash.map do |name, hash|
147
171
  R10K::Environment.from_hash(name, hash)
148
172
  end
149
173
  end
@@ -1,3 +1,6 @@
1
1
  module R10K
2
- VERSION = '3.4.0'
2
+ # When updating to a new major (X) or minor (Y) version, include `#major` or
3
+ # `#minor` (respectively) in your commit message to trigger the appropriate
4
+ # release. Otherwise, a new patch (Z) version will be released.
5
+ VERSION = '3.6.0'
3
6
  end
@@ -1,16 +1,16 @@
1
1
  # SOME DESCRIPTIVE TITLE.
2
- # Copyright (C) 2019 Puppet, Inc.
2
+ # Copyright (C) 2020 Puppet, Inc.
3
3
  # This file is distributed under the same license as the r10k package.
4
- # FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, 2020.
5
5
  #
6
6
  #, fuzzy
7
7
  msgid ""
8
8
  msgstr ""
9
- "Project-Id-Version: r10k 3.3.3-22-g3035320\n"
9
+ "Project-Id-Version: r10k 3.4.1-57-g2eb088a\n"
10
10
  "\n"
11
11
  "Report-Msgid-Bugs-To: docs@puppetlabs.com\n"
12
- "POT-Creation-Date: 2019-12-17 14:55+0000\n"
13
- "PO-Revision-Date: 2019-12-17 14:55+0000\n"
12
+ "POT-Creation-Date: 2020-07-22 16:41+0000\n"
13
+ "PO-Revision-Date: 2020-07-22 16:41+0000\n"
14
14
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
15
15
  "Language-Team: LANGUAGE <LL@li.org>\n"
16
16
  "Language: \n"
@@ -423,26 +423,49 @@ msgstr ""
423
423
  msgid "Setting %{name} requires a URL but '%{value}' could not be parsed as a URL"
424
424
  msgstr ""
425
425
 
426
- #: ../lib/r10k/source/git.rb:72
426
+ #: ../lib/r10k/source/exec.rb:10
427
+ msgid "Environment source %{name} missing required parameter: command"
428
+ msgstr ""
429
+
430
+ #: ../lib/r10k/source/exec.rb:36
431
+ msgid ""
432
+ "Error parsing command output for exec source %{name}:\n"
433
+ "Not valid JSON: %{j_msg}\n"
434
+ "Not valid YAML: %{y_msg}\n"
435
+ "Stdout:\n"
436
+ "%{out}"
437
+ msgstr ""
438
+
439
+ #: ../lib/r10k/source/exec.rb:44
440
+ msgid ""
441
+ "Environment source %{name} command %{cmd} did not return valid environment data.\n"
442
+ "Returned: %{data}"
443
+ msgstr ""
444
+
445
+ #: ../lib/r10k/source/git.rb:77
427
446
  msgid "Fetching '%{remote}' to determine current branches."
428
447
  msgstr ""
429
448
 
430
- #: ../lib/r10k/source/git.rb:75
449
+ #: ../lib/r10k/source/git.rb:80
431
450
  msgid "Unable to determine current branches for Git source '%{name}' (%{basedir})"
432
451
  msgstr ""
433
452
 
434
- #: ../lib/r10k/source/git.rb:100
453
+ #: ../lib/r10k/source/git.rb:105
435
454
  msgid "Environment %{env_name} contained non-word characters, correcting name to %{corrected_env_name}"
436
455
  msgstr ""
437
456
 
438
- #: ../lib/r10k/source/git.rb:104
457
+ #: ../lib/r10k/source/git.rb:109
439
458
  msgid "Environment %{env_name} contained non-word characters, ignoring it."
440
459
  msgstr ""
441
460
 
442
- #: ../lib/r10k/source/git.rb:123 ../lib/r10k/source/svn.rb:113
461
+ #: ../lib/r10k/source/git.rb:128 ../lib/r10k/source/svn.rb:113
443
462
  msgid "Branch %{branch} filtered out by ignore_branch_prefixes %{ibp}"
444
463
  msgstr ""
445
464
 
465
+ #: ../lib/r10k/source/git.rb:139
466
+ msgid "Branch `%{name}:%{branch}` filtered out by filter_command %{cmd}"
467
+ msgstr ""
468
+
446
469
  #: ../lib/r10k/source/yaml.rb:10
447
470
  msgid "Couldn't open environments file %{file}: %{err}"
448
471
  msgstr ""
@@ -23,7 +23,7 @@ Gem::Specification.new do |s|
23
23
  s.license = 'Apache-2.0'
24
24
 
25
25
  s.add_dependency 'colored2', '3.1.2'
26
- s.add_dependency 'cri', '2.15.6'
26
+ s.add_dependency 'cri', ['>= 2.15.10', '< 3.0.0']
27
27
 
28
28
  s.add_dependency 'log4r', '1.1.10'
29
29
  s.add_dependency 'multi_json', '~> 1.10'
@@ -31,6 +31,10 @@ Gem::Specification.new do |s|
31
31
  s.add_dependency 'puppet_forge', '~> 2.3.0'
32
32
 
33
33
  s.add_dependency 'gettext-setup', '~>0.24'
34
+ # These two pins narrow what is allowed by gettext-setup,
35
+ # to preserver compatability with Ruby 2.4
36
+ s.add_dependency 'fast_gettext', '~> 1.1.0'
37
+ s.add_dependency 'gettext', ['>= 3.0.2', '< 3.3.0']
34
38
 
35
39
  s.add_development_dependency 'rspec', '~> 3.1'
36
40
 
@@ -25,6 +25,10 @@ describe R10K::Action::Deploy::Module do
25
25
  it 'can accept a puppet-path option' do
26
26
  described_class.new({ 'puppet-path': '/nonexistent' }, [])
27
27
  end
28
+
29
+ it 'can accept a cachedir option' do
30
+ described_class.new({ cachedir: '/nonexistent' }, [])
31
+ end
28
32
  end
29
33
 
30
34
  describe "with no-force" do
@@ -66,8 +70,8 @@ describe R10K::Action::Deploy::Module do
66
70
 
67
71
  before do
68
72
  allow(subject).to receive(:visit_environment).and_wrap_original do |original, environment, &block|
69
- expect(environment.puppetfile).to receive(:modules).and_return(
70
- [R10K::Module::Local.new(environment.name, '/fakedir', [], environment)]
73
+ expect(environment.puppetfile).to receive(:modules_by_vcs_cachedir).and_return(
74
+ {none: [R10K::Module::Local.new(environment.name, '/fakedir', [], environment)]}
71
75
  )
72
76
  original.call(environment, &block)
73
77
  end
@@ -123,4 +127,13 @@ describe R10K::Action::Deploy::Module do
123
127
  expect(subject.instance_variable_get(:@puppet_path)).to eq('/nonexistent')
124
128
  end
125
129
  end
130
+
131
+ describe 'with cachedir' do
132
+
133
+ subject { described_class.new({ config: '/some/nonexistent/path', cachedir: '/nonexistent' }, []) }
134
+
135
+ it 'sets puppet_path' do
136
+ expect(subject.instance_variable_get(:@cachedir)).to eq('/nonexistent')
137
+ end
138
+ end
126
139
  end
@@ -19,12 +19,15 @@ describe R10K::Action::Puppetfile::Install do
19
19
 
20
20
  describe "installing modules" do
21
21
  let(:modules) do
22
- Array.new(4, R10K::Module::Base.new('author/modname', "/some/nonexistent/path/modname", nil))
22
+ (1..4).map do |idx|
23
+ R10K::Module::Base.new("author/modname#{idx}", "/some/nonexistent/path/modname#{idx}", nil)
24
+ end
23
25
  end
24
26
 
25
27
  before do
26
28
  allow(puppetfile).to receive(:purge!)
27
29
  allow(puppetfile).to receive(:modules).and_return(modules)
30
+ allow(puppetfile).to receive(:modules_by_vcs_cachedir).and_return({none: modules})
28
31
  end
29
32
 
30
33
  it "syncs each module in the Puppetfile" do
@@ -94,7 +94,7 @@ describe R10K::Action::Runner do
94
94
  else
95
95
  { "#{conf_path}": override }
96
96
  end
97
- expect(global_settings).to receive(:evaluate).with(overrides).and_call_original
97
+ expect(global_settings).to receive(:evaluate).with(hash_including(overrides)).and_call_original
98
98
  runner.call
99
99
  end
100
100
  end
@@ -109,7 +109,7 @@ describe R10K::Action::Runner do
109
109
  else
110
110
  { "#{conf_path}": override }
111
111
  end
112
- expect(global_settings).to receive(:evaluate).with(overrides).and_call_original
112
+ expect(global_settings).to receive(:evaluate).with(hash_including(overrides)).and_call_original
113
113
  runner.call
114
114
  end
115
115
  end
@@ -166,33 +166,37 @@ describe R10K::Forge::ModuleRelease do
166
166
  end
167
167
 
168
168
  describe "#cleanup_unpack_path" do
169
- it "ignores the unpack_path if it doesn't exist" do
170
- expect(unpack_path).to receive(:exist?).and_return false
171
- expect(unpack_path).to_not receive(:parent)
169
+ it "ignores the unpack_path if the parent doesn't exist" do
170
+ parent = instance_double('Pathname')
171
+ expect(parent).to receive(:exist?).and_return false
172
+ expect(parent).to_not receive(:rmtree)
173
+ expect(unpack_path).to receive(:parent).and_return(parent)
172
174
  subject.cleanup_unpack_path
173
175
  end
174
176
 
175
177
  it "removes the containing directory of unpack_path if it exists" do
176
178
  parent = instance_double('Pathname')
177
179
  expect(parent).to receive(:rmtree)
178
- expect(unpack_path).to receive(:exist?).and_return true
179
- expect(unpack_path).to receive(:parent).and_return(parent)
180
+ expect(parent).to receive(:exist?).and_return true
181
+ expect(unpack_path).to receive(:parent).and_return(parent).exactly(2).times
180
182
  subject.cleanup_unpack_path
181
183
  end
182
184
  end
183
185
 
184
186
  describe "#cleanup_download_path" do
185
- it "ignores the download_path if it doesn't exist" do
186
- expect(download_path).to receive(:exist?).and_return false
187
- expect(download_path).to_not receive(:parent)
187
+ it "ignores the download_path if the parent doesn't exist" do
188
+ parent = instance_double('Pathname')
189
+ expect(parent).to receive(:exist?).and_return false
190
+ expect(parent).to_not receive(:rmtree)
191
+ expect(download_path).to receive(:parent).and_return(parent)
188
192
  subject.cleanup_download_path
189
193
  end
190
194
 
191
195
  it "removes the containing directory of download_path if it exists" do
192
196
  parent = instance_double('Pathname')
193
197
  expect(parent).to receive(:rmtree)
194
- expect(download_path).to receive(:exist?).and_return true
195
- expect(download_path).to receive(:parent).and_return(parent)
198
+ expect(parent).to receive(:exist?).and_return true
199
+ expect(download_path).to receive(:parent).and_return(parent).exactly(2).times
196
200
  subject.cleanup_download_path
197
201
  end
198
202
  end
@@ -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