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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aa7f2db70ebfda5de9128554d289d3506ad0c922dac4042e80e29ad691b04447
4
- data.tar.gz: b0eedc91174b14a594352d3033a27c13f4f29dbd038679345568e39bea486bc4
3
+ metadata.gz: 2e66e28078af9324bb7d5668ee5079535d829913a2dcb4bce65daa4d471d4524
4
+ data.tar.gz: a821d82c490030be200c0d4ab5dc4a56a1abf1aa862d69d1e4c6a4595e47952d
5
5
  SHA512:
6
- metadata.gz: 1d5620a57dbf4ba82778704296116f53b162a469557162f766c208a5ca5d01e52fbdb219c8e9e965823dca7224dfd1dec507cb432d0d64fa29604fc117a1930f
7
- data.tar.gz: 0fdf98e91e30c49960caf5b67bca32d6698c3df18ce1d75db98831cfc0694ac1ba444c08691e809ba3ecca76d702b16e46154ed5fc65a59db432f27355bf57a0
6
+ metadata.gz: 120dd18c9478c105387f2ad3634276cbaae05fe3638cc01d378aa9cbb1c423bf737991b7278ab4f96eb0a7cca39cd3d259cbba3d1734cea6ac071ba0695da901
7
+ data.tar.gz: 61171ff383d06883e6f6ffa0cafc423a80d41ea0f2d45c3c0e4b76c7037a4626ad7a4b8b625e3a013e077b9e96cdafaab06cad6d5a490dae392af3fbb410d0fe
data/Puppetfile CHANGED
@@ -5,14 +5,14 @@ forge "http://forge.puppetlabs.com"
5
5
  moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
- mod 'puppetlabs-service', '1.4.0'
9
- mod 'puppetlabs-puppet_agent', '4.4.0'
8
+ mod 'puppetlabs-service', '2.0.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
13
- mod 'puppetlabs-augeas_core', '1.1.1'
13
+ mod 'puppetlabs-augeas_core', '1.1.2'
14
14
  mod 'puppetlabs-host_core', '1.0.3'
15
- mod 'puppetlabs-scheduled_task', '2.3.1'
15
+ mod 'puppetlabs-scheduled_task', '3.0.0'
16
16
  mod 'puppetlabs-sshkeys_core', '2.2.0'
17
17
  mod 'puppetlabs-zfs_core', '1.2.0'
18
18
  mod 'puppetlabs-cron_core', '1.0.5'
@@ -22,19 +22,20 @@ mod 'puppetlabs-yumrepo_core', '1.0.7'
22
22
  mod 'puppetlabs-zone_core', '1.0.3'
23
23
 
24
24
  # Useful additional modules
25
- mod 'puppetlabs-package', '1.4.0'
26
- mod 'puppetlabs-puppet_conf', '0.8.0'
25
+ mod 'puppetlabs-package', '2.0.0'
26
+ mod 'puppetlabs-powershell_task_helper', '0.1.0'
27
+ mod 'puppetlabs-puppet_conf', '1.1.0'
27
28
  mod 'puppetlabs-python_task_helper', '0.5.0'
28
- mod 'puppetlabs-reboot', '3.2.0'
29
+ mod 'puppetlabs-reboot', '4.0.2'
29
30
  mod 'puppetlabs-ruby_task_helper', '0.6.0'
30
31
  mod 'puppetlabs-ruby_plugin_helper', '0.2.0'
31
- mod 'puppetlabs-stdlib', '6.5.0'
32
+ mod 'puppetlabs-stdlib', '7.0.0'
32
33
 
33
34
  # Plugin modules
34
35
  mod 'puppetlabs-aws_inventory', '0.6.0'
35
36
  mod 'puppetlabs-azure_inventory', '0.5.0'
36
37
  mod 'puppetlabs-gcloud_inventory', '0.2.0'
37
- mod 'puppetlabs-http_request', '0.2.1'
38
+ mod 'puppetlabs-http_request', '0.2.2'
38
39
  mod 'puppetlabs-pkcs7', '0.1.1'
39
40
  mod 'puppetlabs-secure_env_vars', '0.2.0'
40
41
  mod 'puppetlabs-terraform', '0.6.1'
@@ -45,3 +46,4 @@ mod 'puppetlabs-yaml', '0.2.0'
45
46
  mod 'canary', local: true
46
47
  mod 'aggregate', local: true
47
48
  mod 'puppetdb_fact', local: true
49
+ mod 'puppet_connect', local: true
@@ -7,7 +7,7 @@ require 'bolt/error'
7
7
  # > **Note:** Not available in apply block
8
8
  Puppet::Functions.create_function(:add_facts) do
9
9
  # @param target A target.
10
- # @param facts A hash of fact names to values that may include structured facts.
10
+ # @param facts A hash of fact names to values that can include structured facts.
11
11
  # @return A `Target` object.
12
12
  # @example Adding facts to a target
13
13
  # add_facts($target, { 'os' => { 'family' => 'windows', 'name' => 'windows' } })
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt/logger'
3
4
  require 'bolt/task'
4
5
 
5
6
  # Installs the `puppet-agent` package on targets if needed, then collects facts,
@@ -131,10 +132,20 @@ Puppet::Functions.create_function(:apply_prep) do
131
132
  task = applicator.custom_facts_task
132
133
  arguments = { 'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins) }
133
134
  results = executor.run_task(targets, task, arguments)
135
+
134
136
  # TODO: Standardize RunFailure type with error above
135
137
  raise Bolt::RunFailure.new(results, 'run_task', task.name) unless results.ok?
136
138
 
137
139
  results.each do |result|
140
+ # Log a warning if the client version is < 6
141
+ if unsupported_puppet?(result['clientversion'])
142
+ Bolt::Logger.deprecate(
143
+ "unsupported_puppet",
144
+ "Detected unsupported Puppet agent version #{result['clientversion']} on target "\
145
+ "#{result.target}. Bolt supports Puppet agent 6.0.0 and higher."
146
+ )
147
+ end
148
+
138
149
  inventory.add_facts(result.target, result.value)
139
150
  end
140
151
  end
@@ -143,4 +154,18 @@ Puppet::Functions.create_function(:apply_prep) do
143
154
  # Return nothing
144
155
  nil
145
156
  end
157
+
158
+ # Returns true if the client's major version is < 6.
159
+ #
160
+ private def unsupported_puppet?(client_version)
161
+ if client_version.nil?
162
+ false
163
+ else
164
+ begin
165
+ Integer(client_version.split('.').first) < 6
166
+ rescue StandardError
167
+ false
168
+ end
169
+ end
170
+ end
146
171
  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
 
@@ -256,7 +256,7 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
256
256
  if nodes_param
257
257
  if params['nodes']
258
258
  raise ArgumentError,
259
- "A plan's 'nodes' parameter may be specified as the second positional argument to " \
259
+ "A plan's 'nodes' parameter can be specified as the second positional argument to " \
260
260
  "run_plan(), but in that case 'nodes' must not be specified in the named arguments " \
261
261
  "hash."
262
262
  end
@@ -265,7 +265,7 @@ Puppet::Functions.create_function(:run_plan, Puppet::Functions::InternalFunction
265
265
  elsif targets_param
266
266
  if params['targets']
267
267
  raise ArgumentError,
268
- "A plan's 'targets' parameter may be specified as the second positional argument to " \
268
+ "A plan's 'targets' parameter can be specified as the second positional argument to " \
269
269
  "run_plan(), but in that case 'targets' must not be specified in the named arguments " \
270
270
  "hash."
271
271
  end
@@ -6,19 +6,24 @@
6
6
  # > **Note:** Not available in apply block
7
7
  Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFunction) do
8
8
  # Run a script.
9
- # @param script Path to a script to run on target. May be an absolute path or a modulename/filename selector for a
9
+ # @param script Path to a script to run on target. Can be an absolute path or a modulename/filename selector for a
10
10
  # file in $MODULEROOT/files.
11
11
  # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
12
12
  # @param options A hash of additional options.
13
13
  # @option options [Array[String]] arguments An array of arguments to be passed to the script.
14
+ # Cannot be used with `pwsh_params`.
15
+ # @option options [Hash] pwsh_params Map of named parameters to pass to a PowerShell script.
16
+ # Cannot be used with `arguments`.
14
17
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
15
18
  # @option options [String] _run_as User to run as using privilege escalation.
16
- # @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.
17
20
  # @return A list of results, one entry per target.
18
21
  # @example Run a local script on Linux targets as 'root'
19
22
  # run_script('/var/tmp/myscript', $targets, '_run_as' => 'root')
20
23
  # @example Run a module-provided script with arguments
21
24
  # run_script('iis/setup.ps1', $target, 'arguments' => ['/u', 'Administrator'])
25
+ # @example Pass named parameters to a PowerShell script
26
+ # run_script('iis/setup.ps1', $target, 'pwsh_params' => { 'User' => 'Administrator' })
22
27
  dispatch :run_script do
23
28
  scope_param
24
29
  param 'String[1]', :script
@@ -28,15 +33,18 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
28
33
  end
29
34
 
30
35
  # Run a script, logging the provided description.
31
- # @param script Path to a script to run on target. May be an absolute path or a modulename/filename selector for a
36
+ # @param script Path to a script to run on target. Can be an absolute path or a modulename/filename selector for a
32
37
  # file in $MODULEROOT/files.
33
38
  # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
34
39
  # @param description A description to be output when calling this function.
35
40
  # @param options A hash of additional options.
36
41
  # @option options [Array[String]] arguments An array of arguments to be passed to the script.
42
+ # Cannot be used with `pwsh_params`.
43
+ # @option options [Hash] pwsh_params Map of named parameters to pass to a PowerShell script.
44
+ # Cannot be used with `arguments`.
37
45
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
38
46
  # @option options [String] _run_as User to run as using privilege escalation.
39
- # @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.
40
48
  # @return A list of results, one entry per target.
41
49
  # @example Run a script
42
50
  # run_script('/var/tmp/myscript', $targets, 'Downloading my application')
@@ -59,9 +67,40 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
59
67
  .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'run_script')
60
68
  end
61
69
 
70
+ if options.key?('arguments') && options.key?('pwsh_params')
71
+ raise Bolt::ValidationError, "Cannot specify both 'arguments' and 'pwsh_params'"
72
+ end
73
+
74
+ if options.key?('pwsh_params') && !options['pwsh_params'].is_a?(Hash)
75
+ raise Bolt::ValidationError, "Option 'pwsh_params' must be a hash"
76
+ end
77
+
78
+ if options.key?('arguments') && !options['arguments'].is_a?(Array)
79
+ raise Bolt::ValidationError, "Option 'arguments' must be an array"
80
+ end
81
+
62
82
  arguments = options['arguments'] || []
83
+ pwsh_params = options['pwsh_params']
63
84
  options = options.select { |opt| opt.start_with?('_') }.transform_keys { |k| k.sub(/^_/, '').to_sym }
64
85
  options[:description] = description if description
86
+ options[:pwsh_params] = pwsh_params if pwsh_params
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
65
104
 
66
105
  executor = Puppet.lookup(:bolt_executor)
67
106
  inventory = Puppet.lookup(:bolt_inventory)
@@ -80,7 +119,7 @@ Puppet::Functions.create_function(:run_script, Puppet::Functions::InternalFuncti
80
119
  Puppet::Pops::Issues::NOT_A_FILE, file: script
81
120
  )
82
121
  end
83
-
122
+ executor.report_file_source(self.class.name, script)
84
123
  # Ensure that given targets are all Target instances)
85
124
  targets = inventory.get_targets(targets)
86
125
 
@@ -76,7 +76,7 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
76
76
  Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: source
77
77
  )
78
78
  end
79
-
79
+ executor.report_file_source(self.class.name, source)
80
80
  # Ensure that that given targets are all Target instances
81
81
  targets = inventory.get_targets(targets)
82
82
  if targets.empty?
@@ -2,7 +2,11 @@
2
2
 
3
3
  require 'bolt/util'
4
4
 
5
- # Wait until all targets accept connections.
5
+ # Wait until all targets accept connections. This function allows a plan execution to wait for a customizable
6
+ # amount of time via the `wait_time` option until a target connection can be reestablished. The plan proceeds
7
+ # to the next step if the connection fails to reconnect in the time specified (default: 120 seconds). A typical
8
+ # use case for this function is if your plan reboots a remote host and the plan needs to wait for the host to reconnect
9
+ # before it continues to the next step.
6
10
  #
7
11
  # > **Note:** Not available in apply block
8
12
  Puppet::Functions.create_function(:wait_until_available) do
@@ -10,8 +14,8 @@ Puppet::Functions.create_function(:wait_until_available) do
10
14
  # @param targets A pattern identifying zero or more targets. See {get_targets} for accepted patterns.
11
15
  # @param options A hash of additional options.
12
16
  # @option options [String] description A description for logging. (default: 'wait until available')
13
- # @option options [Numeric] wait_time The time to wait. (default: 120)
14
- # @option options [Numeric] retry_interval The interval to wait before retrying. (default: 1)
17
+ # @option options [Numeric] wait_time The time to wait, in seconds. (default: 120)
18
+ # @option options [Numeric] retry_interval The interval to wait before retrying, in seconds. (default: 1)
15
19
  # @option options [Boolean] _catch_errors Whether to catch raised errors.
16
20
  # @return A list of results, one entry per target. Successful results have no value.
17
21
  # @example Wait for targets
@@ -17,7 +17,8 @@ Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunc
17
17
 
18
18
  def read(scope, filename)
19
19
  # Send Analytics Report
20
- Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
20
+ executor = Puppet.lookup(:bolt_executor) {}
21
+ executor&.report_function_call(self.class.name)
21
22
 
22
23
  found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
23
24
  unless found && Puppet::FileSystem.exist?(found)
@@ -25,7 +26,7 @@ Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunc
25
26
  Puppet::Pops::Issues::NO_SUCH_FILE_OR_DIRECTORY, file: filename
26
27
  )
27
28
  end
28
-
29
+ executor&.report_file_source(self.class.name, filename)
29
30
  File.read(found)
30
31
  end
31
32
  end
@@ -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
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/error'
4
+
5
+ # Display a menu prompt and wait for a response. Continues to prompt
6
+ # until an option from the menu is selected.
7
+ #
8
+ # > **Note:** Not available in apply block
9
+ Puppet::Functions.create_function(:'prompt::menu') do
10
+ # Select from a list of options.
11
+ # @param prompt The prompt to display.
12
+ # @param menu A list of options to choose from.
13
+ # @param options A hash of additional options.
14
+ # @option options [String] default The default option to return if the user does not provide
15
+ # input or if standard in (stdin) is not a tty. Must be an option present in the menu.
16
+ # @return The selected option.
17
+ # @example Prompt the user to select from a list of options
18
+ # $selection = prompt::menu('Select a fruit', ['apple', 'banana', 'carrot'])
19
+ # @example Prompt the user to select from a list of options with a default value
20
+ # $selection = prompt::menu('Select environment', ['development', 'production'], 'default' => 'development')
21
+ dispatch :prompt_menu_array do
22
+ param 'String', :prompt
23
+ param 'Array[Variant[Target, Data]]', :menu
24
+ optional_param 'Hash[String[1], Variant[Target, Data]]', :options
25
+ return_type 'Variant[Target, Data]'
26
+ end
27
+
28
+ # Select from a list of options with custom inputs.
29
+ # @param prompt The prompt to display.
30
+ # @param menu A hash of options to choose from, where keys are the input used to select a value.
31
+ # @param options A hash of additional options.
32
+ # @option options [String] default The default option to return if the user does not provide
33
+ # input or if standard in (stdin) is not a tty. Must be an option present in the menu.
34
+ # @return The selected option.
35
+ # @example Prompt the user to select from a list of options with custom inputs
36
+ # $menu = { 'y' => 'yes', 'n' => 'no' }
37
+ # $selection = prompt::menu('Install Puppet?', $menu)
38
+ dispatch :prompt_menu do
39
+ param 'String', :prompt
40
+ param 'Hash[String[1], Variant[Target, Data]]', :menu
41
+ optional_param 'Hash[String[1], Variant[Target, Data]]', :options
42
+ return_type 'Variant[TargetSpec, Data]'
43
+ end
44
+
45
+ def prompt_menu_array(prompt, menu, options = {})
46
+ menu_hash = menu.map.with_index { |value, index| [(index + 1).to_s, value] }.to_h
47
+ prompt_menu(prompt, menu_hash, options)
48
+ end
49
+
50
+ def prompt_menu(prompt, menu, options = {})
51
+ unless Puppet[:tasks]
52
+ raise Puppet::ParseErrorWithIssue
53
+ .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING,
54
+ action: 'prompt::menu')
55
+ end
56
+
57
+ options = options.transform_keys(&:to_sym)
58
+ executor = Puppet.lookup(:bolt_executor)
59
+
60
+ # Send analytics report
61
+ executor.report_function_call(self.class.name)
62
+
63
+ # Error if there are no options
64
+ if menu.empty?
65
+ raise Bolt::ValidationError, "Menu cannot be empty"
66
+ end
67
+
68
+ # Error if the default value is not on the menu
69
+ if options.key?(:default) && !menu.value?(options[:default])
70
+ raise Bolt::ValidationError, "Default value '#{options[:default]}' is not one of the provided menu options"
71
+ end
72
+
73
+ # The first prompt should include the menu
74
+ to_prompt = format_menu(menu) + prompt
75
+
76
+ # Request input from the user until they provide a valid option
77
+ loop do
78
+ selection = executor.prompt(to_prompt, options)
79
+
80
+ return menu[selection] if menu.key?(selection)
81
+ return selection if options.key?(:default) && options[:default] == selection
82
+
83
+ # Only reprint the prompt, not the menu
84
+ to_prompt = "Invalid option, try again. #{prompt}"
85
+ end
86
+ end
87
+
88
+ # Builds the menu string. Aligns all the values by padding input keys.
89
+ #
90
+ private def format_menu(menu)
91
+ # Find the longest input and add 2 for wrapping parenthesis
92
+ key_length = menu.keys.max_by(&:length).length + 2
93
+
94
+ menu_string = +''
95
+
96
+ menu.each do |key, value|
97
+ key = "(#{key})".ljust(key_length)
98
+ menu_string << "#{key} #{value}\n"
99
+ end
100
+
101
+ menu_string
102
+ end
103
+ end