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.
- checksums.yaml +4 -4
- data/Puppetfile +4 -3
- 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 +116 -26
- data/lib/bolt/catalog.rb +5 -3
- data/lib/bolt/cli.rb +194 -185
- data/lib/bolt/config.rb +61 -26
- data/lib/bolt/config/options.rb +35 -2
- data/lib/bolt/executor.rb +2 -2
- 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 +35 -21
- data/lib/bolt/module_installer.rb +172 -0
- data/lib/bolt/outputter.rb +4 -0
- data/lib/bolt/outputter/human.rb +53 -11
- data/lib/bolt/outputter/json.rb +7 -1
- data/lib/bolt/outputter/logger.rb +3 -3
- data/lib/bolt/pal.rb +29 -20
- data/lib/bolt/pal/yaml_plan/evaluator.rb +1 -1
- data/lib/bolt/plugin/module.rb +1 -1
- data/lib/bolt/plugin/puppetdb.rb +1 -1
- data/lib/bolt/project.rb +89 -28
- data/lib/bolt/project_migrator.rb +80 -0
- data/lib/bolt/project_migrator/base.rb +39 -0
- data/lib/bolt/project_migrator/config.rb +67 -0
- data/lib/bolt/project_migrator/inventory.rb +67 -0
- data/lib/bolt/project_migrator/modules.rb +198 -0
- data/lib/bolt/puppetdb/client.rb +1 -1
- data/lib/bolt/puppetdb/config.rb +1 -1
- data/lib/bolt/puppetfile.rb +142 -0
- data/lib/bolt/puppetfile/installer.rb +43 -0
- data/lib/bolt/puppetfile/module.rb +90 -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/shell/bash.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 +52 -11
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/acl.rb +2 -2
- data/lib/bolt_server/base_config.rb +3 -3
- 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/plans/mock_executor.rb +1 -1
- metadata +15 -13
- data/lib/bolt/project_migrate.rb +0 -138
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,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
|
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/shell/bash.rb
CHANGED
@@ -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,
|
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
|
|