bolt 2.6.0 → 2.11.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/applyresult.rb +2 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +27 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/result.rb +2 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/resultset.rb +2 -0
- data/bolt-modules/boltlib/lib/puppet/datatypes/target.rb +4 -3
- data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +1 -1
- data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +192 -0
- data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +122 -0
- data/bolt-modules/boltlib/types/planresult.pp +12 -1
- data/bolt-modules/file/lib/puppet/functions/file/exists.rb +3 -1
- data/bolt-modules/file/lib/puppet/functions/file/join.rb +1 -1
- data/bolt-modules/file/lib/puppet/functions/file/read.rb +2 -1
- data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
- data/bolt-modules/file/lib/puppet/functions/file/write.rb +3 -1
- data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +43 -0
- data/lib/bolt/analytics.rb +1 -1
- data/lib/bolt/applicator.rb +3 -2
- data/lib/bolt/apply_inventory.rb +1 -1
- data/lib/bolt/apply_result.rb +1 -1
- data/lib/bolt/apply_target.rb +11 -2
- data/lib/bolt/bolt_option_parser.rb +27 -7
- data/lib/bolt/catalog.rb +32 -3
- data/lib/bolt/cli.rb +52 -22
- data/lib/bolt/config.rb +51 -27
- data/lib/bolt/config/transport/base.rb +3 -3
- data/lib/bolt/config/transport/docker.rb +7 -1
- data/lib/bolt/config/transport/local.rb +9 -1
- data/lib/bolt/config/transport/orch.rb +4 -2
- data/lib/bolt/config/transport/remote.rb +2 -0
- data/lib/bolt/config/transport/ssh.rb +81 -3
- data/lib/bolt/config/transport/winrm.rb +6 -1
- data/lib/bolt/executor.rb +38 -0
- data/lib/bolt/inventory.rb +2 -1
- data/lib/bolt/inventory/group.rb +1 -0
- data/lib/bolt/inventory/inventory.rb +9 -0
- data/lib/bolt/inventory/target.rb +17 -1
- data/lib/bolt/node/output.rb +1 -1
- data/lib/bolt/outputter/human.rb +5 -4
- data/lib/bolt/outputter/json.rb +1 -1
- data/lib/bolt/pal.rb +32 -14
- data/lib/bolt/pal/yaml_plan.rb +1 -0
- data/lib/bolt/plugin.rb +14 -8
- data/lib/bolt/plugin/env_var.rb +2 -1
- data/lib/bolt/plugin/module.rb +40 -7
- data/lib/bolt/plugin/prompt.rb +1 -1
- data/lib/bolt/plugin/puppetdb.rb +5 -2
- data/lib/bolt/project.rb +135 -0
- data/lib/bolt/puppetdb/config.rb +16 -28
- data/lib/bolt/rerun.rb +1 -1
- data/lib/bolt/resource_instance.rb +126 -0
- data/lib/bolt/result.rb +46 -23
- data/lib/bolt/result_set.rb +2 -5
- data/lib/bolt/secret.rb +20 -4
- data/lib/bolt/shell/bash.rb +27 -14
- data/lib/bolt/shell/bash/tmpdir.rb +1 -1
- data/lib/bolt/shell/powershell.rb +43 -15
- data/lib/bolt/shell/powershell/snippets.rb +1 -1
- data/lib/bolt/target.rb +18 -2
- data/lib/bolt/transport/base.rb +24 -8
- data/lib/bolt/transport/docker.rb +3 -3
- data/lib/bolt/transport/docker/connection.rb +11 -7
- data/lib/bolt/transport/local/connection.rb +13 -7
- data/lib/bolt/transport/orch.rb +5 -1
- data/lib/bolt/transport/ssh.rb +6 -2
- data/lib/bolt/transport/ssh/connection.rb +26 -1
- data/lib/bolt/transport/ssh/exec_connection.rb +110 -0
- data/lib/bolt/transport/winrm/connection.rb +10 -2
- data/lib/bolt/version.rb +1 -1
- data/lib/bolt_server/pe/pal.rb +1 -38
- data/lib/bolt_server/transport_app.rb +7 -7
- data/lib/bolt_spec/bolt_context.rb +3 -6
- data/lib/bolt_spec/plans.rb +78 -8
- data/lib/bolt_spec/plans/action_stubs.rb +37 -7
- data/lib/bolt_spec/plans/action_stubs/plan_stub.rb +55 -0
- data/lib/bolt_spec/plans/mock_executor.rb +62 -2
- data/lib/bolt_spec/run.rb +10 -13
- metadata +26 -7
- data/lib/bolt/boltdir.rb +0 -54
- data/lib/bolt/plugin/pkcs7.rb +0 -104
- data/lib/bolt/secret/base.rb +0 -41
data/lib/bolt/plugin/prompt.rb
CHANGED
data/lib/bolt/plugin/puppetdb.rb
CHANGED
@@ -14,8 +14,11 @@ module Bolt
|
|
14
14
|
TEMPLATE_OPTS = %w[alias config facts features name uri vars].freeze
|
15
15
|
PLUGIN_OPTS = %w[_plugin query target_mapping].freeze
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
attr_reader :puppetdb_client
|
18
|
+
|
19
|
+
def initialize(config:, context:)
|
20
|
+
pdb_config = Bolt::PuppetDB::Config.load_config(config, context.boltdir)
|
21
|
+
@puppetdb_client = Bolt::PuppetDB::Client.new(pdb_config)
|
19
22
|
@logger = Logging.logger[self]
|
20
23
|
end
|
21
24
|
|
data/lib/bolt/project.rb
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'bolt/pal'
|
5
|
+
|
6
|
+
module Bolt
|
7
|
+
class Project
|
8
|
+
BOLTDIR_NAME = 'Boltdir'
|
9
|
+
PROJECT_SETTINGS = {
|
10
|
+
"name" => "The name of the project",
|
11
|
+
"plans" => "An array of plan names to whitelist. Whitelisted plans are included in `bolt plan show` output",
|
12
|
+
"tasks" => "An array of task names to whitelist. Whitelisted plans are included in `bolt task show` output"
|
13
|
+
}.freeze
|
14
|
+
|
15
|
+
attr_reader :path, :config_file, :inventory_file, :modulepath, :hiera_config,
|
16
|
+
:puppetfile, :rerunfile, :type, :resource_types
|
17
|
+
|
18
|
+
def self.default_project
|
19
|
+
Project.new(File.join('~', '.puppetlabs', 'bolt'), 'user')
|
20
|
+
end
|
21
|
+
|
22
|
+
# Search recursively up the directory hierarchy for the Project. Look for a
|
23
|
+
# directory called Boltdir or a file called bolt.yaml (for a control repo
|
24
|
+
# type Project). Otherwise, repeat the check on each directory up the
|
25
|
+
# hierarchy, falling back to the default if we reach the root.
|
26
|
+
def self.find_boltdir(dir)
|
27
|
+
dir = Pathname.new(dir)
|
28
|
+
if (dir + BOLTDIR_NAME).directory?
|
29
|
+
new(dir + BOLTDIR_NAME, 'embedded')
|
30
|
+
elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
|
31
|
+
new(dir, 'local')
|
32
|
+
elsif dir.root?
|
33
|
+
default_project
|
34
|
+
else
|
35
|
+
find_boltdir(dir.parent)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(path, type = 'option')
|
40
|
+
@path = Pathname.new(path).expand_path
|
41
|
+
@config_file = @path + 'bolt.yaml'
|
42
|
+
@inventory_file = @path + 'inventory.yaml'
|
43
|
+
@modulepath = [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
|
44
|
+
@hiera_config = @path + 'hiera.yaml'
|
45
|
+
@puppetfile = @path + 'Puppetfile'
|
46
|
+
@rerunfile = @path + '.rerun.json'
|
47
|
+
@resource_types = @path + '.resource_types'
|
48
|
+
@type = type
|
49
|
+
|
50
|
+
@project_file = @path + 'bolt-project.yaml'
|
51
|
+
@data = Bolt::Util.read_optional_yaml_hash(File.expand_path(@project_file), 'project') || {}
|
52
|
+
validate if load_as_module?
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
@path.to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
# This API is used to prepend the project as a module to Puppet's internal
|
60
|
+
# module_references list. CHANGE AT YOUR OWN RISK
|
61
|
+
def to_h
|
62
|
+
{ path: @path, name: name }
|
63
|
+
end
|
64
|
+
|
65
|
+
def eql?(other)
|
66
|
+
path == other.path
|
67
|
+
end
|
68
|
+
alias == eql?
|
69
|
+
|
70
|
+
def load_as_module?
|
71
|
+
@project_file.file?
|
72
|
+
end
|
73
|
+
|
74
|
+
def name
|
75
|
+
# If the project is in mymod/Boltdir/bolt-project.yaml, use mymod as the project name
|
76
|
+
dirname = @path.basename.to_s == 'Boltdir' ? @path.parent.basename.to_s : @path.basename.to_s
|
77
|
+
pname = @data['name'] || dirname
|
78
|
+
pname.include?('-') ? pname.split('-', 2)[1] : pname
|
79
|
+
end
|
80
|
+
|
81
|
+
def tasks
|
82
|
+
@data['tasks']
|
83
|
+
end
|
84
|
+
|
85
|
+
def plans
|
86
|
+
@data['plans']
|
87
|
+
end
|
88
|
+
|
89
|
+
def project_directory_name?(name)
|
90
|
+
# it must match an installed project name according to forge validator
|
91
|
+
name =~ /^[a-z][a-z0-9_]*$/
|
92
|
+
end
|
93
|
+
|
94
|
+
def project_namespaced_name?(name)
|
95
|
+
# it must match the full project name according to forge validator
|
96
|
+
name =~ /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
97
|
+
end
|
98
|
+
|
99
|
+
def validate
|
100
|
+
n = @data['name']
|
101
|
+
if n && !project_directory_name?(n) && !project_namespaced_name?(n)
|
102
|
+
raise Bolt::ValidationError, <<~ERROR_STRING
|
103
|
+
Invalid project name '#{n}' in bolt-project.yaml; project names must match either:
|
104
|
+
An installed project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
|
105
|
+
A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
106
|
+
ERROR_STRING
|
107
|
+
elsif !project_directory_name?(name) && !project_namespaced_name?(name)
|
108
|
+
raise Bolt::ValidationError, <<~ERROR_STRING
|
109
|
+
Invalid project name '#{name}'; project names must match either:
|
110
|
+
A project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
|
111
|
+
A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
|
112
|
+
|
113
|
+
Configure project name in <project_dir>/bolt-project.yaml
|
114
|
+
ERROR_STRING
|
115
|
+
# If the project name is the same as one of the built-in modules raise a warning
|
116
|
+
elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
|
117
|
+
raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
|
118
|
+
"with a built-in Bolt module of the same name."
|
119
|
+
end
|
120
|
+
|
121
|
+
%w[tasks plans].each do |conf|
|
122
|
+
unless @data.fetch(conf, []).is_a?(Array)
|
123
|
+
raise Bolt::ValidationError, "'#{conf}' in bolt-project.yaml must be an array"
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def check_deprecated_file
|
129
|
+
if (@path + 'project.yaml').file?
|
130
|
+
logger = Logging.logger[self]
|
131
|
+
logger.warn "Project configuration file 'project.yaml' is deprecated; use 'bolt-project.yaml' instead."
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
data/lib/bolt/puppetdb/config.rb
CHANGED
@@ -22,37 +22,29 @@ module Bolt
|
|
22
22
|
File.expand_path(File.join(Dir::COMMON_APPDATA, 'PuppetLabs/client-tools/puppetdb.conf'))
|
23
23
|
end
|
24
24
|
|
25
|
-
def self.load_config(
|
25
|
+
def self.load_config(options, project_path = nil)
|
26
26
|
config = {}
|
27
27
|
global_path = Bolt::Util.windows? ? default_windows_config : DEFAULT_CONFIG[:global]
|
28
|
-
if filename
|
29
|
-
if File.exist?(filename)
|
30
|
-
config = JSON.parse(File.read(filename))
|
31
|
-
else
|
32
|
-
raise Bolt::PuppetDBError, "config file #{filename} does not exist"
|
33
|
-
end
|
34
|
-
else
|
35
|
-
if File.exist?(DEFAULT_CONFIG[:user])
|
36
|
-
filepath = DEFAULT_CONFIG[:user]
|
37
|
-
elsif File.exist?(global_path)
|
38
|
-
filepath = global_path
|
39
|
-
end
|
40
28
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
29
|
+
if File.exist?(DEFAULT_CONFIG[:user])
|
30
|
+
filepath = DEFAULT_CONFIG[:user]
|
31
|
+
elsif File.exist?(global_path)
|
32
|
+
filepath = global_path
|
33
|
+
end
|
34
|
+
|
35
|
+
begin
|
36
|
+
config = JSON.parse(File.read(filepath)) if filepath
|
37
|
+
rescue StandardError => e
|
38
|
+
Logging.logger[self].error("Could not load puppetdb.conf from #{filepath}: #{e.message}")
|
46
39
|
end
|
47
40
|
|
48
41
|
config = config.fetch('puppetdb', {})
|
49
|
-
new(config.merge(options),
|
42
|
+
new(config.merge(options), project_path)
|
50
43
|
end
|
51
44
|
|
52
|
-
def initialize(settings,
|
45
|
+
def initialize(settings, project_path = nil)
|
53
46
|
@settings = settings
|
54
|
-
|
55
|
-
expand_paths
|
47
|
+
expand_paths(project_path)
|
56
48
|
end
|
57
49
|
|
58
50
|
def token
|
@@ -68,14 +60,10 @@ module Bolt
|
|
68
60
|
@token = @token.strip if @token
|
69
61
|
end
|
70
62
|
|
71
|
-
def expand_paths
|
63
|
+
def expand_paths(project_path)
|
72
64
|
%w[cacert cert key token].each do |file|
|
73
65
|
next unless @settings[file]
|
74
|
-
@settings[file] =
|
75
|
-
File.expand_path(@settings[file], @boltdir_path)
|
76
|
-
else
|
77
|
-
File.expand_path(@settings[file])
|
78
|
-
end
|
66
|
+
@settings[file] = File.expand_path(@settings[file], project_path)
|
79
67
|
end
|
80
68
|
end
|
81
69
|
|
data/lib/bolt/rerun.rb
CHANGED
@@ -45,7 +45,7 @@ module Bolt
|
|
45
45
|
end
|
46
46
|
|
47
47
|
if result_set.is_a?(Bolt::ResultSet)
|
48
|
-
data = result_set.map { |res| res.
|
48
|
+
data = result_set.map { |res| { target: res.target.name, status: res.status } }
|
49
49
|
FileUtils.mkdir_p(File.dirname(@path))
|
50
50
|
File.write(@path, data.to_json)
|
51
51
|
elsif File.exist?(@path)
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Bolt
|
6
|
+
class ResourceInstance
|
7
|
+
attr_reader :target, :type, :title, :state, :desired_state
|
8
|
+
attr_accessor :events
|
9
|
+
|
10
|
+
# Needed by Puppet to recognize Bolt::ResourceInstance as a Puppet object when deserializing
|
11
|
+
def self._pcore_type
|
12
|
+
ResourceInstance
|
13
|
+
end
|
14
|
+
|
15
|
+
# Needed by Puppet to serialize with _pcore_init_hash instead of the object's attributes
|
16
|
+
def self._pcore_init_from_hash(_init_hash)
|
17
|
+
raise "ResourceInstance shouldn't be instantiated from a pcore_init class method. "\
|
18
|
+
"How did this get called?"
|
19
|
+
end
|
20
|
+
|
21
|
+
def _pcore_init_from_hash(init_hash)
|
22
|
+
initialize(init_hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parameters will already be validated when calling ResourceInstance.new or
|
26
|
+
# set_resources() from a plan. We don't perform any validation in the class
|
27
|
+
# itself since Puppet will pass an empty hash to the initializer as part of
|
28
|
+
# the deserialization process before passing the _pcore_init_hash.
|
29
|
+
def initialize(resource_hash)
|
30
|
+
@target = resource_hash['target']
|
31
|
+
@type = resource_hash['type'].to_s.capitalize
|
32
|
+
@title = resource_hash['title']
|
33
|
+
# get_resources() returns observed state under the 'parameters' key
|
34
|
+
@state = resource_hash['state'] || resource_hash['parameters'] || {}
|
35
|
+
@desired_state = resource_hash['desired_state'] || {}
|
36
|
+
@events = resource_hash['events'] || []
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a ResourceInstance from a data hash in a plan when calling
|
40
|
+
# ResourceInstance.new($resource_hash) or $target.set_resources($resource_hash)
|
41
|
+
def self.from_asserted_hash(resource_hash)
|
42
|
+
new(resource_hash)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Creates a ResourceInstance from positional arguments in a plan when
|
46
|
+
# calling ResourceInstance.new(target, type, title, ...)
|
47
|
+
def self.from_asserted_args(target,
|
48
|
+
type,
|
49
|
+
title,
|
50
|
+
state = nil,
|
51
|
+
desired_state = nil,
|
52
|
+
events = nil)
|
53
|
+
new(
|
54
|
+
'target' => target,
|
55
|
+
'type' => type,
|
56
|
+
'title' => title,
|
57
|
+
'state' => state,
|
58
|
+
'desired_state' => desired_state,
|
59
|
+
'events' => events
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def eql?(other)
|
64
|
+
self.class.equal?(other.class) &&
|
65
|
+
target == other.target &&
|
66
|
+
type == other.type &&
|
67
|
+
title == other.title
|
68
|
+
end
|
69
|
+
alias == eql?
|
70
|
+
|
71
|
+
def to_hash
|
72
|
+
{
|
73
|
+
'target' => target,
|
74
|
+
'type' => type,
|
75
|
+
'title' => title,
|
76
|
+
'state' => state,
|
77
|
+
'desired_state' => desired_state,
|
78
|
+
'events' => events
|
79
|
+
}
|
80
|
+
end
|
81
|
+
alias _pcore_init_hash to_hash
|
82
|
+
|
83
|
+
def to_json(opts = nil)
|
84
|
+
to_hash.to_json(opts)
|
85
|
+
end
|
86
|
+
|
87
|
+
def reference
|
88
|
+
"#{type}[#{title}]"
|
89
|
+
end
|
90
|
+
alias to_s reference
|
91
|
+
|
92
|
+
def add_event(event)
|
93
|
+
@events << event
|
94
|
+
end
|
95
|
+
|
96
|
+
# rubocop:disable Naming/AccessorMethodName
|
97
|
+
def set_state(state)
|
98
|
+
assert_hash('state', state)
|
99
|
+
@state.merge!(state)
|
100
|
+
end
|
101
|
+
# rubocop:enable Naming/AccessorMethodName
|
102
|
+
|
103
|
+
def overwrite_state(state)
|
104
|
+
assert_hash('state', state)
|
105
|
+
@state = state
|
106
|
+
end
|
107
|
+
|
108
|
+
# rubocop:disable Naming/AccessorMethodName
|
109
|
+
def set_desired_state(desired_state)
|
110
|
+
assert_hash('desired_state', desired_state)
|
111
|
+
@desired_state.merge!(desired_state)
|
112
|
+
end
|
113
|
+
# rubocop:enable Naming/AccessorMethodName
|
114
|
+
|
115
|
+
def overwrite_desired_state(desired_state)
|
116
|
+
assert_hash('desired_state', desired_state)
|
117
|
+
@desired_state = desired_state
|
118
|
+
end
|
119
|
+
|
120
|
+
def assert_hash(loc, value)
|
121
|
+
unless value.is_a?(Hash)
|
122
|
+
raise Bolt::ValidationError, "#{loc} must be of type Hash; got #{value.class}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/bolt/result.rb
CHANGED
@@ -40,18 +40,23 @@ module Bolt
|
|
40
40
|
end
|
41
41
|
|
42
42
|
def self.for_task(target, stdout, stderr, exit_code, task)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
43
|
+
stdout.force_encoding('utf-8') unless stdout.encoding == Encoding::UTF_8
|
44
|
+
value = if stdout.valid_encoding?
|
45
|
+
parse_hash(stdout) || { '_output' => stdout }
|
46
|
+
else
|
47
|
+
{ '_error' => { 'kind' => 'puppetlabs.tasks/task-error',
|
48
|
+
'issue_code' => 'TASK_ERROR',
|
49
|
+
'msg' => 'The task result contained invalid UTF-8 on stdout',
|
50
|
+
'details' => {} } }
|
51
|
+
end
|
52
|
+
|
52
53
|
if exit_code != 0 && value['_error'].nil?
|
53
54
|
msg = if stdout.empty?
|
54
|
-
|
55
|
+
if stderr.empty?
|
56
|
+
"The task failed with exit code #{exit_code} and no output"
|
57
|
+
else
|
58
|
+
"The task failed with exit code #{exit_code} and no stdout, but stderr contained:\n#{stderr}"
|
59
|
+
end
|
55
60
|
else
|
56
61
|
"The task failed with exit code #{exit_code}"
|
57
62
|
end
|
@@ -63,6 +68,13 @@ module Bolt
|
|
63
68
|
new(target, value: value, action: 'task', object: task)
|
64
69
|
end
|
65
70
|
|
71
|
+
def self.parse_hash(string)
|
72
|
+
value = JSON.parse(string)
|
73
|
+
value if value.is_a? Hash
|
74
|
+
rescue JSON::ParserError
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
66
78
|
def self.for_upload(target, source, destination)
|
67
79
|
new(target, message: "Uploaded '#{source}' to '#{target.host}:#{destination}'", action: 'upload', object: source)
|
68
80
|
end
|
@@ -110,18 +122,8 @@ module Bolt
|
|
110
122
|
message && !message.strip.empty?
|
111
123
|
end
|
112
124
|
|
113
|
-
def status_hash
|
114
|
-
{
|
115
|
-
target: @target.name,
|
116
|
-
action: action,
|
117
|
-
object: object,
|
118
|
-
status: status,
|
119
|
-
value: @value
|
120
|
-
}
|
121
|
-
end
|
122
|
-
|
123
125
|
def generic_value
|
124
|
-
|
126
|
+
safe_value.reject { |k, _| %w[_error _output].include? k }
|
125
127
|
end
|
126
128
|
|
127
129
|
def eql?(other)
|
@@ -139,15 +141,36 @@ module Bolt
|
|
139
141
|
end
|
140
142
|
|
141
143
|
def to_json(opts = nil)
|
142
|
-
|
144
|
+
to_data.to_json(opts)
|
143
145
|
end
|
144
146
|
|
145
147
|
def to_s
|
146
148
|
to_json
|
147
149
|
end
|
148
150
|
|
151
|
+
# This is the value with all non-UTF-8 characters removed, suitable for
|
152
|
+
# printing or converting to JSON. It *should* only be possible to have
|
153
|
+
# non-UTF-8 characters in stdout/stderr keys as they are not allowed from
|
154
|
+
# tasks but we scrub the whole thing just in case.
|
155
|
+
def safe_value
|
156
|
+
Bolt::Util.walk_vals(value) do |val|
|
157
|
+
if val.is_a?(String)
|
158
|
+
# Replace invalid bytes with hex codes, ie. \xDE\xAD\xBE\xEF
|
159
|
+
val.scrub { |c| c.bytes.map { |b| "\\x" + b.to_s(16).upcase }.join }
|
160
|
+
else
|
161
|
+
val
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
149
166
|
def to_data
|
150
|
-
|
167
|
+
{
|
168
|
+
"target" => @target.name,
|
169
|
+
"action" => action,
|
170
|
+
"object" => object,
|
171
|
+
"status" => status,
|
172
|
+
"value" => safe_value
|
173
|
+
}
|
151
174
|
end
|
152
175
|
|
153
176
|
def status
|