r10k 3.9.3 → 3.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec_tests.yml +1 -1
  3. data/CHANGELOG.mkd +9 -0
  4. data/doc/dynamic-environments/configuration.mkd +7 -0
  5. data/integration/Rakefile +1 -1
  6. data/integration/tests/user_scenario/basic_workflow/negative/neg_specify_deleted_forge_module.rb +3 -9
  7. data/integration/tests/user_scenario/basic_workflow/single_env_purge_unmanaged_modules.rb +8 -14
  8. data/lib/r10k/action/deploy/environment.rb +3 -0
  9. data/lib/r10k/action/runner.rb +11 -6
  10. data/lib/r10k/git/cache.rb +1 -1
  11. data/lib/r10k/initializers.rb +7 -0
  12. data/lib/r10k/module/forge.rb +5 -1
  13. data/lib/r10k/module_loader/puppetfile.rb +195 -0
  14. data/lib/r10k/module_loader/puppetfile/dsl.rb +37 -0
  15. data/lib/r10k/puppetfile.rb +77 -153
  16. data/lib/r10k/settings.rb +3 -0
  17. data/lib/r10k/source/base.rb +10 -0
  18. data/lib/r10k/source/git.rb +5 -0
  19. data/lib/r10k/source/svn.rb +4 -0
  20. data/lib/r10k/util/purgeable.rb +70 -8
  21. data/lib/r10k/version.rb +1 -1
  22. data/locales/r10k.pot +37 -33
  23. data/spec/fixtures/unit/action/r10k_forge_auth.yaml +4 -0
  24. data/spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml +3 -0
  25. data/spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_subdir_2/ignored_1 +0 -0
  26. data/spec/fixtures/unit/util/purgeable/managed_two/.hidden/unmanaged_3 +0 -0
  27. data/spec/unit/action/deploy/environment_spec.rb +9 -0
  28. data/spec/unit/action/deploy/module_spec.rb +38 -14
  29. data/spec/unit/action/runner_spec.rb +49 -25
  30. data/spec/unit/git/cache_spec.rb +14 -0
  31. data/spec/unit/module/forge_spec.rb +8 -1
  32. data/spec/unit/module_loader/puppetfile_spec.rb +330 -0
  33. data/spec/unit/puppetfile_spec.rb +99 -193
  34. data/spec/unit/settings_spec.rb +6 -2
  35. data/spec/unit/util/purgeable_spec.rb +38 -6
  36. metadata +10 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46561d19b47798865a0cb7a986deb0dc5f4fadc9094414ca25958a41395f07fa
4
- data.tar.gz: 3d604c77ed0d1735127c18737a1bbb0aacc65ad17a789e4b154e05283e240ac3
3
+ metadata.gz: db8e1373846da121e13c6bdd3f74c7eadac79d356d65628157d1f0b6c0939f23
4
+ data.tar.gz: 7fd4694a5e33f692213339db0422fa7db58d38f84d63f2586593665f8acae8cc
5
5
  SHA512:
6
- metadata.gz: bfff74f4746cf25caf1586e6cfd4dea01e810203d59dab4de2072d35ba42bd90e5f1e2a904e117c1fba63de20666ab4b98f26ad6552ecfd1b4ce6c814eb65c0b
7
- data.tar.gz: 5a3972217637238fd8f9bfd1c810e26a901764b496d7dc195c26a0c567111ab03c621e4cef20564f4c8ef57c6292255a8871625ffe3e15597ba6765d39428002
6
+ metadata.gz: 272021e80cc4ce7ba4d11f3316bd972d7498e102a1f4d4aa9c0bcf83eeef60bd283c9fb4db74ad37e854b2ebc60f6ba28d3c251ae51cb54fb2ae03a4842d4bb8
7
+ data.tar.gz: 3689ec04b7dd2a4597199fd2fddba3e06a903b682760aacd565f96e42a604c42a6ef340f598c37ee4e1c1cfc4f4912c1132880de98bb734d86aac39475a45342
@@ -15,7 +15,7 @@ jobs:
15
15
  - {os: ubuntu-18.04, ruby: 2.5}
16
16
  - {os: ubuntu-18.04, ruby: 2.6}
17
17
  - {os: ubuntu-18.04, ruby: 2.7}
18
- - {os: ubuntu-18.04, ruby: jruby-9.2.9.0}
18
+ - {os: ubuntu-18.04, ruby: jruby-9.2.10.0}
19
19
  - {os: windows-2016, ruby: 2.5}
20
20
  - {os: windows-2016, ruby: 2.6}
21
21
  - {os: windows-2016, ruby: 2.7}
data/CHANGELOG.mkd CHANGED
@@ -4,6 +4,15 @@ CHANGELOG
4
4
  Unreleased
5
5
  ----------
6
6
 
7
+ 3.10.0
8
+ ------
9
+
10
+ - Add `authorization_token` setting to allow authentication to a custom Forge server. [#1181](https://github.com/puppetlabs/r10k/pull/1181)
11
+ - (RK-135) Attempting to download the latest version for a module that has no Forge releases will now issue a meaningful error. [#1177](https://github.com/puppetlabs/r10k/pull/1177)
12
+ - Added an interface to R10K::Source::Base named `reload!` for updating the environments list for a given deployment; `reload!` is called before deployment purges to make r10k deploy pools more threadsafe. [#1172](https://github.com/puppetlabs/r10k/pull/1172)
13
+ - Remove username and password from remote url in cache directory name [#1186](https://github.com/puppetlabs/r10k/pull/1186)
14
+ - Purging efficiency is greatly improved. R10K will no longer recurse into directories that match recursive purge exclusions. This should significantly improve the deploy times for those users who enable the "environment" purge level. [#1178](https://github.com/puppetlabs/r10k/pull/1178)
15
+
7
16
  3.9.3
8
17
  -----
9
18
 
@@ -165,9 +165,16 @@ interactions. See the global proxy setting documentation for more information an
165
165
  The 'baseurl' setting indicates where Forge modules should be installed from.
166
166
  This defaults to 'https://forgeapi.puppetlabs.com'
167
167
 
168
+ #### authorization_token
169
+
170
+ The 'authorization_token' setting allows you to provide a token for authenticating to a
171
+ custom Forge server. When set, 'baseurl' must also be set.
172
+ You will need to prepend your token with 'Bearer ' if using Artifactory as your Forge server.
173
+
168
174
  ```yaml
169
175
  forge:
170
176
  baseurl: 'https://private-forge.mysite'
177
+ authorization_token: 'Bearer mysupersecretauthtoken'
171
178
  ```
172
179
 
173
180
  Deployment options
data/integration/Rakefile CHANGED
@@ -65,7 +65,7 @@ rototiller_task :beaker_hostgenerator do |t|
65
65
  end
66
66
 
67
67
  # This is a hack :(
68
- t.add_flag(:name => '', :default => 'centos6-64mdca-64.fa', :override_env => 'TEST_TARGET')
68
+ t.add_flag(:name => '', :default => 'centos7-64mdca-64.fa', :override_env => 'TEST_TARGET')
69
69
 
70
70
  t.add_flag(:name => '--global-config', :default => '{forge_host=forge-aio01-petest.puppetlabs.com}', :override_env => 'BHG_GLOBAL_CONFIG')
71
71
  end
@@ -10,7 +10,7 @@ last_commit = git_last_commit(master, git_environments_path)
10
10
  r10k_fqp = get_r10k_fqp(master)
11
11
 
12
12
  #Verification
13
- error_notification_regex = /Does 'puppetlabs-regret' have at least one published release?/
13
+ error_notification_regex = /The module puppetlabs-regret does not appear to have any published releases/
14
14
 
15
15
  #File
16
16
  puppet_file = <<-PUPPETFILE
@@ -40,12 +40,6 @@ git_add_commit_push(master, 'production', 'Add module.', git_environments_path)
40
40
 
41
41
  #Tests
42
42
  step "Deploy production environment via r10k with specified module deleted"
43
- on(master, "#{r10k_fqp} deploy environment -p -v", :acceptable_exit_codes => 1) do |result|
44
- if get_puppet_version(master) < 4.0
45
- assert_match(error_notification_regex, result.stderr, 'Unexpected error was detected!')
46
- else
47
- expect_failure('expected to fail due to RK-135') do
48
- assert_match(error_notification_regex, result.stderr, 'Unexpected error was detected!')
49
- end
50
- end
43
+ on(master, "#{r10k_fqp} deploy environment -p -v --trace", :acceptable_exit_codes => 1) do |result|
44
+ assert_match(error_notification_regex, result.stderr, 'Unexpected error was detected!')
51
45
  end
@@ -1,10 +1,11 @@
1
1
  require 'git_utils'
2
2
  require 'r10k_utils'
3
3
  require 'master_manipulator'
4
- test_name 'CODEMGMT-73 - C63184 - Single Environment Purge Unmanaged Modules'
4
+ test_name 'CODEMGMT-78 - Puppetfile Purge --puppetfile & --moduledir flag usage'
5
5
 
6
6
  #Init
7
7
  master_certname = on(master, puppet('config', 'print', 'certname')).stdout.rstrip
8
+ moduledir = on(master, puppet('config', 'print', 'environmentpath')).stdout.strip + '/production/modules'
8
9
  git_environments_path = '/root/environments'
9
10
  last_commit = git_last_commit(master, git_environments_path)
10
11
  r10k_fqp = get_r10k_fqp(master)
@@ -14,8 +15,6 @@ motd_path = '/etc/motd'
14
15
  motd_contents = 'Hello!'
15
16
  motd_contents_regex = /\A#{motd_contents}\z/
16
17
 
17
- error_message_regex = /Blah/
18
-
19
18
  #File
20
19
  puppet_file = <<-PUPPETFILE
21
20
  mod "puppetlabs/xinetd"
@@ -48,9 +47,6 @@ stub_forge_on(master)
48
47
  step 'Checkout "production" Branch'
49
48
  git_on(master, 'checkout production', git_environments_path)
50
49
 
51
- step 'Manually Install the "motd" Module from the Forge'
52
- on(master, puppet('module install puppetlabs-motd'))
53
-
54
50
  step 'Create "Puppetfile" for the "production" Environment'
55
51
  create_remote_file(master, puppet_file_path, puppet_file)
56
52
 
@@ -60,13 +56,13 @@ inject_site_pp(master, site_pp_path, site_pp)
60
56
  step 'Push Changes'
61
57
  git_add_commit_push(master, 'production', 'Update site.pp and add module.', git_environments_path)
62
58
 
63
- #Tests
64
59
  step 'Deploy Environments via r10k'
65
60
  on(master, "#{r10k_fqp} deploy environment -v -p")
66
61
 
67
- step 'Plug-in Sync Agents'
68
- on(agents, puppet("plugin download --server #{master}"))
62
+ step 'Manually Install the "motd" Module from the Forge'
63
+ on(master, puppet("module install puppetlabs-motd --modulepath #{moduledir}"))
69
64
 
65
+ #Tests
70
66
  agents.each do |agent|
71
67
  step 'Run Puppet Agent Against "production" Environment'
72
68
  on(agent, puppet('agent', '--test', '--environment production'), :acceptable_exit_codes => 2) do |result|
@@ -80,14 +76,12 @@ agents.each do |agent|
80
76
  end
81
77
 
82
78
  step 'Use r10k to Purge Unmanaged Modules'
83
- on(master, "#{r10k_fqp} puppetfile purge -v", :acceptable_exit_codes => 1)
79
+ on(master, "#{r10k_fqp} puppetfile purge -v --puppetfile #{puppet_file_path} --moduledir #{moduledir}")
84
80
 
85
81
  #Agent will fail because r10k will purge the "motd" module
86
82
  agents.each do |agent|
87
83
  step 'Attempt to Run Puppet Agent'
88
- on(agent, puppet('agent', '--test', '--environment production'), :acceptable_exit_codes => 0) do |result|
89
- expect_failure('Expected to fail due to CODEMGMT-78') do
90
- assert_match(error_message_regex, result.stderr, 'Expected error was not detected!')
91
- end
84
+ on(agent, puppet('agent', '--test', '--environment production'), :acceptable_exit_codes => 1) do |result|
85
+ assert_match(/Could not find declared class motd/, result.stderr, 'Module was not purged')
92
86
  end
93
87
  end
@@ -116,6 +116,9 @@ module R10K
116
116
 
117
117
  if @settings.dig(:overrides, :purging, :purge_levels).include?(:deployment)
118
118
  logger.debug("Purging unmanaged environments for deployment...")
119
+ deployment.sources.each do |source|
120
+ source.reload!
121
+ end
119
122
  deployment.purge!
120
123
  end
121
124
  ensure
@@ -67,15 +67,20 @@ module R10K
67
67
  exit(8)
68
68
  end
69
69
 
70
+ # Set up authorization from license file if it wasn't
71
+ # already set via the config
70
72
  def setup_authorization
71
- begin
72
- license = R10K::Util::License.load
73
+ if PuppetForge::Connection.authorization.nil?
74
+ begin
75
+ license = R10K::Util::License.load
73
76
 
74
- if license.respond_to?(:authorization_token)
75
- PuppetForge::Connection.authorization = license.authorization_token
77
+ if license.respond_to?(:authorization_token)
78
+ logger.debug "Using token from license to connect to the Forge."
79
+ PuppetForge::Connection.authorization = license.authorization_token
80
+ end
81
+ rescue R10K::Error => e
82
+ logger.warn e.message
76
83
  end
77
- rescue R10K::Error => e
78
- logger.warn e.message
79
84
  end
80
85
  end
81
86
 
@@ -111,6 +111,6 @@ class R10K::Git::Cache
111
111
 
112
112
  # Reformat the remote name into something that can be used as a directory
113
113
  def sanitized_dirname
114
- @sanitized_dirname ||= @remote.gsub(/[^@\w\.-]/, '-')
114
+ @sanitized_dirname ||= @remote.gsub(/(\w+:\/\/)(.*)(@)/, '\1').gsub(/[^@\w\.-]/, '-')
115
115
  end
116
116
  end
@@ -63,6 +63,13 @@ module R10K
63
63
  def call
64
64
  with_setting(:baseurl) { |value| PuppetForge.host = value }
65
65
  with_setting(:proxy) { |value| PuppetForge::Connection.proxy = value }
66
+ with_setting(:authorization_token) { |value|
67
+ if @settings[:baseurl]
68
+ PuppetForge::Connection.authorization = value
69
+ else
70
+ raise R10K::Error, "Cannot specify a Forge authorization token without configuring a custom baseurl."
71
+ end
72
+ }
66
73
  end
67
74
  end
68
75
  end
@@ -83,7 +83,11 @@ class R10K::Module::Forge < R10K::Module::Base
83
83
  def expected_version
84
84
  if @expected_version == :latest
85
85
  begin
86
- @expected_version = @v3_module.current_release.version
86
+ if @v3_module.current_release
87
+ @expected_version = @v3_module.current_release.version
88
+ else
89
+ raise PuppetForge::ReleaseNotFound, _("The module %{title} does not appear to have any published releases, cannot determine latest version.") % { title: @title }
90
+ end
87
91
  rescue Faraday::ResourceNotFound => e
88
92
  raise PuppetForge::ReleaseNotFound, _("The module %{title} does not exist on %{url}.") % {title: @title, url: PuppetForge::V3::Release.conn.url_prefix}, e.backtrace
89
93
  end
@@ -0,0 +1,195 @@
1
+ module R10K
2
+ module ModuleLoader
3
+ class Puppetfile
4
+
5
+ DEFAULT_MODULEDIR = 'modules'
6
+ DEFAULT_PUPPETFILE_NAME = 'Puppetfile'
7
+ DEFAULT_FORGE_API = 'forgeapi.puppetlabs.com'
8
+
9
+ attr_accessor :default_branch_override, :environment
10
+ attr_reader :modules, :moduledir,
11
+ :managed_directories, :desired_contents, :purge_exclusions
12
+
13
+ # @param basedir [String] The path that contains the moduledir &
14
+ # Puppetfile by default. May be an environment, project, or
15
+ # simple directory.
16
+ # @param puppetfile [String] The path to the Puppetfile, either an
17
+ # absolute full path or a relative path with regards to the basedir.
18
+ # @param moduledir [String] The path to the moduledir, either an
19
+ # absolute full path or a relative path with regards to the basedir.
20
+ # @param forge [String] The url (without protocol) to the Forge
21
+ # @param overrides [Hash] Configuration for loaded modules' behavior
22
+ # @param environment [R10K::Environment] When provided, the environment
23
+ # in which loading takes place
24
+ def initialize(basedir:,
25
+ moduledir: DEFAULT_MODULEDIR,
26
+ puppetfile: DEFAULT_PUPPETFILE_NAME,
27
+ forge: DEFAULT_FORGE_API,
28
+ overrides: {},
29
+ environment: nil)
30
+
31
+ @basedir = cleanpath(basedir)
32
+ @moduledir = resolve_path(@basedir, moduledir)
33
+ @puppetfile = resolve_path(@basedir, puppetfile)
34
+ @forge = forge
35
+ @overrides = overrides
36
+ @environment = environment
37
+ @default_branch_override = @overrides.dig(:environments, :default_branch_override)
38
+
39
+ @modules = []
40
+
41
+ @managed_directories = []
42
+ @desired_contents = []
43
+ @purge_exclusions = []
44
+ end
45
+
46
+ def load
47
+ dsl = R10K::ModuleLoader::Puppetfile::DSL.new(self)
48
+ dsl.instance_eval(puppetfile_content(@puppetfile), @puppetfile)
49
+
50
+ validate_no_duplicate_names(@modules)
51
+ @modules
52
+
53
+ managed_content = @modules.group_by(&:dirname)
54
+
55
+ @managed_directories = determine_managed_directories(managed_content)
56
+ @desired_contents = determine_desired_contents(managed_content)
57
+ @purge_exclusions = determine_purge_exclusions(@managed_directories)
58
+
59
+ {
60
+ modules: @modules,
61
+ managed_directories: @managed_directories,
62
+ desired_contents: @desired_contents,
63
+ purge_exclusions: @purge_exclusions
64
+ }
65
+
66
+ rescue SyntaxError, LoadError, ArgumentError, NameError => e
67
+ raise R10K::Error.wrap(e, _("Failed to evaluate %{path}") % {path: @puppetfile})
68
+ end
69
+
70
+
71
+ ##
72
+ ## set_forge, set_moduledir, and add_module are used directly by the DSL class
73
+ ##
74
+
75
+ # @param [String] forge
76
+ def set_forge(forge)
77
+ @forge = forge
78
+ end
79
+
80
+ # @param [String] moduledir
81
+ def set_moduledir(moduledir)
82
+ @moduledir = resolve_path(@basedir, moduledir)
83
+ end
84
+
85
+ # @param [String] name
86
+ # @param [Hash, String, Symbol, nil] module_info Calling with
87
+ # anything but a Hash is deprecated. The DSL will now convert
88
+ # String and Symbol versions to Hashes of the shape
89
+ # { version: <String or Symbol> }
90
+ #
91
+ # String inputs should be valid module versions, the Symbol
92
+ # `:latest` is allowed, as well as `nil`.
93
+ #
94
+ # Non-Hash inputs are only ever used by Forge modules. In
95
+ # future versions this method will require the caller (the
96
+ # DSL class, not the Puppetfile author) to do this conversion
97
+ # itself.
98
+ #
99
+ def add_module(name, module_info)
100
+ if !module_info.is_a?(Hash)
101
+ module_info = { version: module_info }
102
+ end
103
+
104
+ module_info[:overrides] = @overrides
105
+
106
+ if install_path = module_info.delete(:install_path)
107
+ install_path = resolve_path(@basedir, install_path)
108
+ validate_install_path(install_path, name)
109
+ else
110
+ install_path = @moduledir
111
+ end
112
+
113
+ if @default_branch_override
114
+ module_info[:default_branch_override] = @default_branch_override
115
+ end
116
+
117
+ mod = R10K::Module.new(name, install_path, module_info, @environment)
118
+ mod.origin = :puppetfile
119
+
120
+ # Do not save modules if they would conflict with the attached
121
+ # environment
122
+ if @environment && @environment.module_conflicts?(mod)
123
+ return @modules
124
+ end
125
+
126
+ @modules << mod
127
+ end
128
+
129
+ private
130
+
131
+ # @param [Array<R10K::Module>] modules
132
+ def validate_no_duplicate_names(modules)
133
+ dupes = modules
134
+ .group_by { |mod| mod.name }
135
+ .select { |_, mods| mods.size > 1 }
136
+ .map(&:first)
137
+ unless dupes.empty?
138
+ msg = _('Puppetfiles cannot contain duplicate module names.')
139
+ msg += ' '
140
+ msg += _("Remove the duplicates of the following modules: %{dupes}" % { dupes: dupes.join(' ') })
141
+ raise R10K::Error.new(msg)
142
+ end
143
+ end
144
+
145
+ def resolve_path(base, path)
146
+ if Pathname.new(path).absolute?
147
+ cleanpath(path)
148
+ else
149
+ cleanpath(File.join(base, path))
150
+ end
151
+ end
152
+
153
+ def validate_install_path(path, modname)
154
+ unless /^#{Regexp.escape(@basedir)}.*/ =~ path
155
+ raise R10K::Error.new("Puppetfile cannot manage content '#{modname}' outside of containing environment: #{path} is not within #{@basedir}")
156
+ end
157
+
158
+ true
159
+ end
160
+
161
+ def determine_managed_directories(managed_content)
162
+ managed_content.keys.reject { |dir| dir == @basedir }
163
+ end
164
+
165
+ # Returns an array of the full paths to all the content being managed.
166
+ # @return [Array<String>]
167
+ def determine_desired_contents(managed_content)
168
+ managed_content.flat_map do |install_path, mods|
169
+ mods.collect { |mod| File.join(install_path, mod.name) }
170
+ end
171
+ end
172
+
173
+ def determine_purge_exclusions(managed_dirs)
174
+ if environment && environment.respond_to?(:desired_contents)
175
+ managed_dirs + environment.desired_contents
176
+ else
177
+ managed_dirs
178
+ end
179
+ end
180
+
181
+ # .cleanpath is as close to a canonical path as we can do without touching
182
+ # the filesystem. The .realpath methods will choke if some of the
183
+ # intermediate paths are missing, even though in some cases we will create
184
+ # them later as needed.
185
+ def cleanpath(path)
186
+ Pathname.new(path).cleanpath.to_s
187
+ end
188
+
189
+ # For testing purposes only
190
+ def puppetfile_content(path)
191
+ File.read(path)
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,37 @@
1
+ module R10K
2
+ module ModuleLoader
3
+ class Puppetfile
4
+ class DSL
5
+ # A barebones implementation of the Puppetfile DSL
6
+ #
7
+ # @api private
8
+
9
+ def initialize(librarian)
10
+ @librarian = librarian
11
+ end
12
+
13
+ def mod(name, args = nil)
14
+ if args.is_a?(Hash)
15
+ opts = args
16
+ else
17
+ opts = { version: args }
18
+ end
19
+
20
+ @librarian.add_module(name, opts)
21
+ end
22
+
23
+ def forge(location)
24
+ @librarian.set_forge(location)
25
+ end
26
+
27
+ def moduledir(location)
28
+ @librarian.set_moduledir(location)
29
+ end
30
+
31
+ def method_missing(method, *args)
32
+ raise NoMethodError, _("unrecognized declaration '%{method}'") % {method: method}
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end