bolt 1.47.0 → 1.48.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d8c51d1ab9354303ccda2f36ebd31553fa0d8bf91adfe2e505e701cb8fc32eb
4
- data.tar.gz: f092a00fc4ea73fea86b54e6d36f535e873a15d79d00bc85bae433b5fa6eace5
3
+ metadata.gz: 006dcb3a880fefec3005eebb9f31126c3dc164c45d90010f5e7d5b6d984556e2
4
+ data.tar.gz: eaf4f944a88fba6aa4f1a0bb6fabe227246fa1ca90a63516a60d8db656f05176
5
5
  SHA512:
6
- metadata.gz: 741f8a885261e3b50413201fcfb0688d5a0c39a593a6225e9472c2822a114ae135be3b3fb30ee0433d0572ec4440f6cc0f505e10727098d55613b722fd9d86aa
7
- data.tar.gz: 3bce21f684265bd81f23b2a182c6d3bdc858a742faa28591a2483836dea04c736282d0e3551cd54843c7276d85d2e27e1f0794e956a7d973f57493e18973311a
6
+ metadata.gz: 3fc3a8337bed7f52c5b091a94b388de07514d3dcc0840d1523b1417769e244dfa1840b8a77616170589343ceb451e726121752eb4927db271cda526ad7eb0b0e
7
+ data.tar.gz: 617985134dd23308316303222af69da7ca08bf1840b5fb7301a0dc9b609ba0cc1430877b3bf02c7aaa1f2173c1ca20a76dc36594332f0b8f979aa65b5620f449
data/Puppetfile CHANGED
@@ -6,8 +6,8 @@ moduledir File.join(File.dirname(__FILE__), 'modules')
6
6
 
7
7
  # Core modules used by 'apply'
8
8
  mod 'puppetlabs-service', '1.1.0'
9
- mod 'puppetlabs-facts', '0.6.0'
10
- mod 'puppetlabs-puppet_agent', '2.2.2'
9
+ mod 'puppetlabs-puppet_agent', '3.0.1'
10
+ mod 'puppetlabs-facts', '1.0.0'
11
11
 
12
12
  # Core types and providers for Puppet 6
13
13
  mod 'puppetlabs-augeas_core', '1.0.5'
@@ -7,13 +7,9 @@ require 'bolt/task'
7
7
  # installed using either the configured plugin or the `task` plugin with the
8
8
  # `puppet_agent::install` task.
9
9
  #
10
- # Agent detection will be skipped if the target includes the 'puppet-agent' feature, either as a
10
+ # Agent installation will be skipped if the target includes the 'puppet-agent' feature, either as a
11
11
  # property of its transport (PCP) or by explicitly setting it as a feature in Bolt's inventory.
12
12
  #
13
- # If Bolt does not detect an agent on the target using the 'puppet_agent::version' task,
14
- # it will install the agent using either the configured plugin or the
15
- # task plugin.
16
- #
17
13
  # **NOTE:** Not available in apply block
18
14
  Puppet::Functions.create_function(:apply_prep) do
19
15
  # @param targets A pattern or array of patterns identifying a set of targets.
@@ -41,7 +37,7 @@ Puppet::Functions.create_function(:apply_prep) do
41
37
  raise Bolt::ValidationError, "Invalid parameters for #{errors.join("\n")}"
42
38
  end
43
39
 
44
- Bolt::Task.new(tasksig.task_hash)
40
+ Bolt::Task.from_task_signature(tasksig)
45
41
  end
46
42
 
47
43
  # rubocop:disable Naming/AccessorMethodName
@@ -79,53 +75,40 @@ Puppet::Functions.create_function(:apply_prep) do
79
75
  executor.log_action('install puppet and gather facts', targets) do
80
76
  executor.without_default_logging do
81
77
  # Skip targets that include the puppet-agent feature, as we know an agent will be available.
82
- agent_targets, unknown_targets = targets.partition { |target| agent?(target, executor, inventory) }
78
+ agent_targets, need_install_targets = targets.partition { |target| agent?(target, executor, inventory) }
83
79
  agent_targets.each { |target| Puppet.debug "Puppet Agent feature declared for #{target.name}" }
84
- unless unknown_targets.empty?
85
- # Ensure Puppet is installed
86
- version_task = get_task('puppet_agent::version')
87
- versions = run_task(unknown_targets, version_task)
88
- raise Bolt::RunFailure.new(versions, 'run_task', 'puppet_agent::version') unless versions.ok?
89
- need_install, installed = versions.partition { |r| r['version'].nil? }
90
- installed.each do |r|
91
- Puppet.debug "Puppet Agent #{r['version']} installed on #{r.target.name}"
92
- set_agent_feature(r.target)
93
- end
94
-
95
- unless need_install.empty?
96
- need_install_targets = need_install.map(&:target)
97
- # lazy-load expensive gem code
98
- require 'concurrent'
99
- pool = Concurrent::ThreadPoolExecutor.new
100
-
101
- hooks = need_install_targets.map do |t|
102
- begin
103
- opts = t.plugin_hooks&.fetch('puppet_library').dup
104
- plugin_name = opts.delete('plugin')
105
- hook = inventory.plugins.get_hook(plugin_name, :puppet_library)
106
- { 'target' => t,
107
- 'hook_proc' => hook.call(opts, t, self) }
108
- rescue StandardError => e
109
- Bolt::Result.from_exception(t, e)
110
- end
80
+ unless need_install_targets.empty?
81
+ # lazy-load expensive gem code
82
+ require 'concurrent'
83
+ pool = Concurrent::ThreadPoolExecutor.new
84
+
85
+ hooks = need_install_targets.map do |t|
86
+ begin
87
+ opts = t.plugin_hooks&.fetch('puppet_library').dup
88
+ plugin_name = opts.delete('plugin')
89
+ hook = inventory.plugins.get_hook(plugin_name, :puppet_library)
90
+ { 'target' => t,
91
+ 'hook_proc' => hook.call(opts, t, self) }
92
+ rescue StandardError => e
93
+ Bolt::Result.from_exception(t, e)
111
94
  end
95
+ end
112
96
 
113
- hook_errors, ok_hooks = hooks.partition { |h| h.is_a?(Bolt::Result) }
114
-
115
- futures = ok_hooks.map do |hash|
116
- Concurrent::Future.execute(executor: pool) do
117
- hash['hook_proc'].call
118
- end
119
- end
97
+ hook_errors, ok_hooks = hooks.partition { |h| h.is_a?(Bolt::Result) }
120
98
 
121
- results = futures.zip(ok_hooks).map do |f, hash|
122
- f.value || Bolt::Result.from_exception(hash['target'], f.reason)
99
+ futures = ok_hooks.map do |hash|
100
+ Concurrent::Future.execute(executor: pool) do
101
+ hash['hook_proc'].call
123
102
  end
124
- set = Bolt::ResultSet.new(results + hook_errors)
125
- raise Bolt::RunFailure.new(set.error_set, 'apply_prep') unless set.ok
103
+ end
126
104
 
127
- need_install_targets.each { |target| set_agent_feature(target) }
105
+ results = futures.zip(ok_hooks).map do |f, hash|
106
+ f.value || Bolt::Result.from_exception(hash['target'], f.reason)
128
107
  end
108
+ set = Bolt::ResultSet.new(results + hook_errors)
109
+ raise Bolt::RunFailure.new(set.error_set, 'apply_prep') unless set.ok
110
+
111
+ need_install_targets.each { |target| set_agent_feature(target) }
129
112
  end
130
113
 
131
114
  # Gather facts, including custom facts
@@ -30,7 +30,7 @@ Puppet::Functions.create_function(:get_resources) do
30
30
  tasksig = script_compiler.task_signature(name)
31
31
  raise Bolt::Error.new("#{name} could not be found", 'bolt/get-resources') unless tasksig
32
32
 
33
- task = Bolt::Task.new(tasksig.task_hash)
33
+ task = Bolt::Task.from_task_signature(tasksig)
34
34
  results = executor.run_task(targets, task, args)
35
35
  raise Bolt::RunFailure.new(results, 'run_task', task.name) unless results.ok?
36
36
  results
@@ -72,7 +72,7 @@ Puppet::Functions.create_function(:run_task) do
72
72
  # Don't bother loading the local task definition if all targets use the 'pcp' transport.
73
73
  if !targets.empty? && targets.all? { |t| t.transport == 'pcp' }
74
74
  # create a fake task
75
- task = Bolt::Task.new(name: task_name, files: [{ 'name' => '', 'path' => '' }])
75
+ task = Bolt::Task.new(task_name, {}, [{ 'name' => '', 'path' => '' }])
76
76
  else
77
77
  # TODO: use the compiler injection once PUP-8237 lands
78
78
  task_signature = Puppet::Pal::ScriptCompiler.new(closure_scope.compiler).task_signature(task_name)
@@ -80,7 +80,7 @@ Puppet::Functions.create_function(:run_task) do
80
80
  raise Bolt::Error.unknown_task(task_name)
81
81
  end
82
82
 
83
- task = Bolt::Task.new(task_signature.task_hash)
83
+ task = Bolt::Task.from_task_signature(task_signature)
84
84
 
85
85
  # Set the default value for any params that have one and were not provided
86
86
  params = task.parameter_defaults.merge(params)
@@ -1,19 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'base64'
4
+ require 'bolt/apply_result'
5
+ require 'bolt/apply_target'
6
+ require 'bolt/config'
7
+ require 'bolt/error'
8
+ require 'bolt/task'
9
+ require 'bolt/util/puppet_log_level'
4
10
  require 'find'
5
11
  require 'json'
6
12
  require 'logging'
7
13
  require 'open3'
8
- require 'bolt/error'
9
- require 'bolt/task'
10
- require 'bolt/apply_result'
11
- require 'bolt/apply_target'
12
- require 'bolt/util/puppet_log_level'
13
14
 
14
15
  module Bolt
15
16
  class Applicator
16
- def initialize(inventory, executor, modulepath, plugin_dirs, pdb_client, hiera_config, max_compiles)
17
+ def initialize(inventory, executor, modulepath, plugin_dirs, pdb_client, hiera_config, max_compiles, apply_settings)
17
18
  # lazy-load expensive gem code
18
19
  require 'concurrent'
19
20
 
@@ -23,6 +24,7 @@ module Bolt
23
24
  @plugin_dirs = plugin_dirs
24
25
  @pdb_client = pdb_client
25
26
  @hiera_config = hiera_config ? validate_hiera_config(hiera_config) : nil
27
+ @apply_settings = apply_settings || {}
26
28
 
27
29
  @pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
28
30
  @logger = Logging.logger[self]
@@ -50,7 +52,7 @@ module Bolt
50
52
  { 'name' => 'custom_facts.rb' },
51
53
  { 'name' => 'custom_facts.rb', 'remote' => true }
52
54
  ] }
53
- Bolt::Task.new(name: 'apply_helpers::custom_facts', files: [file], metadata: metadata)
55
+ Bolt::Task.new('apply_helpers::custom_facts', metadata, [file])
54
56
  end
55
57
  end
56
58
 
@@ -63,7 +65,7 @@ module Bolt
63
65
  { 'name' => 'apply_catalog.rb' },
64
66
  { 'name' => 'apply_catalog.rb', 'remote' => true }
65
67
  ] }
66
- Bolt::Task.new(name: 'apply_helpers::apply_catalog', files: [file], metadata: metadata)
68
+ Bolt::Task.new('apply_helpers::apply_catalog', metadata, [file])
67
69
  end
68
70
  end
69
71
 
@@ -77,7 +79,7 @@ module Bolt
77
79
  { 'name' => 'query_resources.rb', 'remote' => true }
78
80
  ] }
79
81
 
80
- Bolt::Task.new(name: 'apply_helpers::query_resources', files: [file], metadata: metadata)
82
+ Bolt::Task.new('apply_helpers::query_resources', metadata, [file])
81
83
  end
82
84
  end
83
85
 
@@ -250,7 +252,9 @@ module Bolt
250
252
  end
251
253
  # rubocop:enable Style/GlobalVars
252
254
 
253
- r = @executor.log_action('apply catalog', targets) do
255
+ description = options[:description] || 'apply catalog'
256
+
257
+ r = @executor.log_action(description, targets) do
254
258
  futures = targets.map do |target|
255
259
  Concurrent::Future.execute(executor: @pool) do
256
260
  @executor.with_node_logging("Compiling manifest block", [target]) do
@@ -270,6 +274,7 @@ module Bolt
270
274
  arguments = {
271
275
  'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
272
276
  'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
277
+ 'apply_settings' => @apply_settings,
273
278
  '_task' => catalog_apply_task.name,
274
279
  '_noop' => options[:noop]
275
280
  }
@@ -127,7 +127,7 @@ module Bolt
127
127
  when 'task'
128
128
  case action
129
129
  when 'run'
130
- { flags: ACTION_OPTS + %w[params tmpdir],
130
+ { flags: ACTION_OPTS + %w[params tmpdir noop],
131
131
  banner: TASK_RUN_HELP }
132
132
  when 'show'
133
133
  { flags: OPTIONS[:global] + OPTIONS[:global_config_setters],
@@ -627,7 +627,7 @@ module Bolt
627
627
  "'success' nodes that succeeded in the last run.") do |rerun|
628
628
  @options[:rerun] = rerun
629
629
  end
630
- define('--noop', 'Execute a task that supports it in noop mode') do |_|
630
+ define('--noop', 'See what changes Bolt will make without actually executing the changes') do |_|
631
631
  @options[:noop] = true
632
632
  end
633
633
  define('--description DESCRIPTION',
@@ -132,6 +132,9 @@ module Bolt
132
132
  # Logger must be configured before checking path case, otherwise warnings will not display
133
133
  @config.check_path_case('modulepath', @config.modulepath)
134
134
 
135
+ # Log the file paths for loaded config files
136
+ config_loaded
137
+
135
138
  parser.warnings.each { |warning| @logger.warn(warning[:msg]) }
136
139
  # After validation, initialize inventory and targets. Errors here are better to catch early.
137
140
  # After this step
@@ -448,7 +451,7 @@ module Bolt
448
451
  end
449
452
 
450
453
  def show_task(task_name)
451
- outputter.print_task_info(pal.get_task_info(task_name))
454
+ outputter.print_task_info(pal.get_task(task_name))
452
455
  end
453
456
 
454
457
  def list_tasks
@@ -665,7 +668,8 @@ module Bolt
665
668
  config.hiera_config,
666
669
  config.boltdir.resource_types,
667
670
  config.compile_concurrency,
668
- config.trusted_external)
671
+ config.trusted_external,
672
+ config.apply_settings)
669
673
  end
670
674
 
671
675
  def convert_plan(plan)
@@ -717,5 +721,12 @@ module Bolt
717
721
 
718
722
  content
719
723
  end
724
+
725
+ def config_loaded
726
+ msg = <<~MSG
727
+ Loaded configuration from: '#{config.config_files.join("', '")}'
728
+ MSG
729
+ @logger.debug(msg)
730
+ end
720
731
  end
721
732
  end
@@ -34,22 +34,18 @@ module Bolt
34
34
  class Config
35
35
  attr_accessor :concurrency, :format, :trace, :log, :puppetdb, :color, :save_rerun,
36
36
  :transport, :transports, :inventoryfile, :compile_concurrency, :boltdir,
37
- :puppetfile_config, :plugins, :plugin_hooks, :future, :trusted_external
37
+ :puppetfile_config, :plugins, :plugin_hooks, :future, :trusted_external,
38
+ :apply_settings
38
39
  attr_writer :modulepath
40
+ attr_reader :config_files
39
41
 
40
42
  OPTIONS = {
43
+ "apply_settings" => "A map of Puppet settings to use when applying Puppet code",
41
44
  "color" => "Whether to use colored output when printing messages to the console.",
42
45
  "compile-concurrency" => "The maximum number of simultaneous manifest block compiles.",
43
46
  "concurrency" => "The number of threads to use when executing on remote targets.",
44
47
  "format" => "The format to use when printing results. Options are `human` and `json`.",
45
48
  "hiera-config" => "The path to your Hiera config.",
46
- "interpreters" => "A map of an extension name to the absolute path of an executable, "\
47
- "enabling you to override the shebang defined in a task executable. The "\
48
- "extension can optionally be specified with the `.` character (`.py` and "\
49
- "`py` both map to a task executable `task.py`) and the extension is case "\
50
- "sensitive. The transports that support interpreter configuration are "\
51
- "`docker`, `local`, `ssh`, and `winrm`. When a target's name is `localhost`, "\
52
- "Ruby tasks run with the Bolt Ruby interpreter by default.",
53
49
  "inventoryfile" => "The path to a structured data inventory file used to refer to groups of "\
54
50
  "targets on the command line and from plans.",
55
51
  "log" => "The configuration of the logfile output. Configuration can be set for "\
@@ -58,6 +54,8 @@ module Bolt
58
54
  "of directories or a string containing a list of directories separated by the "\
59
55
  "OS-specific PATH separator.",
60
56
  "plugin_hooks" => "Which plugins a specific hook should use.",
57
+ "plugins" => "A map of plugins and their configuration data.",
58
+ "puppetdb" => "A map containing options for configuring the Bolt PuppetDB client.",
61
59
  "puppetfile" => "A map containing options for the `bolt puppetfile install` command.",
62
60
  "save-rerun" => "Whether to update `.rerun.json` in the Bolt project directory. If "\
63
61
  "your target names include passwords, set this value to `false` to avoid "\
@@ -101,23 +99,64 @@ module Bolt
101
99
  "level" => "`warn` for console, `notice` for file"
102
100
  }.freeze
103
101
 
102
+ APPLY_SETTINGS = {
103
+ "show_diff" => "Whether to log and report a contextual diff when files are being replaced. "\
104
+ "See [Puppet documentation](https://puppet.com/docs/puppet/latest/configuration.html#showdiff) "\
105
+ "for details"
106
+ }.freeze
107
+
108
+ DEFAULT_APPLY_SETTINGS = {
109
+ "show_diff" => false
110
+ }.freeze
111
+
104
112
  def self.default
105
113
  new(Bolt::Boltdir.new('.'), {})
106
114
  end
107
115
 
108
116
  def self.from_boltdir(boltdir, overrides = {})
109
- data = Bolt::Util.read_config_file(nil, [boltdir.config_file], 'config') || {}
117
+ data = {
118
+ filepath: boltdir.config_file,
119
+ data: Bolt::Util.read_config_file(nil, [boltdir.config_file], 'config')
120
+ }
121
+
122
+ data = load_defaults.push(data).select { |config| config[:data]&.any? }
123
+
110
124
  new(boltdir, data, overrides)
111
125
  end
112
126
 
113
127
  def self.from_file(configfile, overrides = {})
114
128
  boltdir = Bolt::Boltdir.new(Pathname.new(configfile).expand_path.dirname)
115
- data = Bolt::Util.read_config_file(configfile, [], 'config') || {}
129
+
130
+ data = {
131
+ filepath: boltdir.config_file,
132
+ data: Bolt::Util.read_config_file(configfile, [], 'config')
133
+ }
134
+
135
+ data = load_defaults.push(data).select { |config| config[:data]&.any? }
116
136
 
117
137
  new(boltdir, data, overrides)
118
138
  end
119
139
 
140
+ def self.load_defaults
141
+ # Lazy-load expensive gem code
142
+ require 'win32/dir' if Bolt::Util.windows?
143
+
144
+ system_path = if Bolt::Util.windows?
145
+ Pathname.new(File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'bolt', 'etc', 'bolt.yaml'))
146
+ else
147
+ Pathname.new(File.join('/etc', 'puppetlabs', 'bolt', 'bolt.yaml'))
148
+ end
149
+ user_path = Pathname.new(File.expand_path(File.join('~', '.puppetlabs', 'etc', 'bolt', 'bolt.yaml')))
150
+
151
+ [{ filepath: system_path, data: Bolt::Util.read_config_file(nil, [system_path], 'config') },
152
+ { filepath: user_path, data: Bolt::Util.read_config_file(nil, [user_path], 'config') }]
153
+ end
154
+
120
155
  def initialize(boltdir, config_data, overrides = {})
156
+ unless config_data.is_a?(Array)
157
+ config_data = [{ filepath: boltdir.config_file, data: config_data }]
158
+ end
159
+
121
160
  @logger = Logging.logger[self]
122
161
 
123
162
  @boltdir = boltdir
@@ -131,6 +170,7 @@ module Bolt
131
170
  @puppetfile_config = {}
132
171
  @plugins = {}
133
172
  @plugin_hooks = {}
173
+ @apply_settings = {}
134
174
 
135
175
  # add an entry for the default console logger
136
176
  @log = { 'console' => {} }
@@ -141,12 +181,39 @@ module Bolt
141
181
  @transports[key] = transport.default_options
142
182
  end
143
183
 
184
+ @config_files = config_data.map { |config| config[:filepath] }
185
+
186
+ config_data = merge_config_data(config_data)
144
187
  update_from_file(config_data)
188
+
145
189
  apply_overrides(overrides)
146
190
 
147
191
  validate
148
192
  end
149
193
 
194
+ # Merge configuration
195
+ # Precedence from highest to lowest is: project, user-level, system-wide
196
+ def merge_config_data(config_data)
197
+ config_data.inject({}) do |acc, config|
198
+ acc.merge(config[:data]) do |key, val1, val2|
199
+ case key
200
+ # Plugin config is shallow merged for each plugin
201
+ when 'plugins'
202
+ val1.merge(val2) { |_, v1, v2| v1.merge(v2) }
203
+ # Transports are deep merged
204
+ when *TRANSPORTS.keys.map(&:to_s)
205
+ Bolt::Util.deep_merge(val1, val2)
206
+ # Hash values are shallow mergeed
207
+ when 'puppetdb', 'plugin_hooks', 'apply_settings', 'log'
208
+ val1.merge(val2)
209
+ # All other values are overwritten
210
+ else
211
+ val2
212
+ end
213
+ end
214
+ end
215
+ end
216
+
150
217
  def overwrite_transport_data(transport, transports)
151
218
  @transport = transport
152
219
  @transports = transports
@@ -224,14 +291,16 @@ module Bolt
224
291
  @trusted_external = if data.key?('trusted-external-command')
225
292
  File.expand_path(data['trusted-external-command'], @boltdir.path)
226
293
  end
294
+
295
+ if data.key?('apply_settings')
296
+ @apply_settings = data['apply_settings'].select { |k, _| APPLY_SETTINGS.keys.include?(k) }
297
+ end
298
+
227
299
  @compile_concurrency = data['compile-concurrency'] if data.key?('compile-concurrency')
228
300
 
229
301
  @save_rerun = data['save-rerun'] if data.key?('save-rerun')
230
302
 
231
- @plugins = data['plugins'] if data.key?('plugins')
232
- @plugin_hooks = data['plugin_hooks'] if data.key?('plugin_hooks')
233
-
234
- %w[concurrency format puppetdb color].each do |key|
303
+ %w[concurrency format puppetdb color plugins plugin_hooks].each do |key|
235
304
  send("#{key}=", data[key]) if data.key?(key)
236
305
  end
237
306
 
@@ -222,9 +222,9 @@ module Bolt
222
222
  # Building lots of strings...
223
223
  pretty_params = +""
224
224
  task_info = +""
225
- usage = +"bolt task run --targets <node-name> #{task['name']}"
225
+ usage = +"bolt task run --targets <node-name> #{task.name}"
226
226
 
227
- task['metadata']['parameters']&.each do |k, v|
227
+ task.parameters&.each do |k, v|
228
228
  pretty_params << "- #{k}: #{v['type'] || 'Any'}\n"
229
229
  pretty_params << " Default: #{v['default'].inspect}\n" if v.key?('default')
230
230
  pretty_params << " #{v['description']}\n" if v['description']
@@ -235,16 +235,16 @@ module Bolt
235
235
  end
236
236
  end
237
237
 
238
- usage << " [--noop]" if task['metadata']['supports_noop']
238
+ usage << " [--noop]" if task.supports_noop
239
239
 
240
- task_info << "\n#{task['name']}"
241
- task_info << " - #{task['metadata']['description']}" if task['metadata']['description']
240
+ task_info << "\n#{task.name}"
241
+ task_info << " - #{task.description}" if task.description
242
242
  task_info << "\n\n"
243
243
  task_info << "USAGE:\n#{usage}\n\n"
244
244
  task_info << "PARAMETERS:\n#{pretty_params}\n" unless pretty_params.empty?
245
245
  task_info << "MODULE:\n"
246
246
 
247
- path = task['files'][0]['path'].chomp("/tasks/#{task['files'][0]['name']}")
247
+ path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
248
248
  task_info << if path.start_with?(Bolt::PAL::MODULES_PATH)
249
249
  "built-in module"
250
250
  else
@@ -55,13 +55,13 @@ module Bolt
55
55
  alias print_module_list print_table
56
56
 
57
57
  def print_task_info(task)
58
- path = task['files'][0]['path'].chomp("/tasks/#{task['files'][0]['name']}")
59
- task['module_dir'] = if path.start_with?(Bolt::PAL::MODULES_PATH)
60
- "built-in module"
61
- else
62
- path
63
- end
64
- @stream.puts task.to_json
58
+ path = task.files.first['path'].chomp("/tasks/#{task.files.first['name']}")
59
+ module_dir = if path.start_with?(Bolt::PAL::MODULES_PATH)
60
+ "built-in module"
61
+ else
62
+ path
63
+ end
64
+ @stream.puts task.to_h.merge(module_dir: module_dir).to_json
65
65
  end
66
66
 
67
67
  def print_tasks(tasks, modulepath)
@@ -39,7 +39,8 @@ module Bolt
39
39
 
40
40
  attr_reader :modulepath
41
41
 
42
- def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors, trusted_external = nil)
42
+ def initialize(modulepath, hiera_config, resource_types, max_compiles = Etc.nprocessors,
43
+ trusted_external = nil, apply_settings = {})
43
44
  # Nothing works without initialized this global state. Reinitializing
44
45
  # is safe and in practice only happens in tests
45
46
  self.class.load_puppet
@@ -48,6 +49,7 @@ module Bolt
48
49
  @modulepath = [BOLTLIB_PATH, *modulepath, MODULES_PATH]
49
50
  @hiera_config = hiera_config
50
51
  @trusted_external = trusted_external
52
+ @apply_settings = apply_settings
51
53
  @max_compiles = max_compiles
52
54
  @resource_types = resource_types
53
55
 
@@ -175,7 +177,8 @@ module Bolt
175
177
  @original_modulepath,
176
178
  pdb_client,
177
179
  @hiera_config,
178
- @max_compiles
180
+ @max_compiles,
181
+ @apply_settings
179
182
  )
180
183
  }
181
184
  Puppet.override(opts, &block)
@@ -280,14 +283,14 @@ module Bolt
280
283
  end
281
284
  end
282
285
 
283
- def get_task_info(task_name)
286
+ def get_task(task_name)
284
287
  task = task_signature(task_name)
285
288
 
286
289
  if task.nil?
287
290
  raise Bolt::Error.unknown_task(task_name)
288
291
  end
289
292
 
290
- task.task_hash.reject { |k, _| k == 'parameters' }
293
+ Bolt::Task.from_task_signature(task)
291
294
  end
292
295
 
293
296
  def list_plans
@@ -83,7 +83,7 @@ module Bolt
83
83
  raise Bolt::Error.unknown_task(task_name) unless tasksig
84
84
 
85
85
  Bolt::Task::Run.validate_params(tasksig, params) if params
86
- Bolt::Task.new(tasksig.task_hash)
86
+ Bolt::Task.from_task_signature(tasksig)
87
87
  end
88
88
  end
89
89
 
@@ -142,8 +142,8 @@ module Bolt
142
142
  plugins
143
143
  end
144
144
 
145
- RUBY_PLUGINS = %w[task pkcs7 prompt].freeze
146
- BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory yaml].freeze
145
+ RUBY_PLUGINS = %w[task pkcs7 prompt env_var].freeze
146
+ BUILTIN_PLUGINS = %w[task terraform pkcs7 prompt vault aws_inventory puppetdb azure_inventory yaml env_var].freeze
147
147
  DEFAULT_PLUGIN_HOOKS = { 'puppet_library' => { 'plugin' => 'puppet_agent', 'stop_service' => true } }.freeze
148
148
 
149
149
  attr_reader :pal, :plugin_context
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ class Plugin
5
+ class EnvVar
6
+ def initialize(*_args); end
7
+
8
+ def name
9
+ 'env_var'
10
+ end
11
+
12
+ def hooks
13
+ %i[resolve_reference validate_resolve_reference]
14
+ end
15
+
16
+ def validate_resolve_reference(opts)
17
+ unless opts['var']
18
+ raise Bolt::ValidationError, "env_var plugin requires that the 'var' is specified"
19
+ end
20
+ unless ENV[opts['var']]
21
+ raise Bolt::ValidationError, "env_var plugin requires that the var '#{opts['var']}' be set"
22
+ end
23
+ end
24
+
25
+ def resolve_reference(opts)
26
+ ENV[opts['var']]
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'concurrent/delay'
4
3
  module Bolt
5
4
  class Plugin
6
5
  class Prompt
@@ -11,7 +10,7 @@ module Bolt
11
10
  end
12
11
 
13
12
  def hooks
14
- [:resolve_reference]
13
+ %i[resolve_reference validate_resolve_reference]
15
14
  end
16
15
 
17
16
  def validate_resolve_reference(opts)
@@ -8,30 +8,33 @@ module Bolt
8
8
  end
9
9
  end
10
10
 
11
- # Represents a Task.
12
- # @file and @files are mutually exclusive.
13
- # @name [String] name of the task
14
- # @file [Hash, nil] containing `filename` and `file_content`
15
- # @files [Array<Hash>] where each entry includes `name` and `path`
16
- # @metadata [Hash] task metadata
17
- Task = Struct.new(
18
- :name,
19
- :file,
20
- :files,
21
- :metadata
22
- ) do
23
- attr_reader :remote
24
-
25
- def initialize(task, remote: false)
26
- super(nil, nil, [], {})
27
-
11
+ class Task
12
+ METADATA_KEYS = %w[description extensions files implementations
13
+ input_method parameters private puppet_task_version
14
+ remote supports_noop].freeze
15
+
16
+ attr_reader :name, :files, :metadata, :remote
17
+
18
+ # name [String] name of the task
19
+ # files [Array<Hash>] where each entry includes `name` and `path`
20
+ # metadata [Hash] task metadata
21
+ def initialize(name, metadata = {}, files = [], remote = false)
22
+ @name = name
23
+ @metadata = metadata
24
+ @files = files
28
25
  @remote = remote
26
+ @logger = Logging.logger[self]
27
+
28
+ validate_metadata
29
+ end
29
30
 
30
- task.reject { |k, _| k == 'parameters' }.each { |k, v| self[k] = v }
31
+ def self.from_task_signature(task_sig)
32
+ hash = task_sig.task_hash
33
+ new(hash['name'], hash.fetch('metadata', {}), hash.fetch('files', []))
31
34
  end
32
35
 
33
36
  def remote_instance
34
- self.class.new(to_h.each_with_object({}) { |(k, v), h| h[k.to_s] = v }, remote: true)
37
+ self.class.new(@name, @metadata, @files, true)
35
38
  end
36
39
 
37
40
  def description
@@ -118,5 +121,33 @@ module Bolt
118
121
 
119
122
  impl
120
123
  end
124
+
125
+ def eql?(other)
126
+ self.class == other.class &&
127
+ @name == other.name &&
128
+ @metadata == other.metadata &&
129
+ @files == other.files &&
130
+ @remote == other.remote
131
+ end
132
+
133
+ alias == :eql?
134
+
135
+ def to_h
136
+ {
137
+ name: @name,
138
+ files: @files,
139
+ metadata: @metadata
140
+ }
141
+ end
142
+
143
+ def validate_metadata
144
+ unknown_keys = metadata.keys - METADATA_KEYS
145
+
146
+ if unknown_keys.any?
147
+ msg = "Metadata for task '#{@name}' contains unknown keys: #{unknown_keys.join(', ')}."
148
+ msg += " This could be a typo in the task metadata or may result in incorrect behavior."
149
+ @logger.warn(msg)
150
+ end
151
+ end
121
152
  end
122
153
  end
@@ -4,21 +4,18 @@ module Bolt
4
4
  class Task
5
5
  class PuppetServer < Bolt::Task
6
6
  def remote_instance
7
- self.class.new(to_h.each_with_object({}) { |(k, v), h| h[k.to_s] = v },
8
- @file_cache,
9
- remote: true)
7
+ self.class.new(@name, @metadata, @files, @file_cache, true)
10
8
  end
11
9
 
12
- def initialize(task, file_cache, **opts)
13
- super(task, **opts)
10
+ def initialize(name, metadata, files, file_cache, remote = false)
11
+ super(name, metadata, files, remote)
14
12
  @file_cache = file_cache
15
- update_file_data(task)
13
+ update_file_data
16
14
  end
17
15
 
18
16
  # puppetserver file entries have 'filename' rather then 'name'
19
- def update_file_data(task_data)
20
- task_data['files'].each { |f| f['name'] = f['filename'] }
21
- task_data
17
+ def update_file_data
18
+ @files.each { |f| f['name'] = f['filename'] }
22
19
  end
23
20
 
24
21
  def file_path(file_name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '1.47.0'
4
+ VERSION = '1.48.0'
5
5
  end
@@ -101,7 +101,8 @@ module BoltServer
101
101
  error = validate_schema(@schemas["action-run_task"], body)
102
102
  return [], error unless error.nil?
103
103
 
104
- task = Bolt::Task::PuppetServer.new(body['task'], @file_cache)
104
+ task_data = body['task']
105
+ task = Bolt::Task::PuppetServer.new(task_data['name'], task_data['metadata'], task_data['files'], @file_cache)
105
106
  parameters = body['parameters'] || {}
106
107
  [@executor.run_task(target, task, parameters), nil]
107
108
  end
@@ -28,6 +28,9 @@ Puppet[:report] = false
28
28
 
29
29
  # Make sure to apply the catalog
30
30
  Puppet[:noop] = args['_noop'] || false
31
+ args['apply_settings'].each do |setting, value|
32
+ Puppet[setting.to_sym] = value
33
+ end
31
34
 
32
35
  Puppet[:default_file_terminus] = :file_server
33
36
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bolt
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.47.0
4
+ version: 1.48.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Puppet
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-27 00:00:00.000000000 Z
11
+ date: 2020-02-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable
@@ -420,6 +420,7 @@ files:
420
420
  - lib/bolt/pal/yaml_plan/transpiler.rb
421
421
  - lib/bolt/plan_result.rb
422
422
  - lib/bolt/plugin.rb
423
+ - lib/bolt/plugin/env_var.rb
423
424
  - lib/bolt/plugin/module.rb
424
425
  - lib/bolt/plugin/pkcs7.rb
425
426
  - lib/bolt/plugin/prompt.rb