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.
- checksums.yaml +4 -4
- data/Puppetfile +1 -1
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -1
- data/bolt-modules/boltlib/lib/puppet/functions/download_file.rb +1 -1
- data/bolt-modules/dir/lib/puppet/functions/dir/children.rb +1 -1
- data/lib/bolt/analytics.rb +7 -3
- data/lib/bolt/applicator.rb +21 -21
- data/lib/bolt/bolt_option_parser.rb +75 -7
- data/lib/bolt/catalog.rb +4 -2
- data/lib/bolt/cli.rb +126 -147
- data/lib/bolt/config.rb +56 -26
- data/lib/bolt/config/options.rb +24 -2
- data/lib/bolt/executor.rb +1 -1
- data/lib/bolt/inventory.rb +8 -1
- data/lib/bolt/inventory/group.rb +1 -1
- data/lib/bolt/inventory/inventory.rb +1 -1
- data/lib/bolt/inventory/target.rb +1 -1
- data/lib/bolt/logger.rb +9 -2
- data/lib/bolt/outputter/logger.rb +1 -1
- data/lib/bolt/pal.rb +21 -10
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +63 -17
- data/lib/bolt/puppetdb/client.rb +1 -1
- data/lib/bolt/puppetdb/config.rb +1 -1
- data/lib/bolt/puppetfile.rb +160 -0
- data/lib/bolt/puppetfile/installer.rb +43 -0
- data/lib/bolt/puppetfile/module.rb +66 -0
- data/lib/bolt/r10k_log_proxy.rb +1 -1
- data/lib/bolt/rerun.rb +2 -2
- data/lib/bolt/result.rb +23 -0
- data/lib/bolt/shell.rb +1 -1
- data/lib/bolt/task.rb +1 -1
- data/lib/bolt/transport/base.rb +5 -5
- data/lib/bolt/transport/docker/connection.rb +1 -1
- data/lib/bolt/transport/local/connection.rb +1 -1
- data/lib/bolt/transport/ssh.rb +1 -1
- data/lib/bolt/transport/ssh/connection.rb +1 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
- data/lib/bolt/transport/winrm.rb +1 -1
- data/lib/bolt/transport/winrm/connection.rb +1 -1
- data/lib/bolt/util.rb +11 -11
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/base_config.rb +1 -1
- data/lib/bolt_server/config.rb +1 -1
- data/lib/bolt_server/file_cache.rb +12 -12
- data/lib/bolt_server/transport_app.rb +125 -26
- data/lib/bolt_spec/bolt_context.rb +4 -4
- data/lib/bolt_spec/run.rb +3 -0
- metadata +9 -12
data/lib/bolt/puppetdb/client.rb
CHANGED
data/lib/bolt/puppetdb/config.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/bolt/r10k_log_proxy.rb
CHANGED
data/lib/bolt/rerun.rb
CHANGED
@@ -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 =
|
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
|
-
|
56
|
+
Bolt::Logger.warn_once('unwriteable_file', "Failed to save result to #{@path}: #{e.message}")
|
57
57
|
end
|
58
58
|
end
|
59
59
|
end
|
data/lib/bolt/result.rb
CHANGED
@@ -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
|
data/lib/bolt/shell.rb
CHANGED
data/lib/bolt/task.rb
CHANGED
data/lib/bolt/transport/base.rb
CHANGED
@@ -40,17 +40,17 @@ module Bolt
|
|
40
40
|
attr_reader :logger
|
41
41
|
|
42
42
|
def initialize
|
43
|
-
@logger =
|
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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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 =
|
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
|
data/lib/bolt/transport/ssh.rb
CHANGED
@@ -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 =
|
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 =
|
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 =
|
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?`
|