bolt 2.25.0 → 2.30.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bolt might be problematic. Click here for more details.

Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +4 -3
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
  5. data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +1 -1
  6. data/lib/bolt/analytics.rb +7 -3
  7. data/lib/bolt/applicator.rb +21 -21
  8. data/lib/bolt/bolt_option_parser.rb +116 -26
  9. data/lib/bolt/catalog.rb +5 -3
  10. data/lib/bolt/cli.rb +194 -185
  11. data/lib/bolt/config.rb +61 -26
  12. data/lib/bolt/config/options.rb +35 -2
  13. data/lib/bolt/executor.rb +2 -2
  14. data/lib/bolt/inventory.rb +8 -1
  15. data/lib/bolt/inventory/group.rb +1 -1
  16. data/lib/bolt/inventory/inventory.rb +1 -1
  17. data/lib/bolt/inventory/target.rb +1 -1
  18. data/lib/bolt/logger.rb +35 -21
  19. data/lib/bolt/module_installer.rb +172 -0
  20. data/lib/bolt/outputter.rb +4 -0
  21. data/lib/bolt/outputter/human.rb +53 -11
  22. data/lib/bolt/outputter/json.rb +7 -1
  23. data/lib/bolt/outputter/logger.rb +3 -3
  24. data/lib/bolt/pal.rb +29 -20
  25. data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
  26. data/lib/bolt/plugin/module.rb +1 -1
  27. data/lib/bolt/plugin/puppetdb.rb +1 -1
  28. data/lib/bolt/project.rb +89 -28
  29. data/lib/bolt/project_migrator.rb +80 -0
  30. data/lib/bolt/project_migrator/base.rb +39 -0
  31. data/lib/bolt/project_migrator/config.rb +67 -0
  32. data/lib/bolt/project_migrator/inventory.rb +67 -0
  33. data/lib/bolt/project_migrator/modules.rb +198 -0
  34. data/lib/bolt/puppetdb/client.rb +1 -1
  35. data/lib/bolt/puppetdb/config.rb +1 -1
  36. data/lib/bolt/puppetfile.rb +142 -0
  37. data/lib/bolt/puppetfile/installer.rb +43 -0
  38. data/lib/bolt/puppetfile/module.rb +90 -0
  39. data/lib/bolt/r10k_log_proxy.rb +1 -1
  40. data/lib/bolt/rerun.rb +2 -2
  41. data/lib/bolt/result.rb +23 -0
  42. data/lib/bolt/shell.rb +1 -1
  43. data/lib/bolt/shell/bash.rb +1 -1
  44. data/lib/bolt/task.rb +1 -1
  45. data/lib/bolt/transport/base.rb +5 -5
  46. data/lib/bolt/transport/docker/connection.rb +1 -1
  47. data/lib/bolt/transport/local/connection.rb +1 -1
  48. data/lib/bolt/transport/ssh.rb +1 -1
  49. data/lib/bolt/transport/ssh/connection.rb +1 -1
  50. data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
  51. data/lib/bolt/transport/winrm.rb +1 -1
  52. data/lib/bolt/transport/winrm/connection.rb +1 -1
  53. data/lib/bolt/util.rb +52 -11
  54. data/lib/bolt/version.rb +1 -1
  55. data/lib/bolt_server/acl.rb +2 -2
  56. data/lib/bolt_server/base_config.rb +3 -3
  57. data/lib/bolt_server/config.rb +1 -1
  58. data/lib/bolt_server/file_cache.rb +12 -12
  59. data/lib/bolt_server/transport_app.rb +125 -26
  60. data/lib/bolt_spec/bolt_context.rb +4 -4
  61. data/lib/bolt_spec/plans/mock_executor.rb +1 -1
  62. metadata +15 -13
  63. data/lib/bolt/project_migrate.rb +0 -138
@@ -13,7 +13,7 @@ module Bolt
13
13
  @config = config
14
14
  @bad_urls = []
15
15
  @current_url = nil
16
- @logger = Logging.logger[self]
16
+ @logger = Bolt::Logger.logger(self)
17
17
  end
18
18
 
19
19
  def query_certnames(query)
@@ -35,7 +35,7 @@ module Bolt
35
35
  begin
36
36
  config = JSON.parse(File.read(filepath)) if filepath
37
37
  rescue StandardError => e
38
- Logging.logger[self].error("Could not load puppetdb.conf from #{filepath}: #{e.message}")
38
+ Bolt::Logger.logger(self).error("Could not load puppetdb.conf from #{filepath}: #{e.message}")
39
39
  end
40
40
 
41
41
  config = config.fetch('puppetdb', {})
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+ require 'bolt/puppetfile/module'
5
+
6
+ # This class manages the logical contents of a Puppetfile. It includes methods
7
+ # for parsing a Puppetfile and its modules, resolving module dependencies,
8
+ # and writing a Puppetfile.
9
+ #
10
+ module Bolt
11
+ class Puppetfile
12
+ attr_reader :modules
13
+
14
+ def initialize(modules = [])
15
+ @modules = Set.new
16
+ add_modules(modules)
17
+ end
18
+
19
+ # Loads a Puppetfile and parses its module specifications, returning a
20
+ # Bolt::Puppetfile object with the modules set.
21
+ #
22
+ def self.parse(path, skip_unsupported_modules: false)
23
+ require 'puppetfile-resolver'
24
+ require 'puppetfile-resolver/puppetfile/parser/r10k_eval'
25
+
26
+ begin
27
+ parsed = ::PuppetfileResolver::Puppetfile::Parser::R10KEval.parse(File.read(path))
28
+ rescue StandardError => e
29
+ raise Bolt::Error.new(
30
+ "Unable to parse Puppetfile #{path}: #{e.message}",
31
+ 'bolt/puppetfile-parsing'
32
+ )
33
+ end
34
+
35
+ unless parsed.valid?
36
+ # valid? Just checks if validation_errors is empty, so if we get here we know it's not.
37
+ raise Bolt::ValidationError, <<~MSG
38
+ Unable to parse Puppetfile #{path}:
39
+ #{parsed.validation_errors.join("\n\n")}.
40
+ This may not be a Puppetfile managed by Bolt.
41
+ MSG
42
+ end
43
+
44
+ modules = parsed.modules.each_with_object([]) do |mod, acc|
45
+ unless mod.instance_of? PuppetfileResolver::Puppetfile::ForgeModule
46
+ next if skip_unsupported_modules
47
+
48
+ raise Bolt::ValidationError,
49
+ "Module '#{mod.title}' is not a Puppet Forge module. Unable to "\
50
+ "parse Puppetfile #{path}."
51
+ end
52
+
53
+ acc << Bolt::Puppetfile::Module.new(mod.owner, mod.name, mod.version)
54
+ end
55
+
56
+ new(modules)
57
+ end
58
+
59
+ # Writes a Puppetfile that includes specifications for each of the
60
+ # modules.
61
+ #
62
+ def write(path, moduledir = nil)
63
+ File.open(path, 'w') do |file|
64
+ file.puts '# This Puppetfile is managed by Bolt. Do not edit.'
65
+ file.puts "moduledir '#{moduledir.basename}'" if moduledir
66
+ modules.each { |mod| file.puts mod.to_spec }
67
+ end
68
+ rescue SystemCallError => e
69
+ raise Bolt::FileError.new(
70
+ "#{e.message}: unable to write Puppetfile.",
71
+ path
72
+ )
73
+ end
74
+
75
+ # Resolves module dependencies using the puppetfile-resolver library. The
76
+ # resolver will return a document model including all module dependencies
77
+ # and the latest version that can be installed for each. The document model
78
+ # is parsed and turned into a Set of Bolt::Puppetfile::Module objects.
79
+ #
80
+ def resolve
81
+ require 'puppetfile-resolver'
82
+
83
+ # Build the document model from the modules.
84
+ model = PuppetfileResolver::Puppetfile::Document.new('')
85
+
86
+ @modules.each do |mod|
87
+ model.add_module(
88
+ PuppetfileResolver::Puppetfile::ForgeModule.new(mod.title).tap do |tap|
89
+ tap.version = mod.version || :latest
90
+ end
91
+ )
92
+ end
93
+
94
+ # Make sure the Puppetfile model is valid.
95
+ unless model.valid?
96
+ raise Bolt::ValidationError,
97
+ "Unable to resolve dependencies for modules: #{@modules.map(&:title).join(', ')}"
98
+ end
99
+
100
+ # Create the resolver using the Puppetfile model. nil disables Puppet
101
+ # version restrictions.
102
+ resolver = PuppetfileResolver::Resolver.new(model, nil)
103
+
104
+ # Configure and resolve the dependency graph, catching any errors
105
+ # raised by puppetfile-resolver and re-raising them as Bolt errors.
106
+ begin
107
+ result = resolver.resolve(
108
+ cache: nil,
109
+ ui: nil,
110
+ module_paths: [],
111
+ allow_missing_modules: false
112
+ )
113
+ rescue StandardError => e
114
+ raise Bolt::Error.new(e.message, 'bolt/puppetfile-resolver-error')
115
+ end
116
+
117
+ # Turn specifications into module objects. This will skip over anything that is not
118
+ # a module specification (i.e. a Puppet version specification).
119
+ @modules = result.specifications.each_with_object(Set.new) do |(_name, spec), acc|
120
+ next unless spec.instance_of? PuppetfileResolver::Models::ModuleSpecification
121
+ acc << Bolt::Puppetfile::Module.new(spec.owner, spec.name, spec.version.to_s)
122
+ end
123
+ end
124
+
125
+ # Adds to the set of modules.
126
+ #
127
+ def add_modules(modules)
128
+ Array(modules).each do |mod|
129
+ case mod
130
+ when Bolt::Puppetfile::Module
131
+ @modules << mod
132
+ when Hash
133
+ @modules << Bolt::Puppetfile::Module.from_hash(mod)
134
+ else
135
+ raise Bolt::ValidationError, "Module must be a Bolt::Puppetfile::Module or Hash."
136
+ end
137
+ end
138
+
139
+ @modules
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'r10k/cli'
4
+ require 'bolt/r10k_log_proxy'
5
+ require 'bolt/error'
6
+
7
+ # This class is used to install modules from a Puppetfile to a module directory.
8
+ #
9
+ module Bolt
10
+ class Puppetfile
11
+ class Installer
12
+ def initialize(config = {})
13
+ @config = config
14
+ end
15
+
16
+ def install(path, moduledir)
17
+ unless File.exist?(path)
18
+ raise Bolt::FileError.new(
19
+ "Could not find a Puppetfile at #{path}",
20
+ path
21
+ )
22
+ end
23
+
24
+ r10k_opts = {
25
+ root: File.dirname(path),
26
+ puppetfile: path.to_s,
27
+ moduledir: moduledir.to_s
28
+ }
29
+
30
+ settings = R10K::Settings.global_settings.evaluate(@config)
31
+ R10K::Initializers::GlobalInitializer.new(settings).call
32
+ install_action = R10K::Action::Puppetfile::Install.new(r10k_opts, nil)
33
+
34
+ # Override the r10k logger with a proxy to our own logger
35
+ R10K::Logging.instance_variable_set(:@outputter, Bolt::R10KLogProxy.new)
36
+
37
+ install_action.call
38
+ rescue R10K::Error => e
39
+ raise PuppetfileError, e
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # This class represents a module specification. It used by the Bolt::Puppetfile
6
+ # class to have a consistent API for accessing a module's attributes.
7
+ #
8
+ module Bolt
9
+ class Puppetfile
10
+ class Module
11
+ attr_reader :owner, :name, :version
12
+
13
+ def initialize(owner, name, version = nil)
14
+ @owner = owner
15
+ @name = name
16
+ @version = version unless version == :latest
17
+ end
18
+
19
+ # Creates a new module from a hash.
20
+ #
21
+ def self.from_hash(mod)
22
+ unless mod['name'].is_a?(String)
23
+ raise Bolt::ValidationError,
24
+ "Module name must be a String, not #{mod['name'].inspect}"
25
+ end
26
+
27
+ owner, name = mod['name'].tr('/', '-').split('-', 2)
28
+
29
+ unless owner && name
30
+ raise Bolt::ValidationError, "Module name #{mod['name']} must include both the owner and module name."
31
+ end
32
+
33
+ new(owner, name, mod['version_requirement'])
34
+ end
35
+
36
+ # Returns the module's title.
37
+ #
38
+ def title
39
+ "#{@owner}-#{@name}"
40
+ end
41
+ alias to_s title
42
+
43
+ # Checks two modules for equality.
44
+ #
45
+ def eql?(other)
46
+ self.class == other.class &&
47
+ @owner == other.owner &&
48
+ @name == other.name &&
49
+ versions_intersect?(other)
50
+ end
51
+ alias == eql?
52
+
53
+ # Returns true if the versions of two modules intersect. Used to determine
54
+ # if an installed module satisfies the version requirement of another.
55
+ #
56
+ def versions_intersect?(other)
57
+ range = ::SemanticPuppet::VersionRange.parse(@version || '')
58
+ other_range = ::SemanticPuppet::VersionRange.parse(other.version || '')
59
+
60
+ range.intersection(other_range) != ::SemanticPuppet::VersionRange::EMPTY_RANGE
61
+ end
62
+
63
+ # Hashes the module.
64
+ #
65
+ def hash
66
+ [@owner, @name].hash
67
+ end
68
+
69
+ # Returns a hash representation similar to the module
70
+ # declaration.
71
+ #
72
+ def to_hash
73
+ {
74
+ 'name' => title,
75
+ 'version_requirement' => version
76
+ }.compact
77
+ end
78
+
79
+ # Returns the Puppetfile specification for the module.
80
+ #
81
+ def to_spec
82
+ if @version
83
+ "mod #{title.inspect}, #{@version.inspect}"
84
+ else
85
+ "mod #{title.inspect}"
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -7,7 +7,7 @@ module Bolt
7
7
  def initialize
8
8
  super('bolt')
9
9
 
10
- @logger = Logging.logger[self]
10
+ @logger = Bolt::Logger.logger(self)
11
11
  end
12
12
 
13
13
  def canonical_log(event)
@@ -8,7 +8,7 @@ module Bolt
8
8
  def initialize(path, save_failures)
9
9
  @path = path
10
10
  @save_failures = save_failures
11
- @logger = Logging.logger[self]
11
+ @logger = Bolt::Logger.logger(self)
12
12
  end
13
13
 
14
14
  def data
@@ -53,7 +53,7 @@ module Bolt
53
53
  end
54
54
  end
55
55
  rescue StandardError => e
56
- @logger.warn("Failed to save result to #{@path}: #{e.message}")
56
+ Bolt::Logger.warn_once('unwriteable_file', "Failed to save result to #{@path}: #{e.message}")
57
57
  end
58
58
  end
59
59
  end
@@ -65,6 +65,25 @@ module Bolt
65
65
  'msg' => msg,
66
66
  'details' => { 'exit_code' => exit_code } }
67
67
  end
68
+
69
+ if value.key?('_error')
70
+ unless value['_error'].is_a?(Hash) && value['_error'].key?('msg')
71
+ value['_error'] = {
72
+ 'msg' => "Invalid error returned from task #{task}: #{value['_error'].inspect}. Error "\
73
+ "must be an object with a msg key.",
74
+ 'kind' => 'bolt/invalid-task-error',
75
+ 'details' => { 'original_error' => value['_error'] }
76
+ }
77
+ end
78
+
79
+ value['_error']['kind'] ||= 'bolt/error'
80
+ value['_error']['details'] ||= {}
81
+ end
82
+
83
+ if value.key?('_sensitive')
84
+ value['_sensitive'] = Puppet::Pops::Types::PSensitiveType::Sensitive.new(value['_sensitive'])
85
+ end
86
+
68
87
  new(target, value: value, action: 'task', object: task)
69
88
  end
70
89
 
@@ -205,5 +224,9 @@ module Bolt
205
224
 
206
225
  end
207
226
  end
227
+
228
+ def sensitive
229
+ value['_sensitive']
230
+ end
208
231
  end
209
232
  end
@@ -7,7 +7,7 @@ module Bolt
7
7
  def initialize(target, conn)
8
8
  @target = target
9
9
  @conn = conn
10
- @logger = Logging.logger[@target.safe_name]
10
+ @logger = Bolt::Logger.logger(@target.safe_name)
11
11
  end
12
12
 
13
13
  def run_command(*_args)
@@ -143,7 +143,7 @@ module Bolt
143
143
 
144
144
  execute_options[:stdin] = stdin
145
145
  execute_options[:sudoable] = true if run_as
146
- output = execute(remote_task_path, execute_options)
146
+ output = execute(remote_task_path, **execute_options)
147
147
  end
148
148
  Bolt::Result.for_task(target, output.stdout.string,
149
149
  output.stderr.string,
@@ -26,7 +26,7 @@ module Bolt
26
26
  @metadata = metadata
27
27
  @files = files
28
28
  @remote = remote
29
- @logger = Logging.logger[self]
29
+ @logger = Bolt::Logger.logger(self)
30
30
 
31
31
  validate_metadata
32
32
  end
@@ -40,17 +40,17 @@ module Bolt
40
40
  attr_reader :logger
41
41
 
42
42
  def initialize
43
- @logger = Logging.logger[self]
43
+ @logger = Bolt::Logger.logger(self)
44
44
  end
45
45
 
46
46
  def with_events(target, callback, action)
47
47
  callback&.call(type: :node_start, target: target)
48
48
 
49
49
  result = begin
50
- yield
51
- rescue StandardError, NotImplementedError => e
52
- Bolt::Result.from_exception(target, e, action: action)
53
- end
50
+ yield
51
+ rescue StandardError, NotImplementedError => e
52
+ Bolt::Result.from_exception(target, e, action: action)
53
+ end
54
54
 
55
55
  callback&.call(type: :node_result, result: result)
56
56
  result
@@ -10,7 +10,7 @@ module Bolt
10
10
  def initialize(target)
11
11
  raise Bolt::ValidationError, "Target #{target.safe_name} does not have a host" unless target.host
12
12
  @target = target
13
- @logger = Logging.logger[target.safe_name]
13
+ @logger = Bolt::Logger.logger(target.safe_name)
14
14
  @docker_host = @target.options['service-url']
15
15
  @logger.trace("Initializing docker connection to #{@target.safe_name}")
16
16
  end
@@ -16,7 +16,7 @@ module Bolt
16
16
  @target = target
17
17
  # The familiar problem: Etc.getlogin is broken on osx
18
18
  @user = ENV['USER'] || Etc.getlogin
19
- @logger = Logging.logger[self]
19
+ @logger = Bolt::Logger.logger(self)
20
20
  end
21
21
 
22
22
  def shell
@@ -17,7 +17,7 @@ module Bolt
17
17
  rescue LoadError
18
18
  logger.debug("Authentication method 'gssapi-with-mic' (Kerberos) is not available.")
19
19
  end
20
- @transport_logger = Logging.logger[Net::SSH]
20
+ @transport_logger = Bolt::Logger.logger(Net::SSH)
21
21
  @transport_logger.level = :warn
22
22
  end
23
23
 
@@ -26,7 +26,7 @@ module Bolt
26
26
  @user = @target.user || ssh_config[:user] || Etc.getlogin
27
27
  @strict_host_key_checking = ssh_config[:strict_host_key_checking]
28
28
 
29
- @logger = Logging.logger[@target.safe_name]
29
+ @logger = Bolt::Logger.logger(@target.safe_name)
30
30
  @transport_logger = transport_logger
31
31
  @logger.trace("Initializing ssh connection to #{@target.safe_name}")
32
32