r10k 3.9.0 → 3.9.1

Sign up to get free protection for your applications and to get access to all the features.
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