r10k 3.9.0 → 3.10.0

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