r10k 3.9.0 → 3.9.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +1 -1
  3. data/.github/workflows/stale.yml +19 -0
  4. data/CHANGELOG.mkd +5 -0
  5. data/doc/dynamic-environments/configuration.mkd +6 -6
  6. data/lib/r10k/action/base.rb +8 -1
  7. data/lib/r10k/action/deploy/display.rb +39 -9
  8. data/lib/r10k/action/deploy/environment.rb +63 -40
  9. data/lib/r10k/action/deploy/module.rb +47 -28
  10. data/lib/r10k/action/puppetfile/check.rb +3 -1
  11. data/lib/r10k/action/puppetfile/install.rb +20 -23
  12. data/lib/r10k/action/puppetfile/purge.rb +8 -2
  13. data/lib/r10k/content_synchronizer.rb +83 -0
  14. data/lib/r10k/deployment.rb +1 -1
  15. data/lib/r10k/environment/base.rb +21 -1
  16. data/lib/r10k/environment/git.rb +0 -3
  17. data/lib/r10k/environment/svn.rb +4 -6
  18. data/lib/r10k/environment/with_modules.rb +18 -10
  19. data/lib/r10k/module.rb +1 -1
  20. data/lib/r10k/module/base.rb +17 -1
  21. data/lib/r10k/module/forge.rb +24 -18
  22. data/lib/r10k/module/git.rb +22 -13
  23. data/lib/r10k/module/local.rb +1 -0
  24. data/lib/r10k/module/svn.rb +11 -8
  25. data/lib/r10k/puppetfile.rb +55 -70
  26. data/lib/r10k/source/base.rb +4 -0
  27. data/lib/r10k/source/git.rb +14 -6
  28. data/lib/r10k/source/hash.rb +1 -3
  29. data/lib/r10k/source/svn.rb +0 -2
  30. data/lib/r10k/util/cleaner.rb +21 -0
  31. data/lib/r10k/version.rb +1 -1
  32. data/locales/r10k.pot +51 -59
  33. data/spec/r10k-mocks/mock_source.rb +1 -1
  34. data/spec/shared-examples/puppetfile-action.rb +7 -7
  35. data/spec/unit/action/deploy/display_spec.rb +32 -6
  36. data/spec/unit/action/deploy/environment_spec.rb +76 -48
  37. data/spec/unit/action/deploy/module_spec.rb +139 -31
  38. data/spec/unit/action/puppetfile/check_spec.rb +2 -2
  39. data/spec/unit/action/puppetfile/install_spec.rb +31 -10
  40. data/spec/unit/action/puppetfile/purge_spec.rb +25 -5
  41. data/spec/unit/module/forge_spec.rb +15 -13
  42. data/spec/unit/module/git_spec.rb +8 -0
  43. data/spec/unit/module_spec.rb +5 -5
  44. data/spec/unit/puppetfile_spec.rb +40 -26
  45. data/spec/unit/util/purgeable_spec.rb +2 -8
  46. metadata +5 -2
@@ -8,7 +8,9 @@ module R10K
8
8
  class Check < R10K::Action::Base
9
9
 
10
10
  def call
11
- pf = R10K::Puppetfile.new(@root, @moduledir, @puppetfile)
11
+ pf = R10K::Puppetfile.new(@root,
12
+ {moduledir: @moduledir,
13
+ puppetfile_path: @puppetfile})
12
14
  begin
13
15
  pf.load!
14
16
  $stderr.puts _("Syntax OK")
@@ -2,6 +2,7 @@ require 'r10k/puppetfile'
2
2
  require 'r10k/errors/formatting'
3
3
  require 'r10k/action/visitor'
4
4
  require 'r10k/action/base'
5
+ require 'r10k/util/cleaner'
5
6
 
6
7
  module R10K
7
8
  module Action
@@ -9,33 +10,29 @@ module R10K
9
10
  class Install < R10K::Action::Base
10
11
 
11
12
  def call
12
- @visit_ok = true
13
- pf = R10K::Puppetfile.new(@root, @moduledir, @puppetfile, nil , @force)
14
- pf.accept(self)
15
- @visit_ok
16
- end
17
-
18
- private
19
-
20
- include R10K::Action::Visitor
21
-
22
- def visit_puppetfile(pf)
23
- pf.load!
24
- yield
25
- pf.purge!
26
- end
27
-
28
- def visit_module(mod)
29
- @force ||= false
30
- logger.info _("Updating module %{mod_path}") % {mod_path: mod.path}
31
-
32
- if mod.respond_to?(:desired_ref) && mod.desired_ref == :control_branch
33
- logger.warn _("Cannot track control repo branch for content '%{name}' when not part of a 'deploy' action, will use default if available." % {name: mod.name})
13
+ @ok = true
14
+ begin
15
+ pf = R10K::Puppetfile.new(@root,
16
+ {moduledir: @moduledir,
17
+ puppetfile_path: @puppetfile,
18
+ force: @force || false})
19
+ pf.load!
20
+ pf.sync
21
+
22
+ R10K::Util::Cleaner.new(pf.managed_directories,
23
+ pf.desired_contents,
24
+ pf.purge_exclusions).purge!
25
+
26
+ rescue => e
27
+ @ok = false
28
+ logger.error R10K::Errors::Formatting.format_exception(e, @trace)
34
29
  end
35
30
 
36
- mod.sync(force: @force)
31
+ @ok
37
32
  end
38
33
 
34
+ private
35
+
39
36
  def allowed_initialize_opts
40
37
  super.merge(root: :self, puppetfile: :self, moduledir: :self, force: :self )
41
38
  end
@@ -1,4 +1,5 @@
1
1
  require 'r10k/puppetfile'
2
+ require 'r10k/util/cleaner'
2
3
  require 'r10k/action/base'
3
4
  require 'r10k/errors/formatting'
4
5
 
@@ -8,9 +9,14 @@ module R10K
8
9
  class Purge < R10K::Action::Base
9
10
 
10
11
  def call
11
- pf = R10K::Puppetfile.new(@root, @moduledir, @puppetfile)
12
+ pf = R10K::Puppetfile.new(@root,
13
+ {moduledir: @moduledir,
14
+ puppetfile_path: @puppetfile})
12
15
  pf.load!
13
- pf.purge!
16
+ R10K::Util::Cleaner.new(pf.managed_directories,
17
+ pf.desired_contents,
18
+ pf.purge_exclusions).purge!
19
+
14
20
  true
15
21
  rescue => e
16
22
  logger.error R10K::Errors::Formatting.format_exception(e, @trace)
@@ -0,0 +1,83 @@
1
+ module R10K
2
+ module ContentSynchronizer
3
+
4
+ def self.serial_accept(modules, visitor, loader)
5
+ visitor.visit(:puppetfile, loader) do
6
+ serial_sync(modules)
7
+ end
8
+ end
9
+
10
+ def self.serial_sync(modules)
11
+ modules.each do |mod|
12
+ mod.sync
13
+ end
14
+ end
15
+
16
+ def self.concurrent_accept(modules, visitor, loader, pool_size, logger)
17
+ mods_queue = modules_visit_queue(modules, visitor, loader)
18
+ sync_queue(mods_queue, pool_size, logger)
19
+ end
20
+
21
+ def self.concurrent_sync(modules, pool_size, logger)
22
+ mods_queue = modules_sync_queue(modules)
23
+ sync_queue(mods_queue, pool_size, logger)
24
+ end
25
+
26
+ def self.sync_queue(mods_queue, pool_size, logger)
27
+ logger.debug _("Updating modules with %{pool_size} threads") % {pool_size: pool_size}
28
+ thread_pool = pool_size.times.map { sync_thread(mods_queue, logger) }
29
+ thread_exception = nil
30
+
31
+ # If any threads raise an exception the deployment is considered a failure.
32
+ # In that event clear the queue, wait for other threads to finish their
33
+ # current work, then re-raise the first exception caught.
34
+ begin
35
+ thread_pool.each(&:join)
36
+ rescue => e
37
+ logger.error _("Error during concurrent deploy of a module: %{message}") % {message: e.message}
38
+ mods_queue.clear
39
+ thread_exception ||= e
40
+ retry
41
+ ensure
42
+ raise thread_exception unless thread_exception.nil?
43
+ end
44
+ end
45
+
46
+ def self.modules_visit_queue(modules, visitor, loader)
47
+ Queue.new.tap do |queue|
48
+ visitor.visit(:puppetfile, loader) do
49
+ enqueue_modules(queue, modules)
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.modules_sync_queue(modules)
55
+ Queue.new.tap do |queue|
56
+ enqueue_modules(queue, modules)
57
+ end
58
+ end
59
+
60
+ def self.enqueue_modules(queue, modules)
61
+ modules_by_cachedir = modules.group_by { |mod| mod.cachedir }
62
+ modules_without_vcs_cachedir = modules_by_cachedir.delete(:none) || []
63
+
64
+ modules_without_vcs_cachedir.each {|mod| queue << Array(mod) }
65
+ modules_by_cachedir.values.each {|mods| queue << mods }
66
+ end
67
+
68
+ def self.sync_thread(mods_queue, logger)
69
+ Thread.new do
70
+ begin
71
+ while mods = mods_queue.pop(true) do
72
+ mods.each { |mod| mod.sync }
73
+ end
74
+ rescue ThreadError => e
75
+ logger.debug _("Module thread %{id} exiting: %{message}") % {message: e.message, id: Thread.current.object_id}
76
+ Thread.exit
77
+ rescue => e
78
+ Thread.main.raise(e)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -118,7 +118,7 @@ module R10K
118
118
  raise R10K::Error, _("Unable to load sources; the supplied configuration does not define the 'sources' key")
119
119
  end
120
120
  @_sources = sources.map do |(name, hash)|
121
- R10K::Source.from_hash(name, hash)
121
+ R10K::Source.from_hash(name, hash.merge({overrides: @config[:overrides]}))
122
122
  end
123
123
  end
124
124
 
@@ -1,10 +1,13 @@
1
1
  require 'r10k/util/subprocess'
2
+ require 'r10k/logging'
2
3
 
3
4
  # This class defines a common interface for environment implementations.
4
5
  #
5
6
  # @since 1.3.0
6
7
  class R10K::Environment::Base
7
8
 
9
+ include R10K::Logging
10
+
8
11
  # @!attribute [r] name
9
12
  # @return [String] A name for this environment that is unique to the given source
10
13
  attr_reader :name
@@ -44,11 +47,15 @@ class R10K::Environment::Base
44
47
  @dirname = dirname
45
48
  @options = options
46
49
  @puppetfile_name = options.delete(:puppetfile_name)
50
+ @overrides = options.delete(:overrides) || {}
47
51
 
48
52
  @full_path = File.join(@basedir, @dirname)
49
53
  @path = Pathname.new(File.join(@basedir, @dirname))
50
54
 
51
- @puppetfile = R10K::Puppetfile.new(@full_path, nil, nil, @puppetfile_name)
55
+ @puppetfile = R10K::Puppetfile.new(@full_path,
56
+ {overrides: @overrides,
57
+ force: @overrides.dig(:modules, :force),
58
+ puppetfile_name: @puppetfile_name})
52
59
  @puppetfile.environment = self
53
60
  end
54
61
 
@@ -116,6 +123,19 @@ class R10K::Environment::Base
116
123
  end
117
124
  end
118
125
 
126
+ def deploy
127
+ puppetfile.load(@overrides.dig(:environments, :default_branch_override))
128
+
129
+ puppetfile.sync
130
+
131
+ if (@overrides.dig(:purging, :purge_levels) || []).include?(:puppetfile)
132
+ 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!
136
+ end
137
+ end
138
+
119
139
  def whitelist(user_whitelist=[])
120
140
  user_whitelist.collect { |pattern| File.join(@full_path, pattern) }
121
141
  end
@@ -1,4 +1,3 @@
1
- require 'r10k/logging'
2
1
  require 'r10k/puppetfile'
3
2
  require 'r10k/git/stateful_repository'
4
3
  require 'forwardable'
@@ -8,8 +7,6 @@ require 'forwardable'
8
7
  # @since 1.3.0
9
8
  class R10K::Environment::Git < R10K::Environment::WithModules
10
9
 
11
- include R10K::Logging
12
-
13
10
  R10K::Environment.register(:git, self)
14
11
  # Register git as the default environment type
15
12
  R10K::Environment.register(nil, self)
@@ -7,8 +7,6 @@ require 'r10k/util/setopts'
7
7
  # @since 1.3.0
8
8
  class R10K::Environment::SVN < R10K::Environment::Base
9
9
 
10
- include R10K::Logging
11
-
12
10
  R10K::Environment.register(:svn, self)
13
11
 
14
12
  # @!attribute [r] remote
@@ -46,12 +44,12 @@ class R10K::Environment::SVN < R10K::Environment::Base
46
44
  super
47
45
  setopts(options, {
48
46
  # Standard option interface
49
- :source => :remote,
50
- :version => :expected_revision,
51
- :type => ::R10K::Util::Setopts::Ignore,
47
+ :source => :remote,
48
+ :version => :expected_revision,
49
+ :type => ::R10K::Util::Setopts::Ignore,
52
50
 
53
51
  # Type-specific options
54
- :remote => :self,
52
+ :remote => :self,
55
53
  :username => :self,
56
54
  :password => :self,
57
55
  })
@@ -1,4 +1,3 @@
1
- require 'r10k/logging'
2
1
  require 'r10k/util/purgeable'
3
2
 
4
3
  # This abstract base class implements an environment that can include module
@@ -7,8 +6,6 @@ require 'r10k/util/purgeable'
7
6
  # @since 3.4.0
8
7
  class R10K::Environment::WithModules < R10K::Environment::Base
9
8
 
10
- include R10K::Logging
11
-
12
9
  # @!attribute [r] moduledir
13
10
  # @return [String] The directory to install environment-defined modules
14
11
  # into (default: #{basedir}/modules)
@@ -78,28 +75,39 @@ class R10K::Environment::WithModules < R10K::Environment::Base
78
75
  def accept(visitor)
79
76
  visitor.visit(:environment, self) do
80
77
  @modules.each do |mod|
81
- mod.accept(visitor)
78
+ mod.sync
82
79
  end
83
80
 
84
81
  puppetfile.accept(visitor)
85
82
  end
86
83
  end
87
84
 
85
+ def deploy
86
+ @modules.each do |mod|
87
+ mod.sync
88
+ end
89
+
90
+ super
91
+ end
92
+
88
93
  def load_modules(module_hash)
89
94
  module_hash.each do |name, args|
95
+ if !args.is_a?(Hash)
96
+ args = { version: args }
97
+ end
98
+
90
99
  add_module(name, args)
91
100
  end
92
101
  end
93
102
 
94
103
  # @param [String] name
95
- # @param [*Object] args
104
+ # @param [Hash] args
96
105
  def add_module(name, args)
97
- if args.is_a?(Hash)
98
- # symbolize keys in the args hash
99
- args = args.inject({}) { |memo,(k,v)| memo[k.to_sym] = v; memo }
100
- end
106
+ # symbolize keys in the args hash
107
+ args = args.inject({}) { |memo,(k,v)| memo[k.to_sym] = v; memo }
108
+ args[:overrides] = @overrides
101
109
 
102
- if args.is_a?(Hash) && install_path = args.delete(:install_path)
110
+ if install_path = args.delete(:install_path)
103
111
  install_path = resolve_install_path(install_path)
104
112
  validate_install_path(install_path, name)
105
113
  else
data/lib/r10k/module.rb CHANGED
@@ -17,7 +17,7 @@ module R10K::Module
17
17
  #
18
18
  # @param [String] name The unique name of the module
19
19
  # @param [String] basedir The root to install the module in
20
- # @param [Object] args An arbitary value or set of values that specifies the implementation
20
+ # @param [Hash] args An arbitary Hash that specifies the implementation
21
21
  # @param [R10K::Environment] environment Optional environment that this module is a part of
22
22
  #
23
23
  # @return [Object < R10K::Module] A member of the implementing subclass
@@ -51,7 +51,11 @@ class R10K::Module::Base
51
51
  @owner, @name = parse_title(@title)
52
52
  @path = Pathname.new(File.join(@dirname, @name))
53
53
  @environment = environment
54
+ @overrides = args.delete(:overrides) || {}
54
55
  @origin = 'external' # Expect Puppetfile or R10k::Environment to set this to a specific value
56
+
57
+ @requested_modules = @overrides.dig(:modules, :requested_modules) || []
58
+ @should_sync = (@requested_modules.empty? || @requested_modules.include?(@name))
55
59
  end
56
60
 
57
61
  # @deprecated
@@ -61,11 +65,22 @@ class R10K::Module::Base
61
65
  end
62
66
 
63
67
  # Synchronize this module with the indicated state.
64
- # @abstract
68
+ # @param [Hash] opts Deprecated
65
69
  def sync(opts={})
66
70
  raise NotImplementedError
67
71
  end
68
72
 
73
+ def should_sync?
74
+ if @should_sync
75
+ logger.info _("Deploying module to %{path}") % {path: path}
76
+ true
77
+ else
78
+ logger.debug1(_("Only updating modules %{modules}, skipping module %{name}") % {modules: @requested_modules.inspect, name: name})
79
+ false
80
+ end
81
+ end
82
+
83
+
69
84
  # Return the desired version of this module
70
85
  # @abstract
71
86
  def version
@@ -87,6 +102,7 @@ class R10K::Module::Base
87
102
  raise NotImplementedError
88
103
  end
89
104
 
105
+ # Deprecated
90
106
  def accept(visitor)
91
107
  visitor.visit(:module, self)
92
108
  end
@@ -13,7 +13,12 @@ class R10K::Module::Forge < R10K::Module::Base
13
13
  R10K::Module.register(self)
14
14
 
15
15
  def self.implement?(name, args)
16
- (args.is_a?(Hash) && args[:type].to_s == 'forge') || (!!(name.match %r[\w+[/-]\w+]) && valid_version?(args))
16
+ args[:type].to_s == 'forge' ||
17
+ (!!
18
+ ((args.keys & %i{git svn type}).empty? &&
19
+ args.has_key?(:version) &&
20
+ name.match(%r[\w+[/-]\w+]) &&
21
+ valid_version?(args[:version])))
17
22
  end
18
23
 
19
24
  def self.valid_version?(expected_version)
@@ -40,28 +45,29 @@ class R10K::Module::Forge < R10K::Module::Base
40
45
  @metadata_file = R10K::Module::MetadataFile.new(path + 'metadata.json')
41
46
  @metadata = @metadata_file.read
42
47
 
43
- if opts.is_a?(Hash)
44
- setopts(opts, {
45
- # Standard option interface
46
- :version => :expected_version,
47
- :source => ::R10K::Util::Setopts::Ignore,
48
- :type => ::R10K::Util::Setopts::Ignore,
49
- })
50
- else
51
- @expected_version = opts || current_version || :latest
52
- end
48
+ setopts(opts, {
49
+ # Standard option interface
50
+ :version => :expected_version,
51
+ :source => ::R10K::Util::Setopts::Ignore,
52
+ :type => ::R10K::Util::Setopts::Ignore,
53
+ })
54
+
55
+ @expected_version ||= current_version || :latest
53
56
 
54
57
  @v3_module = PuppetForge::V3::Module.new(:slug => @title)
55
58
  end
56
59
 
60
+ # @param [Hash] opts Deprecated
57
61
  def sync(opts={})
58
- case status
59
- when :absent
60
- install
61
- when :outdated
62
- upgrade
63
- when :mismatched
64
- reinstall
62
+ if should_sync?
63
+ case status
64
+ when :absent
65
+ install
66
+ when :outdated
67
+ upgrade
68
+ when :mismatched
69
+ reinstall
70
+ end
65
71
  end
66
72
  end
67
73