bolt 3.3.0 → 3.7.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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +5 -5
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/containerresult.rb +24 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_command.rb +66 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +20 -2
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_container.rb +162 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +19 -2
  8. data/bolt-modules/boltlib/types/planresult.pp +1 -0
  9. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +20 -2
  10. data/bolt-modules/prompt/lib/puppet/functions/prompt/menu.rb +103 -0
  11. data/guides/targets.txt +31 -0
  12. data/lib/bolt/analytics.rb +4 -8
  13. data/lib/bolt/bolt_option_parser.rb +35 -17
  14. data/lib/bolt/cli.rb +109 -28
  15. data/lib/bolt/config.rb +11 -7
  16. data/lib/bolt/config/options.rb +41 -9
  17. data/lib/bolt/config/transport/lxd.rb +3 -1
  18. data/lib/bolt/config/transport/options.rb +7 -0
  19. data/lib/bolt/config/transport/podman.rb +33 -0
  20. data/lib/bolt/container_result.rb +105 -0
  21. data/lib/bolt/error.rb +15 -0
  22. data/lib/bolt/executor.rb +27 -15
  23. data/lib/bolt/inventory.rb +5 -4
  24. data/lib/bolt/inventory/inventory.rb +3 -2
  25. data/lib/bolt/inventory/options.rb +9 -0
  26. data/lib/bolt/inventory/target.rb +16 -0
  27. data/lib/bolt/node/output.rb +14 -4
  28. data/lib/bolt/outputter/human.rb +243 -84
  29. data/lib/bolt/outputter/json.rb +6 -4
  30. data/lib/bolt/outputter/logger.rb +17 -0
  31. data/lib/bolt/pal.rb +22 -2
  32. data/lib/bolt/pal/yaml_plan/step.rb +4 -2
  33. data/lib/bolt/pal/yaml_plan/step/command.rb +8 -0
  34. data/lib/bolt/pal/yaml_plan/step/script.rb +4 -0
  35. data/lib/bolt/pal/yaml_plan/transpiler.rb +2 -2
  36. data/lib/bolt/plan_creator.rb +2 -2
  37. data/lib/bolt/plugin.rb +13 -11
  38. data/lib/bolt/puppetdb/client.rb +54 -0
  39. data/lib/bolt/result.rb +5 -14
  40. data/lib/bolt/shell/bash.rb +33 -22
  41. data/lib/bolt/shell/powershell.rb +6 -8
  42. data/lib/bolt/transport/docker.rb +1 -1
  43. data/lib/bolt/transport/docker/connection.rb +21 -32
  44. data/lib/bolt/transport/lxd/connection.rb +5 -5
  45. data/lib/bolt/transport/orch.rb +13 -5
  46. data/lib/bolt/transport/podman.rb +19 -0
  47. data/lib/bolt/transport/podman/connection.rb +98 -0
  48. data/lib/bolt/util.rb +42 -0
  49. data/lib/bolt/version.rb +1 -1
  50. data/lib/bolt_server/transport_app.rb +3 -0
  51. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +8 -1
  52. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +8 -1
  53. data/lib/bolt_spec/plans/mock_executor.rb +91 -11
  54. data/modules/puppet_connect/plans/test_input_data.pp +22 -0
  55. metadata +11 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e32573bc97fea7eca64b7c915cb4b462e1325fb5d4dcbe95c48c72aae8c0e14
4
- data.tar.gz: abb279c5a363092ca37ddf4eac9de665de9f79aa74c9e0af9697910b336b7b93
3
+ metadata.gz: 575cccb1487a82a6537a83d5d23a89d9e3ed44898af25e2fe34acda2ecd765ae
4
+ data.tar.gz: a9de53717fdcb5380f95ef5ee1fb26b3d28eaa59f92355d9558954d14192f307
5
5
  SHA512:
6
- metadata.gz: 23bf7042750c180eb618eaafd8af34e014deb5c7ab0dadcce316e92c3aa66e62ae85e89e96f471cc54f17c81eba463f73b776e9aa4581219567ed15876722967
7
- data.tar.gz: bcd1eb6192f5ac92959088edfbe7b333dcb9ed63f6da7a36a6274d72cbce32e77f4f01a8427edec78be3dd9caeab2472ec3691d846108367df407187606ba2ae
6
+ metadata.gz: 60378d6872763f5fb557deae88207c29db0626d90562e37d80df1f604368629049da79ff0a0216602057566c7eb10892855f33a99b03bfc348fce8b443e7682d
7
+ data.tar.gz: e2f8dd460e346648e53921e299db5b7beaf4ba1a0cd7efe95728e2e2d42c3e4b3624c40ef8a94a21b7e4be5003d67a1a64c48d1a36a9e314c4d7bb296ce85eab
data/Puppetfile CHANGED
@@ -6,7 +6,7 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '2.0.0'
9
- mod 'puppetlabs-puppet_agent', '4.4.0'
9
+ mod 'puppetlabs-puppet_agent', '4.5.0'
10
10
  mod 'puppetlabs-facts', '1.4.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
@@ -24,17 +24,17 @@ mod 'puppetlabs-zone_core', '1.0.3'
24
24
  # Useful additional modules
25
25
  mod 'puppetlabs-package', '2.0.0'
26
26
  mod 'puppetlabs-powershell_task_helper', '0.1.0'
27
- mod 'puppetlabs-puppet_conf', '1.0.0'
27
+ mod 'puppetlabs-puppet_conf', '1.1.0'
28
28
  mod 'puppetlabs-python_task_helper', '0.5.0'
29
- mod 'puppetlabs-reboot', '4.0.0'
29
+ mod 'puppetlabs-reboot', '4.0.2'
30
30
  mod 'puppetlabs-ruby_task_helper', '0.6.0'
31
31
  mod 'puppetlabs-ruby_plugin_helper', '0.2.0'
32
32
  mod 'puppetlabs-stdlib', '7.0.0'
33
33
 
34
34
  # Plugin modules
35
- mod 'puppetlabs-aws_inventory', '0.6.0'
35
+ mod 'puppetlabs-aws_inventory', '0.7.0'
36
36
  mod 'puppetlabs-azure_inventory', '0.5.0'
37
- mod 'puppetlabs-gcloud_inventory', '0.2.0'
37
+ mod 'puppetlabs-gcloud_inventory', '0.3.0'
38
38
  mod 'puppetlabs-http_request', '0.2.2'
39
39
  mod 'puppetlabs-pkcs7', '0.1.1'
40
40
  mod 'puppetlabs-secure_env_vars', '0.2.0'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ Puppet::DataTypes.create_type('ContainerResult') do
4
+ interface <<-PUPPET
5
+ attributes => {
6
+ 'value' => Hash[String[1], Data],
7
+ },
8
+ functions => {
9
+ '[]' => Callable[[String[1]], Data],
10
+ error => Callable[[], Optional[Error]],
11
+ ok => Callable[[], Boolean],
12
+ status => Callable[[], String],
13
+ stdout => Callable[[], String],
14
+ stderr => Callable[[], String],
15
+ to_data => Callable[[], Hash]
16
+ }
17
+ PUPPET
18
+
19
+ load_file('bolt/container_result')
20
+
21
+ # Needed for Puppet to recognize Bolt::ContainerResult as a Puppet object when deserializing
22
+ Bolt::ContainerResult.include(Puppet::Pops::Types::PuppetObject)
23
+ implementation_class Bolt::ContainerResult
24
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Send a command with a payload to PuppetDB.
6
+ #
7
+ # The `pdb_command` function only supports version 5 of the `replace_facts`
8
+ # command. Other commands might also work, but are not tested or supported
9
+ # by Bolt.
10
+ #
11
+ # See the [commands endpoint](https://puppet.com/docs/puppetdb/latest/api/command/v1/commands.html)
12
+ # documentation for more information about available commands and payload
13
+ # format.
14
+ #
15
+ # _This function is experimental and subject to change._
16
+ #
17
+ # > **Note:** Not available in apply block
18
+ #
19
+ Puppet::Functions.create_function(:puppetdb_command) do
20
+ # @param command The command to invoke.
21
+ # @param version The version of the command to invoke.
22
+ # @param payload The payload to the command.
23
+ # @return The UUID identifying the response sent by PuppetDB.
24
+ # @example Replace facts for a target
25
+ # $payload = {
26
+ # 'certname' => 'localhost',
27
+ # 'environment' => 'dev',
28
+ # 'producer' => 'bolt',
29
+ # 'producer_timestamp' => '1970-01-01',
30
+ # 'values' => { 'orchestrator' => 'bolt' }
31
+ # }
32
+ #
33
+ # puppetdb_command('replace_facts', 5, $payload)
34
+ dispatch :puppetdb_command do
35
+ param 'String[1]', :command
36
+ param 'Integer', :version
37
+ param 'Hash[Data, Data]', :payload
38
+ return_type 'String'
39
+ end
40
+
41
+ def puppetdb_command(command, version, payload)
42
+ # Disallow in apply blocks.
43
+ unless Puppet[:tasks]
44
+ raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
45
+ Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
46
+ action: 'puppetdb_command'
47
+ )
48
+ end
49
+
50
+ # Send analytics report.
51
+ Puppet.lookup(:bolt_executor).report_function_call(self.class.name)
52
+
53
+ puppetdb_client = Puppet.lookup(:bolt_pdb_client)
54
+
55
+ # Error if the PDB client does not implement :send_command
56
+ unless puppetdb_client.respond_to?(:send_command)
57
+ raise Bolt::Error.new(
58
+ "PuppetDB client #{puppetdb_client.class} does not implement :send_command, "\
59
+ "unable to invoke command.",
60
+ 'bolt/pdb-command'
61
+ )
62
+ end
63
+
64
+ puppetdb_client.send_command(command, version, payload)
65
+ end
66
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bolt/error'
4
+ require 'json'
4
5
 
5
6
  # Runs a command on the given set of targets and returns the result from each command execution.
6
7
  # This function does nothing if the list of targets is empty.
@@ -13,7 +14,7 @@ Puppet::Functions.create_function(:run_command) do
13
14
  # @param options A hash of additional options.
14
15
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
15
16
  # @option options [String] _run_as User to run as using privilege escalation.
16
- # @option options [Hash] _env_vars Map of environment variables to set
17
+ # @option options [Hash[String, Any]] _env_vars Map of environment variables to set
17
18
  # @return A list of results, one entry per target.
18
19
  # @example Run a command on targets
19
20
  # run_command('hostname', $targets, '_catch_errors' => true)
@@ -31,7 +32,7 @@ Puppet::Functions.create_function(:run_command) do
31
32
  # @param options A hash of additional options.
32
33
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
33
34
  # @option options [String] _run_as User to run as using privilege escalation.
34
- # @option options [Hash] _env_vars Map of environment variables to set
35
+ # @option options [Hash[String, Any]] _env_vars Map of environment variables to set
35
36
  # @return A list of results, one entry per target.
36
37
  # @example Run a command on targets
37
38
  # run_command('hostname', $targets, 'Get hostname')
@@ -56,6 +57,23 @@ Puppet::Functions.create_function(:run_command) do
56
57
  options = options.transform_keys { |k| k.sub(/^_/, '').to_sym }
57
58
  options[:description] = description if description
58
59
 
60
+ # Ensure env_vars is a hash and that each hash value is transformed to JSON
61
+ # so we don't accidentally pass Ruby-style data to the target.
62
+ if options[:env_vars]
63
+ unless options[:env_vars].is_a?(Hash)
64
+ raise Bolt::ValidationError, "Option 'env_vars' must be a hash"
65
+ end
66
+
67
+ if (bad_keys = options[:env_vars].keys.reject { |k| k.is_a?(String) }).any?
68
+ raise Bolt::ValidationError,
69
+ "Keys for option 'env_vars' must be strings: #{bad_keys.map(&:inspect).join(', ')}"
70
+ end
71
+
72
+ options[:env_vars] = options[:env_vars].transform_values do |val|
73
+ [Array, Hash].include?(val.class) ? val.to_json : val
74
+ end
75
+ end
76
+
59
77
  executor = Puppet.lookup(:bolt_executor)
60
78
  inventory = Puppet.lookup(:bolt_inventory)
61
79
 
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/container_result'
4
+ require 'bolt/error'
5
+ require 'bolt/util'
6
+
7
+ # Run a container and return its output to stdout and stderr.
8
+ #
9
+ # > **Note:** Not available in apply block
10
+ Puppet::Functions.create_function(:run_container) do
11
+ # Run a container.
12
+ # @param image The name of the image to run.
13
+ # @param options A hash of additional options.
14
+ # @option options [Boolean] _catch_errors Whether to catch raised errors.
15
+ # @option options [String] cmd A command to run in the container.
16
+ # @option options [Hash[String, Data]] env_vars Map of environment variables to set.
17
+ # @option options [Hash[Integer, Integer]] ports A map of container ports to
18
+ # publish. Keys are the host port, values are the corresponding container
19
+ # port.
20
+ # @option options [Boolean] rm Whether to remove the container once it exits.
21
+ # @option options [Hash[String, String]] volumes A map of absolute paths on
22
+ # the host to absolute paths on the remote to mount.
23
+ # @option options [String] workdir The working directory within the container.
24
+ # @return Output from the container.
25
+ # @example Run Nginx proxy manager
26
+ # run_container('jc21/nginx-proxy-manager', 'ports' => { 80 => 80, 81 => 81, 443 => 443 })
27
+ dispatch :run_container do
28
+ param 'String[1]', :image
29
+ optional_param 'Hash[String[1], Any]', :options
30
+ return_type 'ContainerResult'
31
+ end
32
+
33
+ def run_container(image, options = {})
34
+ unless Puppet[:tasks]
35
+ raise Puppet::ParseErrorWithIssue
36
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'run_container')
37
+ end
38
+
39
+ # Send Analytics Report
40
+ executor = Puppet.lookup(:bolt_executor)
41
+ executor.report_function_call(self.class.name)
42
+
43
+ options = options.transform_keys { |k| k.sub(/^_/, '').to_sym }
44
+ validate_options(options)
45
+
46
+ if options.key?(:env_vars)
47
+ options[:env_vars] = options[:env_vars].transform_values do |val|
48
+ [Array, Hash].include?(val.class) ? val.to_json : val
49
+ end
50
+ end
51
+
52
+ if options[:ports]
53
+ ports = options[:ports].each_with_object([]) do |(host_port, container_port), acc|
54
+ acc << "-p"
55
+ acc << "#{host_port}:#{container_port}"
56
+ end
57
+ end
58
+
59
+ if options[:volumes]
60
+ volumes = options[:volumes].each_with_object([]) do |(host_path, remote_path), acc|
61
+ begin
62
+ FileUtils.mkdir_p(host_path)
63
+ rescue StandardError => e
64
+ message = "Unable to create host volume directory #{host_path}: #{e.message}"
65
+ raise Bolt::Error.new(message, 'bolt/file-error')
66
+ end
67
+ acc << "-v"
68
+ acc << "#{host_path}:#{remote_path}"
69
+ end
70
+ end
71
+
72
+ # Run the container
73
+ # `docker run` will automatically pull the image if it isn't already downloaded
74
+ cmd = %w[run]
75
+ cmd += Bolt::Util.format_env_vars_for_cli(options[:env_vars]) if options[:env_vars]
76
+ cmd += volumes if volumes
77
+ cmd += ports if ports
78
+ cmd << "--rm" if options[:rm]
79
+ cmd += %W[-w #{options[:workdir]}] if options[:workdir]
80
+ cmd << image
81
+ cmd += Shellwords.shellsplit(options[:cmd]) if options[:cmd]
82
+
83
+ executor.publish_event(type: :container_start, image: image)
84
+ out, err, status = Bolt::Util.exec_docker(cmd)
85
+
86
+ o = out.is_a?(String) ? out.dup.force_encoding('utf-8') : out
87
+ e = err.is_a?(String) ? err.dup.force_encoding('utf-8') : err
88
+
89
+ unless status.exitstatus.zero?
90
+ result = Bolt::ContainerResult.from_exception(e,
91
+ status.exitstatus,
92
+ image,
93
+ position: Puppet::Pops::PuppetStack.top_of_stack)
94
+ executor.publish_event(type: :container_finish, result: result)
95
+ if options[:catch_errors]
96
+ return result
97
+ else
98
+ raise Bolt::ContainerFailure, result
99
+ end
100
+ end
101
+
102
+ value = { 'stdout' => o, 'stderr' => e, 'exit_code' => status.exitstatus }
103
+ result = Bolt::ContainerResult.new(value, object: image)
104
+ executor.publish_event(type: :container_finish, result: result)
105
+ result
106
+ end
107
+
108
+ def validate_options(options)
109
+ if options.key?(:env_vars)
110
+ ev = options[:env_vars]
111
+ unless ev.is_a?(Hash)
112
+ msg = "Option 'env_vars' must be a hash. Received #{ev} which is a #{ev.class}"
113
+ raise Bolt::ValidationError, msg
114
+ end
115
+
116
+ if (bad_keys = ev.keys.reject { |k| k.is_a?(String) }).any?
117
+ msg = "Keys for option 'env_vars' must be strings: #{bad_keys.map(&:inspect).join(', ')}"
118
+ raise Bolt::ValidationError, msg
119
+ end
120
+ end
121
+
122
+ if options.key?(:volumes)
123
+ volumes = options[:volumes]
124
+ unless volumes.is_a?(Hash)
125
+ msg = "Option 'volumes' must be a hash. Received #{volumes} which is a #{volumes.class}"
126
+ raise Bolt::ValidationError, msg
127
+ end
128
+
129
+ if (bad_vs = volumes.reject { |k, v| k.is_a?(String) && v.is_a?(String) }).any?
130
+ msg = "Option 'volumes' only accepts strings for keys and values. "\
131
+ "Received: #{bad_vs.map(&:inspect).join(', ')}"
132
+ raise Bolt::ValidationError, msg
133
+ end
134
+ end
135
+
136
+ if options.key?(:cmd) && !options[:cmd].is_a?(String)
137
+ cmd = options[:cmd]
138
+ msg = "Option 'cmd' must be a string. Received #{cmd} which is a #{cmd.class}"
139
+ raise Bolt::ValidationError, msg
140
+ end
141
+
142
+ if options.key?(:workdir) && !options[:workdir].is_a?(String)
143
+ wd = options[:workdir]
144
+ msg = "Option 'workdir' must be a string. Received #{wd} which is a #{wd.class}"
145
+ raise Bolt::ValidationError, msg
146
+ end
147
+
148
+ if options.key?(:ports)
149
+ ports = options[:ports]
150
+ unless ports.is_a?(Hash)
151
+ msg = "Option 'ports' must be a hash. Received #{ports} which is a #{ports.class}"
152
+ raise Bolt::ValidationError, msg
153
+ end
154
+
155
+ if (bad_ps = ports.reject { |k, v| k.is_a?(Integer) && v.is_a?(Integer) }).any?
156
+ msg = "Option 'ports' only accepts integers for keys and values. "\
157
+ "Received: #{bad_ps.map(&:inspect).join(', ')}"
158
+ raise Bolt::ValidationError, msg
159
+ end
160
+ end
161
+ end
162
+ end
@@ -16,7 +16,7 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
16
16
  # Cannot be used with `arguments`.
17
17
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
18
18
  # @option options [String] _run_as User to run as using privilege escalation.
19
- # @option options [Hash] _env_vars Map of environment variables to set.
19
+ # @option options [Hash[String, Any]] _env_vars Map of environment variables to set.
20
20
  # @return A list of results, one entry per target.
21
21
  # @example Run a local script on Linux targets as 'root'
22
22
  # run_script('/var/tmp/myscript', $targets, '_run_as' => 'root')
@@ -44,7 +44,7 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
44
44
  # Cannot be used with `arguments`.
45
45
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
46
46
  # @option options [String] _run_as User to run as using privilege escalation.
47
- # @option options [Hash] _env_vars Map of environment variables to set.
47
+ # @option options [Hash[String, Any]] _env_vars Map of environment variables to set.
48
48
  # @return A list of results, one entry per target.
49
49
  # @example Run a script
50
50
  # run_script('/var/tmp/myscript', $targets, 'Downloading my application')
@@ -85,6 +85,23 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
85
85
  options[:description] = description if description
86
86
  options[:pwsh_params] = pwsh_params if pwsh_params
87
87
 
88
+ # Ensure env_vars is a hash and that each hash value is transformed to JSON
89
+ # so we don't accidentally pass Ruby-style data to the target.
90
+ if options[:env_vars]
91
+ unless options[:env_vars].is_a?(Hash)
92
+ raise Bolt::ValidationError, "Option 'env_vars' must be a hash"
93
+ end
94
+
95
+ if (bad_keys = options[:env_vars].keys.reject { |k| k.is_a?(String) }).any?
96
+ raise Bolt::ValidationError,
97
+ "Keys for option 'env_vars' must be strings: #{bad_keys.map(&:inspect).join(', ')}"
98
+ end
99
+
100
+ options[:env_vars] = options[:env_vars].transform_values do |val|
101
+ [Array, Hash].include?(val.class) ? val.to_json : val
102
+ end
103
+ end
104
+
88
105
  executor = Puppet.lookup(:bolt_executor)
89
106
  inventory = Puppet.lookup(:bolt_inventory)
90
107
 
@@ -12,5 +12,6 @@ type Boltlib::PlanResult = Variant[Boolean,
12
12
  ResultSet,
13
13
  Target,
14
14
  ResourceInstance,
15
+ ContainerResult,
15
16
  Array[Boltlib::PlanResult],
16
17
  Hash[String, Boltlib::PlanResult]]
@@ -11,12 +11,24 @@ Puppet::Functions.create_function(:prompt) do
11
11
  # @option options [Boolean] sensitive Disable echo back and mark the response as sensitive.
12
12
  # The returned value will be wrapped by the `Sensitive` data type. To access the raw
13
13
  # value, use the `unwrap` function (i.e. `$sensitive_value.unwrap`).
14
+ # @option options [String] default The default value to return if the user does not provide
15
+ # input or if stdin is not a tty.
14
16
  # @return The response to the prompt.
15
17
  # @example Prompt the user if plan execution should continue
16
18
  # $response = prompt('Continue executing plan? [Y\N]')
17
19
  # @example Prompt the user for sensitive information
18
20
  # $password = prompt('Enter your password', 'sensitive' => true)
19
21
  # out::message("Password is: ${password.unwrap}")
22
+ # @example Prompt the user and provide a default value
23
+ # $user = prompt('Enter your login username', 'default' => 'root')
24
+ # @example Prompt the user for sensitive information, returning a sensitive default if one is not provided
25
+ # $token = prompt('Enter token', 'default' => lookup('default_token'), 'sensitive' => true)
26
+ # out::message("Token is: ${token.unwrap}")
27
+ # @example Prompt the user and fail with a custom message if no input was provided
28
+ # $response = prompt('Enter your name', 'default' => '')
29
+ # if $response.empty {
30
+ # fail_plan('Must provide your name')
31
+ # }
20
32
  dispatch :prompt do
21
33
  param 'String', :prompt
22
34
  optional_param 'Hash[String[1], Any]', :options
@@ -30,14 +42,20 @@ Puppet::Functions.create_function(:prompt) do
30
42
  action: 'prompt')
31
43
  end
32
44
 
33
- options = options.transform_keys(&:to_sym)
34
-
45
+ options = options.transform_keys(&:to_sym)
35
46
  executor = Puppet.lookup(:bolt_executor)
47
+
36
48
  # Send analytics report
37
49
  executor.report_function_call(self.class.name)
38
50
 
51
+ # Require default to be a string value
52
+ if options.key?(:default) && !options[:default].is_a?(String)
53
+ raise Bolt::ValidationError, "Option 'default' must be a string"
54
+ end
55
+
39
56
  response = executor.prompt(prompt, options)
40
57
 
58
+ # If sensitive, wrap it
41
59
  if options[:sensitive]
42
60
  Puppet::Pops::Types::PSensitiveType::Sensitive.new(response)
43
61
  else