r10k 3.8.0 → 3.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/docker.yml +4 -1
  3. data/.github/workflows/release.yml +3 -2
  4. data/.github/workflows/rspec_tests.yml +1 -1
  5. data/.travis.yml +8 -1
  6. data/CHANGELOG.mkd +10 -0
  7. data/CODEOWNERS +1 -1
  8. data/doc/common-patterns.mkd +1 -0
  9. data/doc/dynamic-environments/configuration.mkd +90 -43
  10. data/doc/dynamic-environments/usage.mkd +7 -7
  11. data/doc/puppetfile.mkd +23 -3
  12. data/docker/Gemfile +1 -1
  13. data/docker/Makefile +4 -3
  14. data/docker/docker-compose.yml +18 -0
  15. data/docker/r10k/Dockerfile +1 -1
  16. data/docker/r10k/docker-entrypoint.sh +0 -1
  17. data/docker/r10k/release.Dockerfile +1 -1
  18. data/docker/spec/dockerfile_spec.rb +26 -32
  19. data/integration/tests/git_source/git_source_repeated_remote.rb +2 -2
  20. data/integration/tests/user_scenario/basic_workflow/multi_env_custom_forge_git_module.rb +2 -1
  21. data/integration/tests/user_scenario/basic_workflow/multi_env_custom_forge_git_module_static.rb +2 -1
  22. data/integration/tests/user_scenario/basic_workflow/multi_source_custom_forge_git_module.rb +1 -1
  23. data/integration/tests/user_scenario/basic_workflow/single_env_custom_forge_git_module.rb +2 -1
  24. data/lib/r10k/action/deploy/display.rb +9 -3
  25. data/lib/r10k/action/deploy/environment.rb +36 -14
  26. data/lib/r10k/cli/deploy.rb +5 -3
  27. data/lib/r10k/environment/base.rb +1 -1
  28. data/lib/r10k/environment/git.rb +17 -2
  29. data/lib/r10k/environment/name.rb +22 -4
  30. data/lib/r10k/environment/svn.rb +11 -2
  31. data/lib/r10k/environment/with_modules.rb +1 -1
  32. data/lib/r10k/git/rugged/credentials.rb +22 -15
  33. data/lib/r10k/module/forge.rb +15 -3
  34. data/lib/r10k/module/git.rb +24 -23
  35. data/lib/r10k/module/local.rb +1 -1
  36. data/lib/r10k/module/svn.rb +14 -11
  37. data/lib/r10k/settings.rb +6 -1
  38. data/lib/r10k/source/base.rb +5 -0
  39. data/lib/r10k/source/git.rb +4 -1
  40. data/lib/r10k/source/hash.rb +4 -2
  41. data/lib/r10k/source/svn.rb +5 -1
  42. data/lib/r10k/util/setopts.rb +33 -12
  43. data/lib/r10k/version.rb +1 -1
  44. data/locales/r10k.pot +22 -18
  45. data/r10k.gemspec +1 -1
  46. data/spec/unit/action/deploy/display_spec.rb +4 -0
  47. data/spec/unit/action/deploy/environment_spec.rb +111 -10
  48. data/spec/unit/environment/git_spec.rb +16 -0
  49. data/spec/unit/environment/name_spec.rb +28 -0
  50. data/spec/unit/environment/svn_spec.rb +12 -0
  51. data/spec/unit/git/rugged/credentials_spec.rb +10 -0
  52. data/spec/unit/module/forge_spec.rb +6 -0
  53. data/spec/unit/module/git_spec.rb +1 -1
  54. data/spec/unit/module_spec.rb +59 -9
  55. data/spec/unit/util/setopts_spec.rb +25 -1
  56. metadata +5 -11
  57. data/azure-pipelines.yml +0 -87
@@ -0,0 +1,18 @@
1
+ version: '3.7'
2
+
3
+ services:
4
+ r10k_check:
5
+ image: ${R10K_IMAGE:-puppet/r10k}
6
+ environment:
7
+ - PUPPERWARE_ANALYTICS_ENABLED=${PUPPERWARE_ANALYTICS_ENABLED:-false}
8
+ command: 'puppetfile check --verbose --trace --puppetfile test/Puppetfile'
9
+ volumes:
10
+ - ${SPEC_DIRECTORY}/fixtures:/home/puppet/test
11
+
12
+ r10k_install:
13
+ image: ${R10K_IMAGE:-puppet/r10k}
14
+ environment:
15
+ - PUPPERWARE_ANALYTICS_ENABLED=${PUPPERWARE_ANALYTICS_ENABLED:-false}
16
+ command: 'puppetfile install --verbose --trace --puppetfile test/Puppetfile'
17
+ volumes:
18
+ - ${SPEC_DIRECTORY}/fixtures:/home/puppet/test
@@ -49,7 +49,7 @@ COPY --from=build /workspace/r10k.gem /
49
49
  SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
50
50
  # ignore apk and gem pinning
51
51
  # hadolint ignore=DL3018,DL3028
52
- RUN chmod a+x /adduser.sh /docker-entrypoint.sh && \
52
+ RUN chmod a+x /adduser.sh /docker-entrypoint.sh /docker-entrypoint.d/*.sh && \
53
53
  # Add a puppet user to run r10k as for consistency with puppetserver
54
54
  /adduser.sh && \
55
55
  chown -R puppet: /docker-entrypoint.d /docker-entrypoint.sh && \
@@ -4,7 +4,6 @@ set -e
4
4
 
5
5
  for f in /docker-entrypoint.d/*.sh; do
6
6
  # Don't print out any messages here since this is a CLI container
7
- chmod +x "$f"
8
7
  "$f"
9
8
  done
10
9
 
@@ -38,7 +38,7 @@ LABEL org.label-schema.version="$version" \
38
38
  SHELL ["/bin/ash", "-eo", "pipefail", "-c"]
39
39
  # ignore apk and gem pinning
40
40
  # hadolint ignore=DL3018,DL3028
41
- RUN chmod a+x /adduser.sh /docker-entrypoint.sh && \
41
+ RUN chmod a+x /adduser.sh /docker-entrypoint.sh /docker-entrypoint.d/*.sh && \
42
42
  /adduser.sh && \
43
43
  chown -R puppet: /docker-entrypoint.d /docker-entrypoint.sh && \
44
44
  apk add --no-cache ruby openssh-client git ruby-rugged curl ruby-dev make gcc musl-dev && \
@@ -1,43 +1,37 @@
1
1
  require 'rspec/core'
2
2
  require 'fileutils'
3
3
  require 'open3'
4
+ include Pupperware::SpecHelpers
4
5
 
5
- SPEC_DIRECTORY = File.dirname(__FILE__)
6
+ ENV['SPEC_DIRECTORY'] = File.dirname(__FILE__)
7
+ # unifies volume naming
8
+ ENV['COMPOSE_PROJECT_NAME'] ||= 'r10k'
6
9
 
7
- describe 'r10k container' do
8
- include Pupperware::SpecHelpers
9
- def run_r10k(command)
10
- run_command("docker run --detach \
11
- --volume #{File.join(SPEC_DIRECTORY, 'fixtures')}:/home/puppet/test \
12
- #{@image} #{command} \
13
- --verbose \
14
- --trace \
15
- --puppetfile test/Puppetfile")
16
- end
17
-
18
- before(:all) do
19
- @image = require_test_image
10
+ RSpec.configure do |c|
11
+ c.before(:suite) do
12
+ ENV['R10K_IMAGE'] = require_test_image
13
+ pull_images(['r10k_check','r10k_install'])
14
+ teardown_cluster()
15
+ # no certs to preload, but if the suite adds puppetserver, be explicit
16
+ docker_compose_up(preload_certs: true)
20
17
  end
21
18
 
22
- after(:all) do
23
- FileUtils.rm_rf(File.join(SPEC_DIRECTORY, 'fixtures', 'modules'))
24
- end
25
-
26
- it 'should validate the Puppetfile' do
27
- result = run_r10k('puppetfile check')
28
- container = result[:stdout].chomp
29
- wait_on_container_exit(container)
30
- expect(get_container_exit_code(container)).to eq(0)
31
- emit_log(container)
32
- teardown_container(container)
19
+ c.after(:suite) do
20
+ teardown_cluster()
21
+ FileUtils.rm_rf(File.join(ENV['SPEC_DIRECTORY'], 'fixtures', 'modules'))
33
22
  end
23
+ end
34
24
 
35
- it 'should install the Puppetfile' do
36
- result = run_r10k('puppetfile install')
37
- container = result[:stdout].chomp
38
- wait_on_container_exit(container)
39
- expect(get_container_exit_code(container)).to eq(0)
40
- emit_log(container)
41
- teardown_container(container)
25
+ describe 'r10k container' do
26
+ {
27
+ 'r10k_check': 'validate',
28
+ 'r10k_install': 'install',
29
+ }.each do |container, op|
30
+ it "should #{op} the Puppetfile" do
31
+ container = get_service_container(container)
32
+ wait_on_container_exit(container)
33
+ expect(get_container_exit_code(container)).to eq(0)
34
+ emit_log(container)
35
+ end
42
36
  end
43
37
  end
@@ -30,11 +30,11 @@ CONF
30
30
  puppetfile = <<-EOS
31
31
  mod 'prod_apache',
32
32
  :git => 'git://github.com/puppetlabs/puppetlabs-apache.git',
33
- :branch => 'master'
33
+ :tag => 'v6.0.0'
34
34
 
35
35
  mod 'test_apache',
36
36
  :git => 'git://github.com/puppetlabs/puppetlabs-apache.git',
37
- :branch => 'master'
37
+ :tag => 'v6.0.0'
38
38
  EOS
39
39
 
40
40
  teardown do
@@ -27,7 +27,8 @@ stdlib_notify_message_regex = /The test message is:.*one.*=>.*1.*two.*=>.*bats.*
27
27
  puppet_file = <<-PUPPETFILE
28
28
  mod "puppetlabs/motd"
29
29
  mod 'puppetlabs/stdlib',
30
- :git => 'git://github.com/puppetlabs/puppetlabs-stdlib.git'
30
+ :git => 'git://github.com/puppetlabs/puppetlabs-stdlib.git',
31
+ :tag => 'v7.0.1'
31
32
  PUPPETFILE
32
33
 
33
34
  puppet_file_path = File.join(git_environments_path, 'Puppetfile')
@@ -31,7 +31,8 @@ puppet_file = <<-PUPPETFILE
31
31
  moduledir '#{@module_path}'
32
32
  mod "puppetlabs/motd"
33
33
  mod 'puppetlabs/stdlib',
34
- :git => 'git://github.com/puppetlabs/puppetlabs-stdlib.git'
34
+ :git => 'git://github.com/puppetlabs/puppetlabs-stdlib.git',
35
+ :tag => 'v7.0.1'
35
36
  PUPPETFILE
36
37
 
37
38
  puppet_file_path = File.join(git_environments_path, 'Puppetfile')
@@ -65,7 +65,7 @@ env_structs = {:production => GitEnv.new('/git_repos',
65
65
  '/git_repos_alt/environments_alt.git',
66
66
  '/root/environments_alt',
67
67
  '/root/environments_alt/Puppetfile',
68
- 'mod "puppetlabs/stdlib", :git => "git://github.com/puppetlabs/puppetlabs-stdlib.git"',
68
+ 'mod "puppetlabs/stdlib", :git => "git://github.com/puppetlabs/puppetlabs-stdlib.git", :tag => "v7.0.1"',
69
69
  '/root/environments_alt/manifests/site.pp',
70
70
  create_site_pp(master_certname, stage_env_manifest)
71
71
  ),
@@ -28,7 +28,8 @@ notify_message_regex = /I am in the production environment/
28
28
  puppet_file = <<-PUPPETFILE
29
29
  mod "puppetlabs/motd"
30
30
  mod 'puppetlabs/inifile',
31
- :git => 'git://github.com/puppetlabs/puppetlabs-inifile'
31
+ :git => 'git://github.com/puppetlabs/puppetlabs-inifile',
32
+ :tag => 'v5.0.1'
32
33
  PUPPETFILE
33
34
 
34
35
  puppet_file_path = File.join(git_environments_path, 'Puppetfile')
@@ -59,14 +59,14 @@ module R10K
59
59
  end
60
60
 
61
61
  def environment_info(env)
62
- if !@puppetfile && !@detail
62
+ if !@modules && !@detail
63
63
  env.dirname
64
64
  else
65
65
  env_info = env.info.merge({
66
66
  :status => (env.status rescue nil),
67
67
  })
68
68
 
69
- env_info[:modules] = env.modules.map { |mod| module_info(mod) } if @puppetfile
69
+ env_info[:modules] = env.modules.map { |mod| module_info(mod) } if @modules
70
70
 
71
71
  env_info
72
72
  end
@@ -81,7 +81,13 @@ module R10K
81
81
  end
82
82
 
83
83
  def allowed_initialize_opts
84
- super.merge(puppetfile: :self, detail: :self, format: :self, fetch: :self)
84
+ super.merge({
85
+ puppetfile: :modules,
86
+ modules: :self,
87
+ detail: :self,
88
+ format: :self,
89
+ fetch: :self
90
+ })
85
91
  end
86
92
  end
87
93
  end
@@ -18,7 +18,8 @@ module R10K
18
18
  def initialize(opts, argv, settings = nil)
19
19
  settings ||= {}
20
20
  @purge_levels = settings.fetch(:deploy, {}).fetch(:purge_levels, [])
21
- @user_purge_whitelist = settings.fetch(:deploy, {}).fetch(:purge_whitelist, [])
21
+ @user_purge_allowlist = read_purge_allowlist(settings.fetch(:deploy, {}).fetch(:purge_whitelist, []),
22
+ settings.fetch(:deploy, {}).fetch(:purge_allowlist, []))
22
23
  @generate_types = settings.fetch(:deploy, {}).fetch(:generate_types, false)
23
24
 
24
25
  super
@@ -43,6 +44,23 @@ module R10K
43
44
 
44
45
  private
45
46
 
47
+ def read_purge_allowlist (whitelist, allowlist)
48
+ whitelist_has_content = !whitelist.empty?
49
+ allowlist_has_content = !allowlist.empty?
50
+ case
51
+ when whitelist_has_content == false && allowlist_has_content == false
52
+ []
53
+ when whitelist_has_content && allowlist_has_content
54
+ raise R10K::Error.new "Values found for both purge_whitelist and purge_allowlist. Setting " <<
55
+ "purge_whitelist is deprecated, please only use purge_allowlist."
56
+ when allowlist_has_content
57
+ allowlist
58
+ else
59
+ logger.warn "Setting purge_whitelist is deprecated; please use purge_allowlist instead."
60
+ whitelist
61
+ end
62
+ end
63
+
46
64
  def visit_deployment(deployment)
47
65
  # Ensure that everything can be preloaded. If we cannot preload all
48
66
  # sources then we can't fully enumerate all environments which
@@ -95,7 +113,7 @@ module R10K
95
113
  environment.sync
96
114
  logger.info _("Environment %{env_dir} is now at %{env_signature}") % {env_dir: environment.dirname, env_signature: environment.signature}
97
115
 
98
- if status == :absent || @puppetfile
116
+ if status == :absent || @modules
99
117
  if status == :absent
100
118
  logger.debug(_("Environment %{env_dir} is new, updating all modules") % {env_dir: environment.dirname})
101
119
  end
@@ -110,7 +128,7 @@ module R10K
110
128
  if @purge_levels.include?(:environment)
111
129
  if @visit_ok
112
130
  logger.debug("Purging unmanaged content for environment '#{environment.dirname}'...")
113
- environment.purge!(:recurse => true, :whitelist => environment.whitelist(@user_purge_whitelist))
131
+ environment.purge!(:recurse => true, :whitelist => environment.whitelist(@user_purge_allowlist))
114
132
  else
115
133
  logger.debug("Not purging unmanaged content for environment '#{environment.dirname}' due to prior deploy failures.")
116
134
  end
@@ -145,17 +163,20 @@ module R10K
145
163
  end
146
164
 
147
165
  def write_environment_info!(environment, started_at, success)
148
- module_deploys = []
149
- begin
150
- environment.modules.each do |mod|
151
- name = mod.name
152
- version = mod.version
153
- sha = mod.repo.head rescue nil
154
- module_deploys.push({:name => name, :version => version, :sha => sha})
166
+ module_deploys =
167
+ begin
168
+ environment.modules.map do |mod|
169
+ props = mod.properties
170
+ {
171
+ name: mod.name,
172
+ version: props[:expected],
173
+ sha: props[:type] == :git ? props[:actual] : nil
174
+ }
175
+ end
176
+ rescue
177
+ logger.debug("Unable to get environment module deploy data for .r10k-deploy.json at #{environment.path}")
178
+ []
155
179
  end
156
- rescue
157
- logger.debug("Unable to get environment module deploy data for .r10k-deploy.json at #{environment.path}")
158
- end
159
180
 
160
181
  # make this file write as atomic as possible in pure ruby
161
182
  final = "#{environment.path}/.r10k-deploy.json"
@@ -183,7 +204,8 @@ module R10K
183
204
  end
184
205
 
185
206
  def allowed_initialize_opts
186
- super.merge(puppetfile: :self,
207
+ super.merge(puppetfile: :modules,
208
+ modules: :self,
187
209
  cachedir: :self,
188
210
  'no-force': :self,
189
211
  'generate-types': :self,
@@ -55,7 +55,7 @@ branches.
55
55
 
56
56
  Environments can provide a Puppetfile at the root of the directory to deploy
57
57
  independent Puppet modules. To recursively deploy an environment, pass the
58
- `--puppetfile` flag to the command.
58
+ `--modules` flag to the command.
59
59
 
60
60
  **NOTE**: If an environment has a Puppetfile when it is instantiated a
61
61
  recursive update will be forced. It is assumed that environments are dependent
@@ -63,7 +63,8 @@ on modules specified in the Puppetfile and an update will be automatically
63
63
  scheduled. On subsequent deployments, Puppetfile deployment will default to off.
64
64
  DESCRIPTION
65
65
 
66
- flag :p, :puppetfile, 'Deploy modules from a puppetfile'
66
+ flag :p, :puppetfile, 'Deploy modules (deprecated, use -m)'
67
+ flag :m, :modules, 'Deploy modules'
67
68
  option nil, :'default-branch-override', 'Specify a branchname to override the default branch in the puppetfile',
68
69
  argument: :required
69
70
 
@@ -100,7 +101,8 @@ try to deploy the given module names in all environments.
100
101
  usage 'display'
101
102
  summary 'Display environments and modules in the deployment'
102
103
 
103
- flag :p, :puppetfile, 'Display Puppetfile modules'
104
+ flag :p, :puppetfile, 'Display modules (deprecated, use -m)'
105
+ flag :m, :modules, 'Display modules'
104
106
  flag nil, :detail, 'Display detailed information'
105
107
  flag nil, :fetch, 'Update available environment lists from all remote sources'
106
108
  option nil, :format, 'Display output in a specific format. Valid values: json, yaml. Default: yaml',
@@ -43,7 +43,7 @@ class R10K::Environment::Base
43
43
  @basedir = basedir
44
44
  @dirname = dirname
45
45
  @options = options
46
- @puppetfile_name = options[:puppetfile_name]
46
+ @puppetfile_name = options.delete(:puppetfile_name)
47
47
 
48
48
  @full_path = File.join(@basedir, @dirname)
49
49
  @path = Pathname.new(File.join(@basedir, @dirname))
@@ -27,6 +27,8 @@ class R10K::Environment::Git < R10K::Environment::WithModules
27
27
  # @return [R10K::Git::StatefulRepository] The git repo backing this environment
28
28
  attr_reader :repo
29
29
 
30
+ include R10K::Util::Setopts
31
+
30
32
  # Initialize the given Git environment.
31
33
  #
32
34
  # @param name [String] The unique name describing this environment.
@@ -38,8 +40,21 @@ class R10K::Environment::Git < R10K::Environment::WithModules
38
40
  # @param options [String] :ref The git reference to use for this environment
39
41
  def initialize(name, basedir, dirname, options = {})
40
42
  super
41
- @remote = options[:remote]
42
- @ref = options[:ref]
43
+ setopts(options, {
44
+ # Standard option interface
45
+ :version => :ref,
46
+ :source => :remote,
47
+ :type => ::R10K::Util::Setopts::Ignore,
48
+
49
+ # Type-specific options
50
+ :ref => :self,
51
+ :remote => :self,
52
+
53
+ }, raise_on_unhandled: false)
54
+ # TODO: in r10k 4.0.0, a major version bump, stop allowing garbage options.
55
+ # We only allow them now, here, on this object, because prior to adopting
56
+ # setopts in the constructor, this object type didn't do any validation
57
+ # checking of options passed, and would permit garbage parameters.
43
58
 
44
59
  @repo = R10K::Git::StatefulRepository.new(@remote, @basedir, @dirname)
45
60
  end
@@ -12,13 +12,13 @@ module R10K
12
12
  INVALID_CHARACTERS = %r[\W]
13
13
 
14
14
  def initialize(name, opts)
15
- @name = name
16
- @opts = opts
17
-
18
15
  @source = opts[:source]
19
16
  @prefix = opts[:prefix]
20
17
  @invalid = opts[:invalid]
21
18
 
19
+ @name = derive_name(name, opts[:strip_component])
20
+ @opts = opts
21
+
22
22
  case @invalid
23
23
  when 'correct_and_warn'
24
24
  @validate = true
@@ -71,8 +71,26 @@ module R10K
71
71
 
72
72
  private
73
73
 
74
- def derive_prefix(source,prefix)
74
+ def derive_name(name, strip_component)
75
+ return name unless strip_component
76
+
77
+ unless strip_component.is_a?(String)
78
+ raise _('Improper configuration value given for strip_component setting in %{src} source. ' \
79
+ 'Value must be a string, a /regex/, false, or omitted. Got "%{val}" (%{type})' \
80
+ % {src: @source, val: strip_component, type: strip_component.class})
81
+ end
75
82
 
83
+ if %r{^/.*/$}.match(strip_component)
84
+ regex = Regexp.new(strip_component[1..-2])
85
+ name.gsub(regex, '')
86
+ elsif name.start_with?(strip_component)
87
+ name[strip_component.size..-1]
88
+ else
89
+ name
90
+ end
91
+ end
92
+
93
+ def derive_prefix(source,prefix)
76
94
  if prefix == true
77
95
  "#{source}_"
78
96
  elsif prefix.is_a? String
@@ -44,8 +44,17 @@ class R10K::Environment::SVN < R10K::Environment::Base
44
44
  # @option options [String] :password The SVN password
45
45
  def initialize(name, basedir, dirname, options = {})
46
46
  super
47
+ setopts(options, {
48
+ # Standard option interface
49
+ :source => :remote,
50
+ :version => :expected_revision,
51
+ :type => ::R10K::Util::Setopts::Ignore,
47
52
 
48
- setopts(options, {:remote => :self, :username => :self, :password => :self, :puppetfile_name => :self })
53
+ # Type-specific options
54
+ :remote => :self,
55
+ :username => :self,
56
+ :password => :self,
57
+ })
49
58
 
50
59
  @working_dir = R10K::SVN::WorkingDir.new(Pathname.new(@full_path), :username => @username, :password => @password)
51
60
  end
@@ -61,7 +70,7 @@ class R10K::Environment::SVN < R10K::Environment::Base
61
70
  if @working_dir.is_svn?
62
71
  @working_dir.update
63
72
  else
64
- @working_dir.checkout(@remote)
73
+ @working_dir.checkout(@remote, @expected_revision)
65
74
  end
66
75
  @synced = true
67
76
  end