r10k 3.4.1 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -1,17 +1,27 @@
1
1
  PUPPERWARE_ANALYTICS_STREAM ?= dev
2
2
  NAMESPACE ?= puppet
3
- git_describe = $(shell git describe)
3
+ git_describe = $(shell git describe --tags)
4
4
  vcs_ref := $(shell git rev-parse HEAD)
5
5
  build_date := $(shell date -u +%FT%T)
6
6
  hadolint_available := $(shell hadolint --help > /dev/null 2>&1; echo $$?)
7
- hadolint_command := hadolint --ignore DL3008 --ignore DL3018 --ignore DL3028 --ignore DL4000 --ignore DL4001
7
+ hadolint_command := hadolint
8
8
  hadolint_container := hadolint/hadolint:latest
9
+ alpine_version := 3.9
9
10
  export BUNDLE_PATH = $(PWD)/.bundle/gems
10
11
  export BUNDLE_BIN = $(PWD)/.bundle/bin
11
12
  export GEMFILE = $(PWD)/Gemfile
13
+ export DOCKER_BUILDKIT = 1
12
14
 
13
15
  ifeq ($(IS_RELEASE),true)
14
16
  VERSION ?= $(shell echo $(git_describe) | sed 's/-.*//')
17
+ PUBLISHED_VERSION ?= $(shell curl --silent 'https://rubygems.org/api/v1/gems/r10k.json' | jq '."version"' | tr -d '"')
18
+ CONTAINER_EXISTS = $(shell DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect $(NAMESPACE)/r10k:$(VERSION) > /dev/null 2>&1; echo $$?)
19
+ ifeq ($(CONTAINER_EXISTS),0)
20
+ SKIP_BUILD ?= true
21
+ else ifneq ($(VERSION),$(PUBLISHED_VERSION))
22
+ SKIP_BUILD ?= true
23
+ endif
24
+
15
25
  LATEST_VERSION ?= latest
16
26
  dockerfile := release.Dockerfile
17
27
  dockerfile_context := r10k
@@ -25,6 +35,10 @@ endif
25
35
  prep:
26
36
  @git fetch --unshallow 2> /dev/null ||:
27
37
  @git fetch origin 'refs/tags/*:refs/tags/*'
38
+ ifeq ($(SKIP_BUILD),true)
39
+ @echo "SKIP_BUILD is true, exiting with 1"
40
+ @exit 1
41
+ endif
28
42
 
29
43
  lint:
30
44
  ifeq ($(hadolint_available),0)
@@ -35,8 +49,10 @@ else
35
49
  endif
36
50
 
37
51
  build: prep
52
+ docker pull alpine:$(alpine_version)
38
53
  docker build \
39
- --pull \
54
+ ${DOCKER_BUILD_FLAGS} \
55
+ --build-arg alpine_version=$(alpine_version) \
40
56
  --build-arg vcs_ref=$(vcs_ref) \
41
57
  --build-arg build_date=$(build_date) \
42
58
  --build-arg version=$(VERSION) \
@@ -1,5 +1,7 @@
1
- FROM alpine:3.9 as build
1
+ ARG alpine_version=3.9
2
+ FROM alpine:${alpine_version} as build
2
3
 
4
+ # hadolint ignore=DL3018
3
5
  RUN apk add --no-cache ruby git && \
4
6
  mkdir /workspace
5
7
  WORKDIR /workspace
@@ -7,7 +9,7 @@ COPY . /workspace
7
9
  RUN gem build r10k.gemspec && \
8
10
  mv r10k*.gem r10k.gem
9
11
 
10
- FROM alpine:3.9
12
+ FROM alpine:${alpine_version}
11
13
 
12
14
  ARG vcs_ref
13
15
  ARG build_date
@@ -15,6 +17,11 @@ ARG version="3.1.0"
15
17
  # Used by entrypoint to submit metrics to Google Analytics.
16
18
  # Published images should use "production" for this build_arg.
17
19
  ARG pupperware_analytics_stream="dev"
20
+ # required to schedule runs of "r10k" in K8s
21
+ ARG supercronic_version="0.1.9"
22
+ ARG supercronic_sha1sum="5ddf8ea26b56d4a7ff6faecdd8966610d5cb9d85"
23
+ ARG supercronic="supercronic-linux-amd64"
24
+ ARG supercronic_url="https://github.com/aptible/supercronic/releases/download/v$supercronic_version/$supercronic"
18
25
 
19
26
  LABEL org.label-schema.maintainer="Puppet Release Team <release@puppet.com>" \
20
27
  org.label-schema.vendor="Puppet" \
@@ -31,7 +38,7 @@ COPY docker/r10k/docker-entrypoint.d /docker-entrypoint.d
31
38
  ENTRYPOINT ["/docker-entrypoint.sh"]
32
39
  CMD ["help"]
33
40
 
34
- # dyanmic LABELs and ENV vars placed lower for the sake of Docker layer caching
41
+ # dynamic LABELs and ENV vars placed lower for the sake of Docker layer caching
35
42
  ENV PUPPERWARE_ANALYTICS_STREAM="$pupperware_analytics_stream"
36
43
 
37
44
  LABEL org.label-schema.version="$version" \
@@ -39,14 +46,21 @@ LABEL org.label-schema.version="$version" \
39
46
  org.label-schema.build-date="$build_date"
40
47
 
41
48
  COPY --from=build /workspace/r10k.gem /
42
- RUN chmod a+x /adduser.sh && \
49
+ SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
50
+ # ignore apk and gem pinning
51
+ # hadolint ignore=DL3018,DL3028
52
+ RUN chmod a+x /adduser.sh /docker-entrypoint.sh && \
43
53
  # Add a puppet user to run r10k as for consistency with puppetserver
44
54
  /adduser.sh && \
45
- chmod +x /docker-entrypoint.sh && \
46
55
  chown -R puppet: /docker-entrypoint.d /docker-entrypoint.sh && \
47
- apk add --no-cache ruby openssh-client git ruby-rugged curl ruby-dev make gcc musl-dev && \
48
- gem install --no-doc /r10k.gem json etc && \
49
- rm -f /r10k.gem
56
+ apk add --no-cache ruby openssh-client git ruby-rugged curl ruby-json ruby-etc && \
57
+ gem install --no-doc /r10k.gem && \
58
+ rm -f /r10k.gem && \
59
+ curl --fail --silent --show-error --location --remote-name "$supercronic_url" && \
60
+ echo "${supercronic_sha1sum} ${supercronic}" | sha1sum -c - && \
61
+ chmod +x "$supercronic" && \
62
+ mv "$supercronic" "/usr/local/bin/${supercronic}" && \
63
+ ln -s "/usr/local/bin/${supercronic}" /usr/local/bin/supercronic
50
64
 
51
65
  USER puppet
52
66
  WORKDIR /home/puppet
@@ -1,4 +1,5 @@
1
- FROM alpine:3.9
1
+ ARG alpine_version=3.9
2
+ FROM alpine:${alpine_version}
2
3
 
3
4
  ARG vcs_ref
4
5
  ARG build_date
@@ -6,6 +7,11 @@ ARG version="3.1.0"
6
7
  # Used by entrypoint to submit metrics to Google Analytics.
7
8
  # Published images should use "production" for this build_arg.
8
9
  ARG pupperware_analytics_stream="dev"
10
+ # required to schedule runs of "r10k" in K8s
11
+ ARG supercronic_version="0.1.9"
12
+ ARG supercronic_sha1sum="5ddf8ea26b56d4a7ff6faecdd8966610d5cb9d85"
13
+ ARG supercronic="supercronic-linux-amd64"
14
+ ARG supercronic_url="https://github.com/aptible/supercronic/releases/download/v$supercronic_version/$supercronic"
9
15
 
10
16
  LABEL org.label-schema.maintainer="Puppet Release Team <release@puppet.com>" \
11
17
  org.label-schema.vendor="Puppet" \
@@ -16,7 +22,7 @@ LABEL org.label-schema.maintainer="Puppet Release Team <release@puppet.com>" \
16
22
  org.label-schema.schema-version="1.0" \
17
23
  org.label-schema.dockerfile="/release.Dockerfile"
18
24
 
19
- COPY docker-entrypoint.sh /
25
+ COPY adduser.sh docker-entrypoint.sh /
20
26
  COPY docker-entrypoint.d /docker-entrypoint.d
21
27
 
22
28
  ENTRYPOINT ["/docker-entrypoint.sh"]
@@ -29,8 +35,21 @@ LABEL org.label-schema.version="$version" \
29
35
  org.label-schema.vcs-ref="$vcs_ref" \
30
36
  org.label-schema.build-date="$build_date"
31
37
 
32
- RUN chmod +x /docker-entrypoint.sh && \
38
+ SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
39
+ # ignore apk and gem pinning
40
+ # hadolint ignore=DL3018,DL3028
41
+ RUN chmod a+x /adduser.sh /docker-entrypoint.sh && \
42
+ /adduser.sh && \
43
+ chown -R puppet: /docker-entrypoint.d /docker-entrypoint.sh && \
33
44
  apk add --no-cache ruby openssh-client git ruby-rugged curl ruby-dev make gcc musl-dev && \
34
- gem install --no-doc r10k:"$version" json etc
45
+ gem install --no-doc r10k:"$version" json etc && \
46
+ curl --fail --silent --show-error --location --remote-name "$supercronic_url" && \
47
+ echo "${supercronic_sha1sum} ${supercronic}" | sha1sum -c - && \
48
+ chmod +x "$supercronic" && \
49
+ mv "$supercronic" "/usr/local/bin/${supercronic}" && \
50
+ ln -s "/usr/local/bin/${supercronic}" /usr/local/bin/supercronic
51
+
52
+ USER puppet
53
+ WORKDIR /home/puppet
35
54
 
36
55
  COPY release.Dockerfile /
@@ -0,0 +1,68 @@
1
+ require 'git_utils'
2
+ require 'r10k_utils'
3
+ require 'master_manipulator'
4
+ test_name 'Verify the same remote can be used in more than one object'
5
+
6
+ env_path = on(master, puppet('config print environmentpath')).stdout.rstrip
7
+ r10k_fqp = get_r10k_fqp(master)
8
+ git_environments_path = '/root/environments'
9
+ git_repo_path = '/git_repos'
10
+ git_repo_name = 'environments'
11
+ git_control_remote = File.join(git_repo_path, "#{git_repo_name}.git")
12
+ code_dir = "#{env_path}/production"
13
+
14
+ last_commit = git_last_commit(master, git_environments_path)
15
+ git_provider = ENV['GIT_PROVIDER']
16
+ r10k_config_path = get_r10k_config_file_path(master)
17
+ r10k_config_bak_path = "#{r10k_config_path}.bak"
18
+ #In-line files
19
+ r10k_conf = <<-CONF
20
+ cachedir: '/var/cache/r10k'
21
+ git:
22
+ provider: '#{git_provider}'
23
+ sources:
24
+ control:
25
+ basedir: "#{env_path}"
26
+ remote: "#{git_control_remote}"
27
+ CONF
28
+
29
+ # Install the same module in two different places
30
+ puppetfile = <<-EOS
31
+ mod 'prod_apache',
32
+ :git => 'git://github.com/puppetlabs/puppetlabs-apache.git',
33
+ :branch => 'master'
34
+
35
+ mod 'test_apache',
36
+ :git => 'git://github.com/puppetlabs/puppetlabs-apache.git',
37
+ :branch => 'master'
38
+ EOS
39
+
40
+ teardown do
41
+ step 'Restore Original "r10k" Config'
42
+ on(master, "mv #{r10k_config_bak_path} #{r10k_config_path}")
43
+
44
+ clean_up_r10k(master, last_commit, git_environments_path)
45
+ end
46
+
47
+ step 'Stub the forge'
48
+ stub_forge_on(master)
49
+
50
+ step 'Backup Current "r10k" Config'
51
+ on(master, "mv #{r10k_config_path} #{r10k_config_bak_path}")
52
+
53
+ step 'Update the "r10k" Config'
54
+ create_remote_file(master, r10k_config_path, r10k_conf)
55
+
56
+ step 'Ask r10k to deploy'
57
+ on(master, "#{r10k_fqp} deploy environment -p")
58
+
59
+ step 'Add puppetfile with repeated remote'
60
+ create_remote_file(master, "#{git_environments_path}/Puppetfile", puppetfile)
61
+ git_add_commit_push(master, 'production', 'add Puppetfile', git_environments_path)
62
+
63
+ step 'Deploy r10k'
64
+ on(master, "#{r10k_fqp} deploy environment -p")
65
+
66
+ step 'Verify module was installed in both places'
67
+ on(master, "test -d #{code_dir}/modules/prod_apache")
68
+ on(master, "test -d #{code_dir}/modules/test_apache")
@@ -24,7 +24,7 @@ stage_env_notify_message = 'This is a different message'
24
24
  stage_env_notify_message_regex = /#{stage_env_notify_message}/
25
25
 
26
26
  #Verification for "test" Environment
27
- test_env_error_message_regex = /Error:.*Could not find environment 'test'/
27
+ test_env_error_message_regex = /Error:.*Could not.*environment '?test'?/
28
28
 
29
29
  #Verification for "temp" Environment
30
30
  test_env_notify_message_regex = /I am in the temp environment/
@@ -15,7 +15,7 @@ initial_env_names = ['production', 'stage']
15
15
 
16
16
  #Verification
17
17
  notify_message_regex = /I am in the production environment/
18
- stage_env_error_message_regex = /Error:.*Could not find environment 'stage'/
18
+ stage_env_error_message_regex = /Error:.*Could not.*environment '?stage'?/
19
19
 
20
20
  #Manifest
21
21
  site_pp_path = File.join(git_environments_path, 'manifests', 'site.pp')
@@ -22,7 +22,7 @@ site_pp = create_site_pp(master_certname, ' include helloworld')
22
22
  notify_message_prod_env_regex = /I am in the production environment/
23
23
  notify_message_test_env_regex = /I am in the test environment/
24
24
  removal_message_test_env_regex = /Removing unmanaged path.*test/
25
- error_message_regex = /Could not retrieve catalog from remote server/
25
+ error_message_regex = /Could not retrieve (catalog from remote server|information from environment test)/
26
26
 
27
27
  #Teardown
28
28
  teardown do
@@ -157,7 +157,10 @@ module R10K
157
157
  logger.debug("Unable to get environment module deploy data for .r10k-deploy.json at #{environment.path}")
158
158
  end
159
159
 
160
- File.open("#{environment.path}/.r10k-deploy.json", 'w') do |f|
160
+ # make this file write as atomic as possible in pure ruby
161
+ final = "#{environment.path}/.r10k-deploy.json"
162
+ staging = "#{environment.path}/.r10k-deploy.json~"
163
+ File.open(staging, 'w') do |f|
161
164
  deploy_info = environment.info.merge({
162
165
  :started_at => started_at,
163
166
  :finished_at => Time.new,
@@ -167,6 +170,7 @@ module R10K
167
170
 
168
171
  f.puts(JSON.pretty_generate(deploy_info))
169
172
  end
173
+ FileUtils.mv(staging, final)
170
174
  end
171
175
 
172
176
  def undeployable_environment_names(environments, expected_names)
@@ -184,6 +188,7 @@ module R10K
184
188
  'no-force': :self,
185
189
  'generate-types': :self,
186
190
  'puppet-path': :self,
191
+ 'puppet-conf': :self,
187
192
  'default-branch-override': :self)
188
193
  end
189
194
  end
@@ -76,7 +76,8 @@ module R10K
76
76
  cachedir: :self,
77
77
  'no-force': :self,
78
78
  'generate-types': :self,
79
- 'puppet-path': :self)
79
+ 'puppet-path': :self,
80
+ 'puppet-conf': :self)
80
81
  end
81
82
  end
82
83
  end
@@ -43,10 +43,11 @@ module R10K
43
43
  config_settings = settings_from_config(@opts[:config])
44
44
 
45
45
  overrides = {}
46
- overrides[:cachedir] = @opts[:cachedir] unless @opts[:cachedir].nil?
47
- overrides[:deploy] = {} if !@opts[:'puppet-path'].nil? || !@opts[:'generate-types'].nil?
48
- overrides[:deploy][:puppet_path] = @opts[:'puppet-path'] unless @opts[:'puppet-path'].nil?
49
- overrides[:deploy][:generate_types] = @opts[:'generate-types'] unless @opts[:'generate-types'].nil?
46
+ overrides[:cachedir] = @opts[:cachedir] if @opts.key?(:cachedir)
47
+ overrides[:deploy] = {} if @opts.key?(:'puppet-path') || @opts.key?(:'generate-types')
48
+ overrides[:deploy][:puppet_path] = @opts[:'puppet-path'] if @opts.key?(:'puppet-path')
49
+ overrides[:deploy][:puppet_conf] = @opts[:'puppet-conf'] unless @opts[:'puppet-conf'].nil?
50
+ overrides[:deploy][:generate_types] = @opts[:'generate-types'] if @opts.key?(:'generate-types')
50
51
 
51
52
  with_overrides = config_settings.merge(overrides) do |key, oldval, newval|
52
53
  newval = oldval.merge(newval) if oldval.is_a? Hash
@@ -31,6 +31,7 @@ module R10K::CLI
31
31
  exit 1
32
32
  end
33
33
  end
34
+ option nil, :'puppet-conf', 'Path to puppet.conf', argument: :required
34
35
 
35
36
  run do |opts, args, cmd|
36
37
  puts cmd.help(:verbose => opts[:verbose])
@@ -137,7 +137,7 @@ class R10K::Environment::Base
137
137
  end
138
138
 
139
139
  def generate_types!
140
- argv = [R10K::Settings.puppet_path, 'generate', 'types', '--environment', dirname, '--environmentpath', basedir]
140
+ argv = [R10K::Settings.puppet_path, 'generate', 'types', '--environment', dirname, '--environmentpath', basedir, '--config', R10K::Settings.puppet_conf]
141
141
  subproc = R10K::Util::Subprocess.new(argv)
142
142
  subproc.raise_on_fail = true
143
143
  subproc.logger = logger
@@ -212,14 +212,14 @@ module R10K
212
212
 
213
213
  # Remove the temporary directory used for unpacking the module.
214
214
  def cleanup_unpack_path
215
- if unpack_path.exist?
215
+ if unpack_path.parent.exist?
216
216
  unpack_path.parent.rmtree
217
217
  end
218
218
  end
219
219
 
220
220
  # Remove the downloaded module release.
221
221
  def cleanup_download_path
222
- if download_path.exist?
222
+ if download_path.parent.exist?
223
223
  download_path.parent.rmtree
224
224
  end
225
225
  end
@@ -16,7 +16,17 @@ class R10K::Git::Cache
16
16
 
17
17
  include R10K::Settings::Mixin
18
18
 
19
- def_setting_attr :cache_root, File.expand_path(ENV['HOME'] ? '~/.r10k/git': '/root/.r10k/git')
19
+ #@api private
20
+ def self.determine_cache_root
21
+ if R10K::Util::Platform.windows?
22
+ File.join(ENV['LOCALAPPDATA'], 'r10k', 'git')
23
+ else
24
+ File.expand_path(ENV['HOME'] ? '~/.r10k/git': '/root/.r10k/git')
25
+ end
26
+ end
27
+ private_class_method :determine_cache_root
28
+
29
+ def_setting_attr :cache_root, determine_cache_root
20
30
 
21
31
  @instance_cache = R10K::InstanceCache.new(self)
22
32
 
@@ -99,10 +109,8 @@ class R10K::Git::Cache
99
109
 
100
110
  alias cached? exist?
101
111
 
102
- private
103
-
104
112
  # Reformat the remote name into something that can be used as a directory
105
113
  def sanitized_dirname
106
- @remote.gsub(/[^@\w\.-]/, '-')
114
+ @sanitized_dirname ||= @remote.gsub(/[^@\w\.-]/, '-')
107
115
  end
108
116
  end
@@ -12,6 +12,10 @@ class R10K::Git::StatefulRepository
12
12
  # @api private
13
13
  attr_reader :repo
14
14
 
15
+ # @!attribute [r] cache
16
+ # @api private
17
+ attr_reader :cache
18
+
15
19
  extend Forwardable
16
20
  def_delegators :@repo, :head, :tracked_paths
17
21
 
@@ -44,6 +44,7 @@ module R10K
44
44
  class DeployInitializer < BaseInitializer
45
45
  def call
46
46
  with_setting(:puppet_path) { |value| R10K::Settings.puppet_path = value }
47
+ with_setting(:puppet_conf) { |value| R10K::Settings.puppet_conf = value }
47
48
  end
48
49
  end
49
50
 
@@ -99,6 +99,14 @@ class R10K::Module::Base
99
99
  raise NotImplementedError
100
100
  end
101
101
 
102
+ # Return the module's cachedir. Subclasses that implement a cache
103
+ # will override this to return a real directory location.
104
+ #
105
+ # @return [String, :none]
106
+ def cachedir
107
+ :none
108
+ end
109
+
102
110
  private
103
111
 
104
112
  def parse_title(title)
@@ -57,6 +57,10 @@ class R10K::Module::Git < R10K::Module::Base
57
57
  @repo.status(version)
58
58
  end
59
59
 
60
+ def cachedir
61
+ @repo.cache.sanitized_dirname
62
+ end
63
+
60
64
  private
61
65
 
62
66
  def validate_ref(desired, default)
@@ -10,7 +10,7 @@ class Puppetfile
10
10
 
11
11
  include R10K::Settings::Mixin
12
12
 
13
- def_setting_attr :pool_size, 1
13
+ def_setting_attr :pool_size, 4
14
14
 
15
15
  include R10K::Logging
16
16
 
@@ -42,6 +42,11 @@ class Puppetfile
42
42
  # @return [Boolean] Overwrite any locally made changes
43
43
  attr_accessor :force
44
44
 
45
+ # @!attribute [r] modules_by_vcs_cachedir
46
+ # @api private Only exposed for testing purposes
47
+ # @return [Hash{:none, String => Array<R10K::Module>}]
48
+ attr_reader :modules_by_vcs_cachedir
49
+
45
50
  # @param [String] basedir
46
51
  # @param [String] moduledir The directory to install the modules, default to #{basedir}/modules
47
52
  # @param [String] puppetfile_path The path to the Puppetfile, default to #{basedir}/Puppetfile
@@ -58,6 +63,7 @@ class Puppetfile
58
63
 
59
64
  @modules = []
60
65
  @managed_content = {}
66
+ @modules_by_vcs_cachedir = {}
61
67
  @forge = 'forgeapi.puppetlabs.com'
62
68
 
63
69
  @loaded = false
@@ -137,6 +143,9 @@ class Puppetfile
137
143
  mod.origin = 'Puppetfile'
138
144
 
139
145
  @managed_content[install_path] << mod.name
146
+ cachedir = mod.cachedir
147
+ @modules_by_vcs_cachedir[cachedir] ||= []
148
+ @modules_by_vcs_cachedir[cachedir] << mod
140
149
  @modules << mod
141
150
  end
142
151
 
@@ -145,7 +154,9 @@ class Puppetfile
145
154
  def managed_directories
146
155
  self.load unless @loaded
147
156
 
148
- @managed_content.keys
157
+ dirs = @managed_content.keys
158
+ dirs.delete(real_basedir)
159
+ dirs
149
160
  end
150
161
 
151
162
  # Returns an array of the full paths to all the content being managed.
@@ -212,15 +223,22 @@ class Puppetfile
212
223
  def modules_queue(visitor)
213
224
  Queue.new.tap do |queue|
214
225
  visitor.visit(:puppetfile, self) do
215
- modules.each { |mod| queue << mod }
226
+ modules_by_cachedir = modules_by_vcs_cachedir.clone
227
+ modules_without_vcs_cachedir = modules_by_cachedir.delete(:none) || []
228
+
229
+ modules_without_vcs_cachedir.each {|mod| queue << Array(mod) }
230
+ modules_by_cachedir.values.each {|mods| queue << mods }
216
231
  end
217
232
  end
218
233
  end
234
+ public :modules_queue
219
235
 
220
236
  def visitor_thread(visitor, mods_queue)
221
237
  Thread.new do
222
238
  begin
223
- while mod = mods_queue.pop(true) do mod.accept(visitor) end
239
+ while mods = mods_queue.pop(true) do
240
+ mods.each {|mod| mod.accept(visitor) }
241
+ end
224
242
  rescue ThreadError => e
225
243
  logger.debug _("Module thread %{id} exiting: %{message}") % {message: e.message, id: Thread.current.object_id}
226
244
  Thread.exit
@@ -248,8 +266,6 @@ class Puppetfile
248
266
  end
249
267
 
250
268
  def validate_install_path(path, modname)
251
- real_basedir = Pathname.new(basedir).cleanpath.to_s
252
-
253
269
  unless /^#{Regexp.escape(real_basedir)}.*/ =~ path
254
270
  raise R10K::Error.new("Puppetfile cannot manage content '#{modname}' outside of containing environment: #{path} is not within #{real_basedir}")
255
271
  end
@@ -257,6 +273,10 @@ class Puppetfile
257
273
  true
258
274
  end
259
275
 
276
+ def real_basedir
277
+ Pathname.new(basedir).cleanpath.to_s
278
+ end
279
+
260
280
  class DSL
261
281
  # A barebones implementation of the Puppetfile DSL
262
282
  #