bolt 2.44.0 → 3.4.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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +11 -9
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +25 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +2 -2
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +44 -5
  8. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  9. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +7 -3
  10. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -2
  11. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  12. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  13. data/lib/bolt/apply_result.rb +1 -1
  14. data/lib/bolt/bolt_option_parser.rb +9 -123
  15. data/lib/bolt/cli.rb +125 -127
  16. data/lib/bolt/config.rb +39 -214
  17. data/lib/bolt/config/options.rb +34 -125
  18. data/lib/bolt/config/transport/local.rb +1 -0
  19. data/lib/bolt/config/transport/lxd.rb +23 -0
  20. data/lib/bolt/config/transport/options.rb +9 -2
  21. data/lib/bolt/executor.rb +20 -5
  22. data/lib/bolt/logger.rb +9 -1
  23. data/lib/bolt/module_installer.rb +2 -2
  24. data/lib/bolt/module_installer/puppetfile.rb +2 -2
  25. data/lib/bolt/module_installer/specs/forge_spec.rb +2 -2
  26. data/lib/bolt/module_installer/specs/git_spec.rb +2 -2
  27. data/lib/bolt/node/output.rb +14 -4
  28. data/lib/bolt/outputter/human.rb +52 -24
  29. data/lib/bolt/outputter/json.rb +16 -16
  30. data/lib/bolt/pal.rb +26 -5
  31. data/lib/bolt/pal/yaml_plan.rb +1 -2
  32. data/lib/bolt/pal/yaml_plan/evaluator.rb +5 -153
  33. data/lib/bolt/pal/yaml_plan/step.rb +91 -52
  34. data/lib/bolt/pal/yaml_plan/step/command.rb +21 -13
  35. data/lib/bolt/pal/yaml_plan/step/download.rb +15 -16
  36. data/lib/bolt/pal/yaml_plan/step/eval.rb +11 -11
  37. data/lib/bolt/pal/yaml_plan/step/message.rb +13 -4
  38. data/lib/bolt/pal/yaml_plan/step/plan.rb +19 -15
  39. data/lib/bolt/pal/yaml_plan/step/resources.rb +82 -21
  40. data/lib/bolt/pal/yaml_plan/step/script.rb +36 -17
  41. data/lib/bolt/pal/yaml_plan/step/task.rb +19 -16
  42. data/lib/bolt/pal/yaml_plan/step/upload.rb +16 -17
  43. data/lib/bolt/pal/yaml_plan/transpiler.rb +3 -3
  44. data/lib/bolt/plan_creator.rb +1 -1
  45. data/lib/bolt/plugin/module.rb +0 -23
  46. data/lib/bolt/plugin/puppet_connect_data.rb +45 -3
  47. data/lib/bolt/project.rb +16 -56
  48. data/lib/bolt/project_manager.rb +5 -4
  49. data/lib/bolt/project_manager/module_migrator.rb +7 -6
  50. data/lib/bolt/result.rb +10 -11
  51. data/lib/bolt/shell.rb +16 -0
  52. data/lib/bolt/shell/bash.rb +61 -31
  53. data/lib/bolt/shell/bash/tmpdir.rb +2 -2
  54. data/lib/bolt/shell/powershell.rb +35 -14
  55. data/lib/bolt/shell/powershell/snippets.rb +37 -150
  56. data/lib/bolt/task.rb +1 -1
  57. data/lib/bolt/transport/base.rb +0 -9
  58. data/lib/bolt/transport/docker.rb +1 -125
  59. data/lib/bolt/transport/docker/connection.rb +86 -161
  60. data/lib/bolt/transport/local.rb +1 -9
  61. data/lib/bolt/transport/lxd.rb +26 -0
  62. data/lib/bolt/transport/lxd/connection.rb +99 -0
  63. data/lib/bolt/transport/orch.rb +13 -5
  64. data/lib/bolt/transport/ssh/connection.rb +1 -1
  65. data/lib/bolt/transport/winrm/connection.rb +1 -1
  66. data/lib/bolt/util.rb +8 -0
  67. data/lib/bolt/version.rb +1 -1
  68. data/lib/bolt_server/transport_app.rb +61 -33
  69. data/lib/bolt_spec/bolt_context.rb +9 -4
  70. data/lib/bolt_spec/plans.rb +1 -109
  71. data/lib/bolt_spec/plans/action_stubs.rb +1 -1
  72. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  73. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  74. data/lib/bolt_spec/plans/mock_executor.rb +4 -0
  75. data/modules/aggregate/plans/count.pp +21 -0
  76. data/modules/aggregate/plans/targets.pp +21 -0
  77. data/modules/puppet_connect/plans/test_input_data.pp +67 -0
  78. data/modules/puppetdb_fact/plans/init.pp +10 -0
  79. metadata +7 -3
  80. data/modules/aggregate/plans/nodes.pp +0 -36
@@ -5,31 +5,30 @@ module Bolt
5
5
  class YamlPlan
6
6
  class Step
7
7
  class Upload < Step
8
- def self.allowed_keys
9
- super + Set['source', 'destination', 'upload']
8
+ def self.option_keys
9
+ Set['catch_errors', 'run_as']
10
10
  end
11
11
 
12
12
  def self.required_keys
13
- Set['upload', 'destination', 'targets']
13
+ Set['destination', 'targets', 'upload']
14
14
  end
15
15
 
16
- def initialize(step_body)
17
- super
18
- @source = step_body['upload'] || step_body['source']
19
- @destination = step_body['destination']
20
- end
21
-
22
- def transpile
23
- code = String.new(" ")
24
- code << "$#{@name} = " if @name
16
+ # Returns an array of arguments to pass to the step's function call
17
+ #
18
+ private def format_args(body)
19
+ opts = format_options(body)
25
20
 
26
- fn = 'upload_file'
27
- args = [@source, @destination, @targets]
28
- args << @description if @description
21
+ args = [body['upload'], body['destination'], body['targets']]
22
+ args << body['description'] if body['description']
23
+ args << opts if opts.any?
29
24
 
30
- code << function_call(fn, args)
25
+ args
26
+ end
31
27
 
32
- code << "\n"
28
+ # Returns the function corresponding to the step
29
+ #
30
+ private def function
31
+ 'upload_file'
33
32
  end
34
33
  end
35
34
  end
@@ -14,8 +14,8 @@ module Bolt
14
14
  end
15
15
  end
16
16
 
17
- def transpile(relative_path)
18
- @plan_path = File.expand_path(relative_path)
17
+ def transpile(plan_path)
18
+ @plan_path = plan_path
19
19
  @modulename = Bolt::Util.module_name(@plan_path)
20
20
  @filename = @plan_path.split(File::SEPARATOR)[-1]
21
21
  validate_path
@@ -29,7 +29,7 @@ module Bolt
29
29
 
30
30
  plan_string = String.new('')
31
31
  plan_string << "# #{plan_object.description}\n" if plan_object.description
32
- plan_string << "# WARNING: This is an autogenerated plan. It may not behave as expected.\n"
32
+ plan_string << "# WARNING: This is an autogenerated plan. It might not behave as expected.\n"
33
33
  plan_string << "# @private #{plan_object.private}\n" unless plan_object.private.nil?
34
34
  plan_string << "#{param_descriptions}\n" unless param_descriptions.empty?
35
35
 
@@ -22,7 +22,7 @@ module Bolt
22
22
  Invalid plan name '#{plan_name}'. Plan names are composed of one or more name segments
23
23
  separated by double colons '::'.
24
24
 
25
- Each name segment must begin with a lowercase letter, and may only include lowercase
25
+ Each name segment must begin with a lowercase letter, and can only include lowercase
26
26
  letters, digits, and underscores.
27
27
 
28
28
  Examples of valid plan names:
@@ -30,10 +30,6 @@ module Bolt
30
30
  @module = mod
31
31
  @config = config
32
32
  @context = context
33
-
34
- if @module.name == 'pkcs7'
35
- @config = handle_deprecated_pkcs7_keys(@config)
36
- end
37
33
  end
38
34
 
39
35
  # This method interacts with the module on disk so it's separate from initialize
@@ -160,10 +156,6 @@ module Bolt
160
156
  # out now.
161
157
  meta, params = opts.partition { |key, _val| key.start_with?('_') }.map(&:to_h)
162
158
 
163
- if task.module_name == 'pkcs7'
164
- params = handle_deprecated_pkcs7_keys(params)
165
- end
166
-
167
159
  # Reject parameters from config that are not accepted by the task and
168
160
  # merge in parameter defaults
169
161
  params = if task.parameters
@@ -181,21 +173,6 @@ module Bolt
181
173
  [params, meta]
182
174
  end
183
175
 
184
- # Raises a deprecation warning if the pkcs7 plugin is using deprecated keys and
185
- # modifies the keys so they are the correct format
186
- def handle_deprecated_pkcs7_keys(params)
187
- if params.key?('private-key') || params.key?('public-key')
188
- message = "pkcs7 keys 'private-key' and 'public-key' have been deprecated and will be "\
189
- "removed in a future version of Bolt; use 'private_key' and 'public_key' instead."
190
- Bolt::Logger.deprecate("pkcs7_params", message)
191
- end
192
-
193
- params['private_key'] = params.delete('private-key') if params.key?('private-key')
194
- params['public_key'] = params.delete('public-key') if params.key?('public-key')
195
-
196
- params
197
- end
198
-
199
176
  def extract_task_parameter_schema
200
177
  # Get the intersection of expected types (using Set)
201
178
  type_set = @hook_map.each_with_object({}) do |(_hook, task), acc|
@@ -3,12 +3,45 @@
3
3
  module Bolt
4
4
  class Plugin
5
5
  class PuppetConnectData
6
+ INPUT_DATA_VAR = 'PUPPET_CONNECT_INPUT_DATA'
7
+
6
8
  def initialize(context:, **_opts)
7
- puppet_connect_data_yaml_path = File.join(context.boltdir, 'puppet_connect_data.yaml')
9
+ if ENV.key?(INPUT_DATA_VAR)
10
+ # The user provided input data that they will copy-paste into the Puppet Connect UI
11
+ # for inventory syncing. This environment variable will likely be set when invoking a
12
+ # general "test Puppet Connect input data" command. That command tests that parsing
13
+ # the inventory with the given input data results in connectable targets. Part of
14
+ # that requires validating that the input data contains all of the referenced keys,
15
+ # which is what this plugin will do in validate_resolve_reference.
16
+ @input_data_path = ENV[INPUT_DATA_VAR]
17
+ data_path = @input_data_path
18
+ else
19
+ # The user is using this plugin during a regular Bolt invocation, so fetch the (minimal)
20
+ # required data from the default location. This data should typically be non-autoloadable
21
+ # secrets like WinRM passwords.
22
+ #
23
+ # Note that any unspecified keys will be resolved to nil.
24
+ data_path = File.join(context.boltdir, 'puppet_connect_data.yaml')
25
+ end
26
+
8
27
  @data = Bolt::Util.read_optional_yaml_hash(
9
- puppet_connect_data_yaml_path,
10
- 'puppet_connect_data.yaml'
28
+ data_path,
29
+ File.basename(data_path)
11
30
  )
31
+
32
+ if @input_data_path
33
+ # Validate that the data does not contain any plugin-reference
34
+ # values
35
+ @data.each do |key, toplevel_value|
36
+ # Use walk_vals to check for nested plugin references
37
+ Bolt::Util.walk_vals(toplevel_value) do |current_value|
38
+ if current_value.is_a?(Hash) && current_value.key?('_plugin')
39
+ raise invalid_input_data_err("the #{key} key's value contains a plugin reference")
40
+ end
41
+ current_value
42
+ end
43
+ end
44
+ end
12
45
  end
13
46
 
14
47
  def name
@@ -29,6 +62,15 @@ module Bolt
29
62
  raise Bolt::ValidationError,
30
63
  "puppet_connect_data plugin requires that 'key' be specified"
31
64
  end
65
+ if @input_data_path && !@data.key?(opts['key'])
66
+ # Input data for Puppet Connect was provided and opts['key'] does not have a
67
+ # value specified. Raise an error for this case.
68
+ raise invalid_input_data_err("a value for the #{opts['key']} key is not specified")
69
+ end
70
+ end
71
+
72
+ def invalid_input_data_err(msg)
73
+ Bolt::ValidationError.new("invalid input data #{@input_data_path}: #{msg}")
32
74
  end
33
75
  end
34
76
  end
data/lib/bolt/project.rb CHANGED
@@ -11,7 +11,7 @@ module Bolt
11
11
  BOLTDIR_NAME = 'Boltdir'
12
12
  CONFIG_NAME = 'bolt-project.yaml'
13
13
 
14
- attr_reader :path, :data, :config_file, :inventory_file, :hiera_config,
14
+ attr_reader :path, :data, :inventory_file, :hiera_config,
15
15
  :puppetfile, :rerunfile, :type, :resource_types, :project_file,
16
16
  :downloads, :plans_path, :modulepath, :managed_moduledir,
17
17
  :backup_dir, :plugin_cache_file, :plan_cache_file
@@ -24,31 +24,21 @@ module Bolt
24
24
  end
25
25
 
26
26
  # Search recursively up the directory hierarchy for the Project. Look for a
27
- # directory called Boltdir or a file called bolt.yaml (for a control repo
28
- # type Project). Otherwise, repeat the check on each directory up the
27
+ # directory called Boltdir or a file called bolt-project.yaml (for a control
28
+ # repo type Project). Otherwise, repeat the check on each directory up the
29
29
  # hierarchy, falling back to the default if we reach the root.
30
30
  def self.find_boltdir(dir)
31
31
  dir = Pathname.new(dir)
32
32
 
33
33
  if (dir + BOLTDIR_NAME).directory?
34
34
  create_project(dir + BOLTDIR_NAME, 'embedded')
35
- elsif (dir + 'bolt.yaml').file?
36
- command = Bolt::Util.powershell? ? 'Update-BoltProject' : 'bolt project migrate'
37
- Bolt::Logger.deprecate(
38
- "bolt_yaml",
39
- "Configuration file #{dir + 'bolt.yaml'} is deprecated and will be "\
40
- "removed in Bolt 3.0.\nUpdate your Bolt project to the latest Bolt practices "\
41
- "using #{command}."
42
- )
43
- create_project(dir, 'local')
44
35
  elsif (dir + CONFIG_NAME).file?
45
36
  create_project(dir, 'local')
46
37
  elsif dir.root?
47
38
  default_project
48
39
  else
49
40
  Bolt::Logger.debug(
50
- "Did not detect Boltdir, bolt.yaml, or bolt-project.yaml at '#{dir}'. "\
51
- "This directory won't be loaded as a project."
41
+ "Did not detect Boltdir or bolt-project.yaml at '#{dir}'. This directory won't be loaded as a project."
52
42
  )
53
43
  find_boltdir(dir.parent)
54
44
  end
@@ -85,9 +75,8 @@ module Bolt
85
75
  project_file = File.join(fullpath, CONFIG_NAME)
86
76
  data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
87
77
  default = type =~ /user|system/ ? 'default ' : ''
88
- exist = File.exist?(File.expand_path(project_file))
89
78
 
90
- if exist
79
+ if File.exist?(File.expand_path(project_file))
91
80
  Bolt::Logger.info("Loaded #{default}project from '#{fullpath}'")
92
81
  end
93
82
 
@@ -105,67 +94,37 @@ module Bolt
105
94
  def self.schema
106
95
  {
107
96
  type: Hash,
108
- properties: Bolt::Config::BOLT_PROJECT_OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
97
+ properties: Bolt::Config::PROJECT_OPTIONS.map { |opt| [opt, _ref: opt] }.to_h,
109
98
  definitions: Bolt::Config::OPTIONS
110
99
  }
111
100
  end
112
101
 
113
- def initialize(raw_data, path, type = 'option')
114
- @path = Pathname.new(path).expand_path
115
- @project_file = @path + CONFIG_NAME
116
-
117
- if (@path + 'bolt.yaml').file? && project_file?
118
- Bolt::Logger.deprecate(
119
- "bolt_yaml",
120
- "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
121
- "Transport config should be set in inventory.yaml, all other config should be set in "\
122
- "bolt-project.yaml."
123
- )
124
- end
125
-
102
+ def initialize(data, path, type = 'option')
103
+ @type = type
104
+ @path = Pathname.new(path).expand_path
105
+ @project_file = @path + CONFIG_NAME
126
106
  @inventory_file = @path + 'inventory.yaml'
127
107
  @hiera_config = @path + 'hiera.yaml'
128
108
  @puppetfile = @path + 'Puppetfile'
129
109
  @rerunfile = @path + '.rerun.json'
130
110
  @resource_types = @path + '.resource_types'
131
- @type = type
132
111
  @downloads = @path + 'downloads'
133
112
  @plans_path = @path + 'plans'
134
113
  @managed_moduledir = @path + '.modules'
135
114
  @backup_dir = @path + '.bolt-bak'
136
115
  @plugin_cache_file = @path + '.plugin_cache.json'
137
116
  @plan_cache_file = @path + '.plan_cache.json'
117
+ @modulepath = [(@path + 'modules').to_s]
138
118
 
139
- if (tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys).any?
119
+ if (tc = Bolt::Config::INVENTORY_OPTIONS.keys & data.keys).any?
140
120
  Bolt::Logger.warn(
141
121
  "project_transport_config",
142
122
  "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}."
143
123
  )
144
124
  end
145
125
 
146
- @data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
147
-
148
- # If the 'modules' key is present in the project configuration file,
149
- # use the new, shorter modulepath.
150
- @modulepath = if @data.key?('modules')
151
- [(@path + 'modules').to_s]
152
- else
153
- [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
154
- end
155
-
156
- # Once bolt.yaml deprecation is removed, this attribute should be removed
157
- # and replaced with .project_file in lib/bolt/config.rb
158
- @config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
159
- if (@path + 'bolt.yaml').file?
160
- Bolt::Logger.warn(
161
- "project_config_conflict",
162
- "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
163
- )
164
- end
165
- @project_file
166
- else
167
- @path + 'bolt.yaml'
168
- end
126
+ @data = data.slice(*Bolt::Config::PROJECT_OPTIONS)
127
+
169
128
  validate if project_file?
170
129
  end
171
130
 
@@ -219,7 +178,8 @@ module Bolt
219
178
  end
220
179
 
221
180
  def modules
222
- @modules ||= @data['modules']&.map do |mod|
181
+ mod_data = @data['modules'] || []
182
+ @modules ||= mod_data.map do |mod|
223
183
  if mod.is_a?(String)
224
184
  { 'name' => mod }
225
185
  else
@@ -143,7 +143,7 @@ module Bolt
143
143
  @outputter.print_message("Migrating project #{@config.project.path}\n\n")
144
144
 
145
145
  @outputter.print_action_step(
146
- "Migrating a Bolt project may make irreversible changes to the project's "\
146
+ "Migrating a Bolt project might make irreversible changes to the project's "\
147
147
  "configuration and inventory files. Before continuing, make sure the "\
148
148
  "project has a backup or uses a version control system."
149
149
  )
@@ -166,10 +166,11 @@ module Bolt
166
166
  # Migrates the project-level configuration file to the latest version.
167
167
  #
168
168
  private def migrate_config
169
- migrator = ConfigMigrator.new(@outputter)
169
+ migrator = ConfigMigrator.new(@outputter)
170
+ configfile = @config.project.path + 'bolt.yaml'
170
171
 
171
172
  migrator.migrate(
172
- @config.project.config_file,
173
+ configfile,
173
174
  @config.project.project_file,
174
175
  @config.inventoryfile || @config.project.inventory_file,
175
176
  @config.project.backup_dir
@@ -194,7 +195,7 @@ module Bolt
194
195
 
195
196
  migrator.migrate(
196
197
  @config.project,
197
- @config.modulepath
198
+ @config.modulepath[0...-1]
198
199
  )
199
200
  end
200
201
  end
@@ -6,19 +6,20 @@ module Bolt
6
6
  class ProjectManager
7
7
  class ModuleMigrator < Migrator
8
8
  def migrate(project, configured_modulepath)
9
- return true unless project.modules.nil?
9
+ return true if project.managed_moduledir.exist?
10
10
 
11
11
  @outputter.print_message "Migrating project modules\n\n"
12
12
 
13
13
  config = project.project_file
14
14
  puppetfile = project.puppetfile
15
15
  managed_moduledir = project.managed_moduledir
16
- modulepath = [(project.path + 'modules').to_s,
16
+ new_modulepath = [(project.path + 'modules').to_s]
17
+ old_modulepath = [(project.path + 'modules').to_s,
17
18
  (project.path + 'site-modules').to_s,
18
19
  (project.path + 'site').to_s]
19
20
 
20
21
  # Notify user to manually migrate modules if using non-default modulepath
21
- if configured_modulepath != modulepath
22
+ if configured_modulepath != new_modulepath && configured_modulepath != old_modulepath
22
23
  @outputter.print_action_step(
23
24
  "Project has a non-default configured modulepath, unable to automatically "\
24
25
  "migrate project modules. To migrate project modules manually, see "\
@@ -27,10 +28,10 @@ module Bolt
27
28
  true
28
29
  # Migrate modules from Puppetfile
29
30
  elsif File.exist?(puppetfile)
30
- migrate_modules_from_puppetfile(config, puppetfile, managed_moduledir, modulepath)
31
+ migrate_modules_from_puppetfile(config, puppetfile, managed_moduledir, old_modulepath)
31
32
  # Migrate modules to updated modulepath
32
33
  else
33
- consolidate_modules(modulepath)
34
+ consolidate_modules(old_modulepath)
34
35
  update_project_config([], config)
35
36
  end
36
37
  end
@@ -65,7 +66,7 @@ module Bolt
65
66
  # Attempt to resolve dependencies
66
67
  begin
67
68
  @outputter.print_message('')
68
- @outputter.print_action_step("Resolving module dependencies, this may take a moment")
69
+ @outputter.print_action_step("Resolving module dependencies, this might take a moment")
69
70
  puppetfile = Bolt::ModuleInstaller::Resolver.new.resolve(specs)
70
71
  rescue Bolt::Error => e
71
72
  @outputter.print_action_error("#{e.message}\nSkipping module migration.")
data/lib/bolt/result.rb CHANGED
@@ -28,20 +28,14 @@ module Bolt
28
28
  %w[file line].zip(position).to_h.compact
29
29
  end
30
30
 
31
- def self.for_command(target, stdout, stderr, exit_code, action, command, position)
32
- value = {
33
- 'stdout' => stdout,
34
- 'stderr' => stderr,
35
- 'exit_code' => exit_code
36
- }
37
-
31
+ def self.for_command(target, value, action, command, position)
38
32
  details = create_details(position)
39
- unless exit_code == 0
40
- details['exit_code'] = exit_code
33
+ unless value['exit_code'] == 0
34
+ details['exit_code'] = value['exit_code']
41
35
  value['_error'] = {
42
36
  'kind' => 'puppetlabs.tasks/command-error',
43
37
  'issue_code' => 'COMMAND_ERROR',
44
- 'msg' => "The command failed with exit code #{exit_code}",
38
+ 'msg' => "The command failed with exit code #{value['exit_code']}",
45
39
  'details' => details
46
40
  }
47
41
  end
@@ -203,12 +197,17 @@ module Bolt
203
197
  end
204
198
 
205
199
  def to_data
200
+ serialized_value = safe_value
201
+ if serialized_value.key?('_sensitive') &&
202
+ serialized_value['_sensitive'].is_a?(Puppet::Pops::Types::PSensitiveType::Sensitive)
203
+ serialized_value['_sensitive'] = serialized_value['_sensitive'].to_s
204
+ end
206
205
  {
207
206
  "target" => @target.name,
208
207
  "action" => action,
209
208
  "object" => object,
210
209
  "status" => status,
211
- "value" => safe_value
210
+ "value" => serialized_value
212
211
  }
213
212
  end
214
213