r10k 3.11.0 → 3.12.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/CHANGELOG.mkd +9 -0
  3. data/doc/dynamic-environments/configuration.mkd +7 -3
  4. data/doc/dynamic-environments/usage.mkd +26 -0
  5. data/doc/puppetfile.mkd +3 -4
  6. data/integration/tests/basic_functionality/basic_deployment.rb +176 -0
  7. data/lib/r10k/action/deploy/environment.rb +8 -1
  8. data/lib/r10k/action/deploy/module.rb +11 -6
  9. data/lib/r10k/action/puppetfile/check.rb +7 -5
  10. data/lib/r10k/action/puppetfile/install.rb +22 -16
  11. data/lib/r10k/action/puppetfile/purge.rb +12 -9
  12. data/lib/r10k/cli/deploy.rb +1 -0
  13. data/lib/r10k/cli/puppetfile.rb +0 -1
  14. data/lib/r10k/content_synchronizer.rb +16 -4
  15. data/lib/r10k/environment/base.rb +64 -11
  16. data/lib/r10k/environment/with_modules.rb +6 -10
  17. data/lib/r10k/git/stateful_repository.rb +7 -0
  18. data/lib/r10k/initializers.rb +1 -7
  19. data/lib/r10k/module/base.rb +5 -1
  20. data/lib/r10k/module/definition.rb +64 -0
  21. data/lib/r10k/module/forge.rb +10 -2
  22. data/lib/r10k/module/git.rb +22 -1
  23. data/lib/r10k/module/local.rb +2 -3
  24. data/lib/r10k/module/svn.rb +10 -0
  25. data/lib/r10k/module.rb +20 -2
  26. data/lib/r10k/module_loader/puppetfile/dsl.rb +8 -3
  27. data/lib/r10k/module_loader/puppetfile.rb +95 -29
  28. data/lib/r10k/puppetfile.rb +1 -2
  29. data/lib/r10k/settings.rb +11 -0
  30. data/lib/r10k/version.rb +1 -1
  31. data/locales/r10k.pot +75 -47
  32. data/spec/fixtures/unit/puppetfile/forge-override/Puppetfile +8 -0
  33. data/spec/fixtures/unit/puppetfile/various-modules/Puppetfile +9 -0
  34. data/spec/fixtures/unit/puppetfile/various-modules/Puppetfile.new +9 -0
  35. data/spec/r10k-mocks/mock_env.rb +3 -0
  36. data/spec/r10k-mocks/mock_source.rb +7 -3
  37. data/spec/unit/action/deploy/environment_spec.rb +80 -30
  38. data/spec/unit/action/deploy/module_spec.rb +50 -62
  39. data/spec/unit/action/puppetfile/check_spec.rb +17 -5
  40. data/spec/unit/action/puppetfile/install_spec.rb +42 -36
  41. data/spec/unit/action/puppetfile/purge_spec.rb +15 -17
  42. data/spec/unit/action/runner_spec.rb +0 -8
  43. data/spec/unit/environment/base_spec.rb +30 -17
  44. data/spec/unit/environment/git_spec.rb +2 -2
  45. data/spec/unit/environment/svn_spec.rb +4 -3
  46. data/spec/unit/environment/with_modules_spec.rb +2 -1
  47. data/spec/unit/module/base_spec.rb +8 -8
  48. data/spec/unit/module/forge_spec.rb +32 -4
  49. data/spec/unit/module/git_spec.rb +51 -10
  50. data/spec/unit/module/svn_spec.rb +18 -6
  51. data/spec/unit/module_loader/puppetfile_spec.rb +90 -30
  52. data/spec/unit/puppetfile_spec.rb +2 -2
  53. data/spec/unit/settings_spec.rb +25 -2
  54. metadata +7 -2
@@ -1,7 +1,7 @@
1
- require 'r10k/puppetfile'
2
- require 'r10k/util/cleaner'
3
1
  require 'r10k/action/base'
4
2
  require 'r10k/errors/formatting'
3
+ require 'r10k/module_loader/puppetfile'
4
+ require 'r10k/util/cleaner'
5
5
 
6
6
  module R10K
7
7
  module Action
@@ -9,13 +9,16 @@ module R10K
9
9
  class Purge < R10K::Action::Base
10
10
 
11
11
  def call
12
- pf = R10K::Puppetfile.new(@root,
13
- {moduledir: @moduledir,
14
- puppetfile_path: @puppetfile})
15
- pf.load!
16
- R10K::Util::Cleaner.new(pf.managed_directories,
17
- pf.desired_contents,
18
- pf.purge_exclusions).purge!
12
+ options = { basedir: @root }
13
+
14
+ options[:moduledir] = @moduledir if @moduledir
15
+ options[:puppetfile] = @puppetfile if @puppetfile
16
+
17
+ loader = R10K::ModuleLoader::Puppetfile.new(**options)
18
+ loaded_content = loader.load!
19
+ R10K::Util::Cleaner.new(loaded_content[:managed_directories],
20
+ loaded_content[:desired_contents],
21
+ loaded_content[:purge_exclusions]).purge!
19
22
 
20
23
  true
21
24
  rescue => e
@@ -69,6 +69,7 @@ scheduled. On subsequent deployments, Puppetfile deployment will default to off.
69
69
 
70
70
  flag :p, :puppetfile, 'Deploy modules (deprecated, use -m)'
71
71
  flag :m, :modules, 'Deploy modules'
72
+ flag nil, :incremental, 'Used with the --modules flag, only update those modules whose definition has changed or whose definition allows the version to float'
72
73
  option nil, :'default-branch-override', 'Specify a branchname to override the default branch in the puppetfile',
73
74
  argument: :required
74
75
 
@@ -1,5 +1,4 @@
1
1
  require 'r10k/cli'
2
- require 'r10k/puppetfile'
3
2
  require 'r10k/action/puppetfile'
4
3
 
5
4
  require 'cri'
@@ -8,24 +8,31 @@ module R10K
8
8
  end
9
9
 
10
10
  def self.serial_sync(modules)
11
+ updated_modules = []
11
12
  modules.each do |mod|
12
- mod.sync
13
+ updated = mod.sync
14
+ updated_modules << mod.name if updated
13
15
  end
16
+ updated_modules
14
17
  end
15
18
 
19
+ # Returns a Queue of the names of modules actually updated
16
20
  def self.concurrent_accept(modules, visitor, loader, pool_size, logger)
17
21
  mods_queue = modules_visit_queue(modules, visitor, loader)
18
22
  sync_queue(mods_queue, pool_size, logger)
19
23
  end
20
24
 
25
+ # Returns a Queue of the names of modules actually updated
21
26
  def self.concurrent_sync(modules, pool_size, logger)
22
27
  mods_queue = modules_sync_queue(modules)
23
28
  sync_queue(mods_queue, pool_size, logger)
24
29
  end
25
30
 
31
+ # Returns a Queue of the names of modules actually updated
26
32
  def self.sync_queue(mods_queue, pool_size, logger)
27
33
  logger.debug _("Updating modules with %{pool_size} threads") % {pool_size: pool_size}
28
- thread_pool = pool_size.times.map { sync_thread(mods_queue, logger) }
34
+ updated_modules = Queue.new
35
+ thread_pool = pool_size.times.map { sync_thread(mods_queue, logger, updated_modules) }
29
36
  thread_exception = nil
30
37
 
31
38
  # If any threads raise an exception the deployment is considered a failure.
@@ -33,6 +40,8 @@ module R10K
33
40
  # current work, then re-raise the first exception caught.
34
41
  begin
35
42
  thread_pool.each(&:join)
43
+ # Return the list of all modules that were actually updated
44
+ updated_modules
36
45
  rescue => e
37
46
  logger.error _("Error during concurrent deploy of a module: %{message}") % {message: e.message}
38
47
  mods_queue.clear
@@ -65,11 +74,14 @@ module R10K
65
74
  modules_by_cachedir.values.each {|mods| queue << mods }
66
75
  end
67
76
 
68
- def self.sync_thread(mods_queue, logger)
77
+ def self.sync_thread(mods_queue, logger, updated_modules)
69
78
  Thread.new do
70
79
  begin
71
80
  while mods = mods_queue.pop(true) do
72
- mods.each { |mod| mod.sync }
81
+ mods.each do |mod|
82
+ updated = mod.sync
83
+ updated_modules << mod.name if updated
84
+ end
73
85
  end
74
86
  rescue ThreadError => e
75
87
  logger.debug _("Module thread %{id} exiting: %{message}") % {message: e.message, id: Thread.current.object_id}
@@ -1,5 +1,8 @@
1
- require 'r10k/util/subprocess'
1
+ require 'r10k/content_synchronizer'
2
2
  require 'r10k/logging'
3
+ require 'r10k/module_loader/puppetfile'
4
+ require 'r10k/util/cleaner'
5
+ require 'r10k/util/subprocess'
3
6
 
4
7
  # This class defines a common interface for environment implementations.
5
8
  #
@@ -34,6 +37,10 @@ class R10K::Environment::Base
34
37
  # @return [String] The puppetfile name (relative)
35
38
  attr_reader :puppetfile_name
36
39
 
40
+ attr_reader :managed_directories, :desired_contents
41
+
42
+ attr_reader :loader
43
+
37
44
  # Initialize the given environment.
38
45
  #
39
46
  # @param name [String] The unique name describing this environment.
@@ -57,6 +64,20 @@ class R10K::Environment::Base
57
64
  force: @overrides.dig(:modules, :force),
58
65
  puppetfile_name: @puppetfile_name})
59
66
  @puppetfile.environment = self
67
+
68
+ loader_options = { basedir: @full_path, overrides: @overrides, environment: self }
69
+ loader_options[:puppetfile] = @puppetfile_name if @puppetfile_name
70
+
71
+ @loader = R10K::ModuleLoader::Puppetfile.new(**loader_options)
72
+
73
+ if @overrides.dig(:environments, :incremental)
74
+ @loader.load_metadata
75
+ end
76
+
77
+ @base_modules = nil
78
+ @purge_exclusions = nil
79
+ @managed_directories = [ @full_path ]
80
+ @desired_contents = []
60
81
  end
61
82
 
62
83
  # Synchronize the given environment.
@@ -106,8 +127,11 @@ class R10K::Environment::Base
106
127
  # @return [Array<R10K::Module::Base>] All modules defined in the Puppetfile
107
128
  # associated with this environment.
108
129
  def modules
109
- @puppetfile.load
110
- @puppetfile.modules
130
+ if @base_modules.nil?
131
+ load_puppetfile_modules
132
+ end
133
+
134
+ @base_modules
111
135
  end
112
136
 
113
137
  # @return [Array<R10K::Module::Base>] Whether or not the given module
@@ -123,29 +147,50 @@ class R10K::Environment::Base
123
147
  end
124
148
  end
125
149
 
150
+
151
+ # Returns a Queue of the names of modules actually updated
126
152
  def deploy
127
- puppetfile.load(@overrides.dig(:environments, :default_branch_override))
153
+ if @base_modules.nil?
154
+ load_puppetfile_modules
155
+ end
128
156
 
129
- puppetfile.sync
157
+ if ! @base_modules.empty?
158
+ pool_size = @overrides.dig(:modules, :pool_size)
159
+ updated_modules = R10K::ContentSynchronizer.concurrent_sync(@base_modules, pool_size, logger)
160
+ end
130
161
 
131
162
  if (@overrides.dig(:purging, :purge_levels) || []).include?(:puppetfile)
132
163
  logger.debug("Purging unmanaged Puppetfile content for environment '#{dirname}'...")
133
- R10K::Util::Cleaner.new(puppetfile.managed_directories,
134
- puppetfile.desired_contents,
135
- puppetfile.purge_exclusions).purge!
164
+ @puppetfile_cleaner.purge!
136
165
  end
166
+
167
+ updated_modules
168
+ end
169
+
170
+ def load_puppetfile_modules
171
+ loaded_content = @loader.load
172
+ @base_modules = loaded_content[:modules]
173
+
174
+ @purge_exclusions = determine_purge_exclusions(loaded_content[:managed_directories],
175
+ loaded_content[:desired_contents])
176
+
177
+ @puppetfile_cleaner = R10K::Util::Cleaner.new(loaded_content[:managed_directories],
178
+ loaded_content[:desired_contents],
179
+ loaded_content[:purge_exclusions])
137
180
  end
138
181
 
139
182
  def whitelist(user_whitelist=[])
140
183
  user_whitelist.collect { |pattern| File.join(@full_path, pattern) }
141
184
  end
142
185
 
143
- def purge_exclusions
186
+ def determine_purge_exclusions(pf_managed_dirs = @puppetfile.managed_directories,
187
+ pf_desired_contents = @puppetfile.desired_contents)
188
+
144
189
  list = [File.join(@full_path, '.r10k-deploy.json')].to_set
145
190
 
146
- list += @puppetfile.managed_directories
191
+ list += pf_managed_dirs
147
192
 
148
- list += @puppetfile.desired_contents.flat_map do |item|
193
+ list += pf_desired_contents.flat_map do |item|
149
194
  desired_tree = []
150
195
 
151
196
  if File.directory?(item)
@@ -163,6 +208,14 @@ class R10K::Environment::Base
163
208
  list.to_a
164
209
  end
165
210
 
211
+ def purge_exclusions
212
+ if @purge_exclusions.nil?
213
+ load_puppetfile_modules
214
+ end
215
+
216
+ @purge_exclusions
217
+ end
218
+
166
219
  def generate_types!
167
220
  argv = [R10K::Settings.puppet_path, 'generate', 'types', '--environment', dirname, '--environmentpath', basedir, '--config', R10K::Settings.puppet_conf]
168
221
  subproc = R10K::Util::Subprocess.new(argv)
@@ -23,6 +23,7 @@ class R10K::Environment::WithModules < R10K::Environment::Base
23
23
  def initialize(name, basedir, dirname, options = {})
24
24
  super
25
25
 
26
+ @all_modules = nil
26
27
  @managed_content = {}
27
28
  @modules = []
28
29
  @moduledir = case options[:moduledir]
@@ -43,10 +44,12 @@ class R10K::Environment::WithModules < R10K::Environment::Base
43
44
  # - The r10k environment object
44
45
  # - A Puppetfile in the environment's content
45
46
  def modules
46
- return @modules if puppetfile.nil?
47
+ if @all_modules.nil?
48
+ puppetfile_modules = super()
49
+ @all_modules = @modules + puppetfile_modules
50
+ end
47
51
 
48
- puppetfile.load unless puppetfile.loaded?
49
- @modules + puppetfile.modules
52
+ @all_modules
50
53
  end
51
54
 
52
55
  def module_conflicts?(mod_b)
@@ -126,13 +129,6 @@ class R10K::Environment::WithModules < R10K::Environment::Base
126
129
 
127
130
  include R10K::Util::Purgeable
128
131
 
129
- # Returns an array of the full paths that can be purged.
130
- # @note This implements a required method for the Purgeable mixin
131
- # @return [Array<String>]
132
- def managed_directories
133
- [@full_path]
134
- end
135
-
136
132
  # Returns an array of the full paths of filenames that should exist. Files
137
133
  # inside managed_directories that are not listed in desired_contents will
138
134
  # be purged.
@@ -35,6 +35,7 @@ class R10K::Git::StatefulRepository
35
35
  @cache.resolve(ref)
36
36
  end
37
37
 
38
+ # Returns true if the sync actually updated the repo, false otherwise
38
39
  def sync(ref, force=true)
39
40
  @cache.sync if sync_cache?(ref)
40
41
 
@@ -46,6 +47,7 @@ class R10K::Git::StatefulRepository
46
47
 
47
48
  workdir_status = status(ref)
48
49
 
50
+ updated = true
49
51
  case workdir_status
50
52
  when :absent
51
53
  logger.debug(_("Cloning %{repo_path} and checking out %{ref}") % {repo_path: @repo.path, ref: ref })
@@ -64,15 +66,20 @@ class R10K::Git::StatefulRepository
64
66
  @repo.checkout(sha, {:force => force})
65
67
  else
66
68
  logger.warn(_("Skipping %{repo_path} due to local modifications") % {repo_path: @repo.path})
69
+ updated = false
67
70
  end
68
71
  else
69
72
  logger.debug(_("%{repo_path} is already at Git ref %{ref}") % {repo_path: @repo.path, ref: ref })
73
+ updated = false
70
74
  end
75
+ updated
71
76
  end
72
77
 
73
78
  def status(ref)
74
79
  if !@repo.exist?
75
80
  :absent
81
+ elsif !@cache.exist?
82
+ :mismatched
76
83
  elsif !@repo.git_dir.exist?
77
84
  :mismatched
78
85
  elsif !@repo.git_dir.directory?
@@ -66,13 +66,7 @@ module R10K
66
66
  def call
67
67
  with_setting(:baseurl) { |value| PuppetForge.host = value }
68
68
  with_setting(:proxy) { |value| PuppetForge::Connection.proxy = value }
69
- with_setting(:authorization_token) { |value|
70
- if @settings[:baseurl]
71
- PuppetForge::Connection.authorization = value
72
- else
73
- raise R10K::Error, "Cannot specify a Forge authorization token without configuring a custom baseurl."
74
- end
75
- }
69
+ with_setting(:authorization_token) { |value| PuppetForge::Connection.authorization = value }
76
70
  end
77
71
  end
78
72
  end
@@ -1,9 +1,12 @@
1
1
  require 'r10k/module'
2
+ require 'r10k/logging'
2
3
  require 'puppet_forge'
3
4
 
4
5
  # This class defines a common interface for module implementations.
5
6
  class R10K::Module::Base
6
7
 
8
+ include R10K::Logging
9
+
7
10
  # @!attribute [r] title
8
11
  # @return [String] The forward slash separated owner and name of the module
9
12
  attr_reader :title
@@ -47,7 +50,7 @@ class R10K::Module::Base
47
50
 
48
51
  # @param title [String]
49
52
  # @param dirname [String]
50
- # @param args [Array]
53
+ # @param args [Hash]
51
54
  def initialize(title, dirname, args, environment=nil)
52
55
  @title = PuppetForge::V3.normalize_name(title)
53
56
  @dirname = dirname
@@ -103,6 +106,7 @@ class R10K::Module::Base
103
106
 
104
107
  # Synchronize this module with the indicated state.
105
108
  # @param [Hash] opts Deprecated
109
+ # @return [Boolean] true if the module was updated, false otherwise
106
110
  def sync(opts={})
107
111
  raise NotImplementedError
108
112
  end
@@ -0,0 +1,64 @@
1
+ require 'r10k/module'
2
+
3
+ class R10K::Module::Definition < R10K::Module::Base
4
+
5
+ attr_reader :version
6
+
7
+ def initialize(name, dirname:, args:, implementation:, environment: nil)
8
+ @original_name = name
9
+ @original_args = args.dup
10
+ @implementation = implementation
11
+ @version = implementation.statically_defined_version(name, args)
12
+
13
+ super(name, dirname, args, environment)
14
+ end
15
+
16
+ def to_implementation
17
+ mod = @implementation.new(@title, @dirname, @original_args, @environment)
18
+
19
+ mod.origin = origin
20
+ mod.spec_deletable = spec_deletable
21
+
22
+ mod
23
+ end
24
+
25
+ # syncing is a noop for module definitions
26
+ # Returns false to inidicate the module was not updated
27
+ def sync(args = {})
28
+ logger.debug1(_("Not updating module %{name}, assuming content unchanged") % {name: name})
29
+ false
30
+ end
31
+
32
+ def status
33
+ :insync
34
+ end
35
+
36
+ def properties
37
+ type = nil
38
+
39
+ if @args[:type]
40
+ type = @args[:type]
41
+ elsif @args[:ref] || @args[:commit] || @args[:branch] || @args[:tag]
42
+ type = 'git'
43
+ elsif @args[:svn]
44
+ # This logic is clear and included for completeness sake, though at
45
+ # this time module definitions do not support SVN versions.
46
+ type = 'svn'
47
+ else
48
+ type = 'forge'
49
+ end
50
+
51
+ {
52
+ expected: version,
53
+ # We can't get the value for `actual` here because that requires the
54
+ # implementation (and potentially expensive operations by the
55
+ # implementation). Some consumers will check this value, if it exists
56
+ # and if not, fall back to the expected version. That is the correct
57
+ # behavior when assuming modules are unchanged, and why `actual` is set
58
+ # to `nil` here.
59
+ actual: nil,
60
+ type: type
61
+ }
62
+ end
63
+ end
64
+
@@ -25,6 +25,10 @@ class R10K::Module::Forge < R10K::Module::Base
25
25
  expected_version == :latest || expected_version.nil? || PuppetForge::Util.version_valid?(expected_version)
26
26
  end
27
27
 
28
+ def self.statically_defined_version(name, args)
29
+ args[:version] if args[:version].is_a?(String)
30
+ end
31
+
28
32
  # @!attribute [r] metadata
29
33
  # @api private
30
34
  # @return [PuppetForge::Metadata]
@@ -35,8 +39,6 @@ class R10K::Module::Forge < R10K::Module::Base
35
39
  # @return [PuppetForge::V3::Module] The Puppet Forge module metadata
36
40
  attr_reader :v3_module
37
41
 
38
- include R10K::Logging
39
-
40
42
  include R10K::Util::Setopts
41
43
 
42
44
  def initialize(title, dirname, opts, environment=nil)
@@ -58,18 +60,24 @@ class R10K::Module::Forge < R10K::Module::Base
58
60
  end
59
61
 
60
62
  # @param [Hash] opts Deprecated
63
+ # @return [Boolean] true if the module was updated, false otherwise
61
64
  def sync(opts={})
65
+ updated = false
62
66
  if should_sync?
63
67
  case status
64
68
  when :absent
65
69
  install
70
+ updated = true
66
71
  when :outdated
67
72
  upgrade
73
+ updated = true
68
74
  when :mismatched
69
75
  reinstall
76
+ updated = true
70
77
  end
71
78
  maybe_delete_spec_dir
72
79
  end
80
+ updated
73
81
  end
74
82
 
75
83
  def properties