r10k 3.9.0 → 3.10.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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/pull_request_template.md +1 -1
  3. data/.github/workflows/rspec_tests.yml +1 -1
  4. data/.github/workflows/stale.yml +19 -0
  5. data/CHANGELOG.mkd +24 -0
  6. data/doc/dynamic-environments/configuration.mkd +13 -6
  7. data/integration/Rakefile +1 -1
  8. data/integration/tests/user_scenario/basic_workflow/negative/neg_specify_deleted_forge_module.rb +3 -9
  9. data/integration/tests/user_scenario/basic_workflow/single_env_purge_unmanaged_modules.rb +8 -14
  10. data/lib/r10k/action/base.rb +10 -0
  11. data/lib/r10k/action/deploy/display.rb +42 -9
  12. data/lib/r10k/action/deploy/environment.rb +70 -41
  13. data/lib/r10k/action/deploy/module.rb +51 -29
  14. data/lib/r10k/action/puppetfile/check.rb +3 -1
  15. data/lib/r10k/action/puppetfile/install.rb +20 -23
  16. data/lib/r10k/action/puppetfile/purge.rb +8 -2
  17. data/lib/r10k/action/runner.rb +11 -6
  18. data/lib/r10k/content_synchronizer.rb +83 -0
  19. data/lib/r10k/deployment.rb +1 -1
  20. data/lib/r10k/environment/base.rb +21 -1
  21. data/lib/r10k/environment/git.rb +0 -3
  22. data/lib/r10k/environment/svn.rb +4 -6
  23. data/lib/r10k/environment/with_modules.rb +18 -10
  24. data/lib/r10k/git/cache.rb +1 -1
  25. data/lib/r10k/initializers.rb +7 -0
  26. data/lib/r10k/module.rb +1 -1
  27. data/lib/r10k/module/base.rb +17 -1
  28. data/lib/r10k/module/forge.rb +29 -19
  29. data/lib/r10k/module/git.rb +23 -14
  30. data/lib/r10k/module/local.rb +1 -0
  31. data/lib/r10k/module/svn.rb +12 -9
  32. data/lib/r10k/module_loader/puppetfile.rb +195 -0
  33. data/lib/r10k/module_loader/puppetfile/dsl.rb +37 -0
  34. data/lib/r10k/puppetfile.rb +111 -202
  35. data/lib/r10k/settings.rb +3 -0
  36. data/lib/r10k/source/base.rb +14 -0
  37. data/lib/r10k/source/git.rb +19 -6
  38. data/lib/r10k/source/hash.rb +1 -3
  39. data/lib/r10k/source/svn.rb +4 -2
  40. data/lib/r10k/util/cleaner.rb +21 -0
  41. data/lib/r10k/util/purgeable.rb +70 -8
  42. data/lib/r10k/version.rb +1 -1
  43. data/locales/r10k.pot +67 -71
  44. data/spec/fixtures/unit/action/r10k_forge_auth.yaml +4 -0
  45. data/spec/fixtures/unit/action/r10k_forge_auth_no_url.yaml +3 -0
  46. data/spec/fixtures/unit/util/purgeable/managed_one/managed_subdir_1/managed_subdir_2/ignored_1 +0 -0
  47. data/spec/fixtures/unit/util/purgeable/managed_two/.hidden/unmanaged_3 +0 -0
  48. data/spec/r10k-mocks/mock_source.rb +1 -1
  49. data/spec/shared-examples/puppetfile-action.rb +7 -7
  50. data/spec/unit/action/deploy/display_spec.rb +32 -6
  51. data/spec/unit/action/deploy/environment_spec.rb +85 -48
  52. data/spec/unit/action/deploy/module_spec.rb +163 -31
  53. data/spec/unit/action/puppetfile/check_spec.rb +2 -2
  54. data/spec/unit/action/puppetfile/install_spec.rb +31 -10
  55. data/spec/unit/action/puppetfile/purge_spec.rb +25 -5
  56. data/spec/unit/action/runner_spec.rb +49 -25
  57. data/spec/unit/git/cache_spec.rb +14 -0
  58. data/spec/unit/module/forge_spec.rb +23 -14
  59. data/spec/unit/module/git_spec.rb +8 -8
  60. data/spec/unit/module_loader/puppetfile_spec.rb +330 -0
  61. data/spec/unit/module_spec.rb +22 -5
  62. data/spec/unit/puppetfile_spec.rb +123 -203
  63. data/spec/unit/settings_spec.rb +6 -2
  64. data/spec/unit/util/purgeable_spec.rb +40 -14
  65. metadata +12 -2
@@ -10,25 +10,56 @@ module R10K
10
10
 
11
11
  include R10K::Action::Deploy::DeployHelpers
12
12
 
13
+ # Deprecated
13
14
  attr_reader :force
14
15
 
15
- def initialize(opts, argv, settings = nil)
16
- settings ||= {}
17
-
16
+ attr_reader :settings
17
+
18
+ # @param opts [Hash] A hash of options defined in #allowed_initialized_opts
19
+ # and managed by the SetOps mixin within the Action::Base class.
20
+ # Corresponds to the CLI flags and options.
21
+ # @param argv [Enumerable] Typically CRI::ArgumentList or Array. A list-like
22
+ # collection of the remaining arguments to the CLI invocation (after
23
+ # removing flags and options).
24
+ # @param settings [Hash] A hash of configuration loaded from the relevant
25
+ # config (r10k.yaml).
26
+ #
27
+ # @note All arguments will be required in the next major version
28
+ def initialize(opts, argv, settings = {})
18
29
  super
19
30
 
20
- # @force here is used to make it easier to reason about
21
- @force = !@no_force
31
+ requested_env = @opts[:environment] ? [@opts[:environment].gsub(/\W/, '_')] : []
32
+
33
+ @settings = @settings.merge({
34
+ overrides: {
35
+ environments: {
36
+ requested_environments: requested_env,
37
+ generate_types: @generate_types
38
+ },
39
+ modules: {
40
+ requested_modules: @argv.map.to_a,
41
+ # force here is used to make it easier to reason about
42
+ force: !@no_force
43
+ },
44
+ purging: {},
45
+ output: {}
46
+ }
47
+ })
22
48
  end
23
49
 
24
50
  def call
25
51
  @visit_ok = true
52
+ begin
53
+ expect_config!
54
+ deployment = R10K::Deployment.new(@settings)
55
+ check_write_lock!(@settings)
56
+
57
+ deployment.accept(self)
58
+ rescue => e
59
+ @visit_ok = false
60
+ logger.error R10K::Errors::Formatting.format_exception(e, @trace)
61
+ end
26
62
 
27
- expect_config!
28
- deployment = R10K::Deployment.new(@settings)
29
- check_write_lock!(@settings)
30
-
31
- deployment.accept(self)
32
63
  @visit_ok
33
64
  end
34
65
 
@@ -45,29 +76,20 @@ module R10K
45
76
  end
46
77
 
47
78
  def visit_environment(environment)
48
- if @opts[:environment] && (@opts[:environment] != environment.dirname)
49
- logger.debug1(_("Only updating modules in environment %{opt_env} skipping environment %{env_path}") % {opt_env: @opts[:environment], env_path: environment.path})
79
+ requested_envs = @settings.dig(:overrides, :environments, :requested_environments)
80
+ if !requested_envs.empty? && !requested_envs.include?(environment.dirname)
81
+ logger.debug1(_("Only updating modules in environment(s) %{opt_env} skipping environment %{env_path}") % {opt_env: requested_envs.inspect, env_path: environment.path})
50
82
  else
51
- logger.debug1(_("Updating modules %{modules} in environment %{env_path}") % {modules: @argv.inspect, env_path: environment.path})
52
- yield
53
- end
54
- end
83
+ logger.debug1(_("Updating modules %{modules} in environment %{env_path}") % {modules: @settings.dig(:overrides, :modules, :requested_modules).inspect, env_path: environment.path})
55
84
 
56
- def visit_puppetfile(puppetfile)
57
- puppetfile.load
58
- yield
59
- end
85
+ environment.deploy
60
86
 
61
- def visit_module(mod)
62
- if @argv.include?(mod.name)
63
- logger.info _("Deploying module %{mod_path}") % {mod_path: mod.path}
64
- mod.sync(force: @force)
65
- if mod.environment && @generate_types
66
- logger.debug("Generating puppet types for environment '#{mod.environment.dirname}'...")
67
- mod.environment.generate_types!
87
+ requested_mods = @settings.dig(:overrides, :modules, :requested_modules) || []
88
+ generate_types = @settings.dig(:overrides, :environments, :generate_types)
89
+ if generate_types && !((environment.modules.map(&:name) & requested_mods).empty?)
90
+ logger.debug("Generating puppet types for environment '#{environment.dirname}'...")
91
+ environment.generate_types!
68
92
  end
69
- else
70
- logger.debug1(_("Only updating modules %{modules}, skipping module %{mod_name}") % {modules: @argv.inspect, mod_name: mod.name})
71
93
  end
72
94
  end
73
95
 
@@ -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)
@@ -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
 
@@ -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