bolt 2.24.0 → 2.28.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  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 +75 -7
  9. data/lib/bolt/catalog.rb +4 -2
  10. data/lib/bolt/cli.rb +126 -147
  11. data/lib/bolt/config.rb +56 -26
  12. data/lib/bolt/config/options.rb +24 -2
  13. data/lib/bolt/executor.rb +1 -1
  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 +9 -2
  19. data/lib/bolt/outputter/logger.rb +1 -1
  20. data/lib/bolt/pal.rb +21 -10
  21. data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
  22. data/lib/bolt/plugin/puppetdb.rb +1 -1
  23. data/lib/bolt/project.rb +63 -17
  24. data/lib/bolt/puppetdb/client.rb +1 -1
  25. data/lib/bolt/puppetdb/config.rb +1 -1
  26. data/lib/bolt/puppetfile.rb +160 -0
  27. data/lib/bolt/puppetfile/installer.rb +43 -0
  28. data/lib/bolt/puppetfile/module.rb +66 -0
  29. data/lib/bolt/r10k_log_proxy.rb +1 -1
  30. data/lib/bolt/rerun.rb +2 -2
  31. data/lib/bolt/result.rb +23 -0
  32. data/lib/bolt/shell.rb +1 -1
  33. data/lib/bolt/task.rb +1 -1
  34. data/lib/bolt/transport/base.rb +5 -5
  35. data/lib/bolt/transport/docker/connection.rb +1 -1
  36. data/lib/bolt/transport/local/connection.rb +1 -1
  37. data/lib/bolt/transport/ssh.rb +1 -1
  38. data/lib/bolt/transport/ssh/connection.rb +1 -1
  39. data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
  40. data/lib/bolt/transport/winrm.rb +1 -1
  41. data/lib/bolt/transport/winrm/connection.rb +1 -1
  42. data/lib/bolt/util.rb +11 -11
  43. data/lib/bolt/version.rb +1 -1
  44. data/lib/bolt_server/base_config.rb +1 -1
  45. data/lib/bolt_server/config.rb +1 -1
  46. data/lib/bolt_server/file_cache.rb +12 -12
  47. data/lib/bolt_server/transport_app.rb +125 -26
  48. data/lib/bolt_spec/bolt_context.rb +4 -4
  49. data/lib/bolt_spec/run.rb +3 -0
  50. metadata +9 -12
@@ -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,160 @@
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)
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
+ raise Bolt::ValidationError,
37
+ "Unable to parse Puppetfile #{path}"
38
+ end
39
+
40
+ modules = parsed.modules.map do |mod|
41
+ Bolt::Puppetfile::Module.new(mod.owner, mod.name, mod.version)
42
+ end
43
+
44
+ new(modules)
45
+ end
46
+
47
+ # Writes a Puppetfile that includes specifications for each of the
48
+ # modules.
49
+ #
50
+ def write(path, force: false)
51
+ if File.exist?(path) && !force
52
+ raise Bolt::FileError.new(
53
+ "Cannot overwrite existing Puppetfile at #{path}. To forcibly overwrite, "\
54
+ "run with the '--force' option.",
55
+ path
56
+ )
57
+ end
58
+
59
+ File.open(path, 'w') do |file|
60
+ file.puts '# This Puppetfile is managed by Bolt. Do not edit.'
61
+ modules.each { |mod| file.puts mod.to_spec }
62
+ file.puts
63
+ end
64
+ rescue SystemCallError => e
65
+ raise Bolt::FileError.new(
66
+ "#{e.message}: unable to write Puppetfile.",
67
+ path
68
+ )
69
+ end
70
+
71
+ # Resolves module dependencies using the puppetfile-resolver library. The
72
+ # resolver will return a document model including all module dependencies
73
+ # and the latest version that can be installed for each. The document model
74
+ # is parsed and turned into a Set of Bolt::Puppetfile::Module objects.
75
+ #
76
+ def resolve
77
+ require 'puppetfile-resolver'
78
+
79
+ # Build the document model from the modules.
80
+ model = PuppetfileResolver::Puppetfile::Document.new('')
81
+
82
+ @modules.each do |mod|
83
+ model.add_module(
84
+ PuppetfileResolver::Puppetfile::ForgeModule.new(mod.title).tap do |tap|
85
+ tap.version = :latest
86
+ end
87
+ )
88
+ end
89
+
90
+ # Make sure the Puppetfile model is valid.
91
+ unless model.valid?
92
+ raise Bolt::ValidationError,
93
+ "Unable to resolve dependencies for modules: #{@modules.map(&:title).join(', ')}"
94
+ end
95
+
96
+ # Create the resolver using the Puppetfile model. nil disables Puppet
97
+ # version restrictions.
98
+ resolver = PuppetfileResolver::Resolver.new(model, nil)
99
+
100
+ # Configure and resolve the dependency graph, catching any errors
101
+ # raised by puppetfile-resolver and re-raising them as Bolt errors.
102
+ begin
103
+ result = resolver.resolve(
104
+ cache: nil,
105
+ ui: nil,
106
+ module_paths: [],
107
+ allow_missing_modules: true
108
+ )
109
+ rescue StandardError => e
110
+ raise Bolt::Error.new(e.message, 'bolt/puppetfile-resolver-error')
111
+ end
112
+
113
+ # Validate that the modules exist.
114
+ missing_graph = result.specifications.select do |_name, spec|
115
+ spec.instance_of? PuppetfileResolver::Models::MissingModuleSpecification
116
+ end
117
+
118
+ if missing_graph.any?
119
+ titles = model.modules.each_with_object({}) do |mod, acc|
120
+ acc[mod.name] = mod.title
121
+ end
122
+
123
+ names = titles.values_at(*missing_graph.keys)
124
+ plural = names.count == 1 ? '' : 's'
125
+
126
+ raise Bolt::Error.new(
127
+ "Unknown module name#{plural} #{names.join(', ')}",
128
+ 'bolt/unknown-modules'
129
+ )
130
+ end
131
+
132
+ # Filter the dependency graph to only include module specifications. This
133
+ # will only remove the Puppet version specification, which is not needed.
134
+ specs = result.specifications.select do |_name, spec|
135
+ spec.instance_of? PuppetfileResolver::Models::ModuleSpecification
136
+ end
137
+
138
+ @modules = specs.map do |_name, spec|
139
+ Bolt::Puppetfile::Module.new(spec.owner, spec.name, spec.version.to_s)
140
+ end.to_set
141
+ end
142
+
143
+ # Adds to the set of modules.
144
+ #
145
+ def add_modules(modules)
146
+ modules.each do |mod|
147
+ case mod
148
+ when Bolt::Puppetfile::Module
149
+ @modules << mod
150
+ when Hash
151
+ @modules << Bolt::Puppetfile::Module.from_hash(mod)
152
+ else
153
+ raise Bolt::ValidationError, "Module must be a Bolt::Puppetfile::Module or Hash."
154
+ end
155
+ end
156
+
157
+ @modules
158
+ end
159
+ end
160
+ 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,66 @@
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
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)
34
+ end
35
+
36
+ # Returns the module's title.
37
+ #
38
+ def title
39
+ "#{@owner}-#{@name}"
40
+ end
41
+
42
+ # Checks two modules for equality.
43
+ #
44
+ def eql?(other)
45
+ self.class == other.class && @owner == other.owner && @name == other.name
46
+ end
47
+ alias == eql?
48
+
49
+ # Hashes the module.
50
+ #
51
+ def hash
52
+ [@owner, @name].hash
53
+ end
54
+
55
+ # Returns the Puppetfile specification for the module.
56
+ #
57
+ def to_spec
58
+ if @version
59
+ "mod #{title.inspect}, #{@version.inspect}"
60
+ else
61
+ "mod #{title.inspect}"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ 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)
@@ -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
 
@@ -14,7 +14,7 @@ module Bolt
14
14
  @target = target
15
15
  ssh_config = Net::SSH::Config.for(target.host)
16
16
  @user = @target.user || ssh_config[:user] || Etc.getlogin
17
- @logger = Logging.logger[self]
17
+ @logger = Bolt::Logger.logger(self)
18
18
  end
19
19
 
20
20
  # This is used to verify we can connect to targets with `connected?`