bolt 2.13.0 → 2.18.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -0
  4. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +20 -9
  6. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -0
  7. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +1 -0
  8. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +1 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +1 -0
  11. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +1 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +1 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +1 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +1 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +3 -0
  18. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +7 -2
  19. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +7 -4
  20. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +2 -1
  21. data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +1 -0
  22. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +1 -0
  23. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +1 -0
  24. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +1 -0
  25. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -0
  26. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +1 -0
  27. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +1 -0
  28. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -0
  29. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  30. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  31. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +2 -0
  32. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +2 -1
  33. data/bolt-modules/file/lib/puppet/functions/file/join.rb +2 -0
  34. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -1
  35. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
  36. data/bolt-modules/file/lib/puppet/functions/file/write.rb +2 -0
  37. data/bolt-modules/out/lib/puppet/functions/out/message.rb +2 -0
  38. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +1 -0
  39. data/bolt-modules/system/lib/puppet/functions/system/env.rb +2 -0
  40. data/lib/bolt/applicator.rb +36 -21
  41. data/lib/bolt/apply_inventory.rb +4 -0
  42. data/lib/bolt/apply_result.rb +1 -1
  43. data/lib/bolt/apply_target.rb +4 -0
  44. data/lib/bolt/bolt_option_parser.rb +27 -16
  45. data/lib/bolt/catalog.rb +79 -69
  46. data/lib/bolt/cli.rb +78 -58
  47. data/lib/bolt/config.rb +163 -136
  48. data/lib/bolt/config/options.rb +474 -0
  49. data/lib/bolt/config/transport/base.rb +16 -16
  50. data/lib/bolt/config/transport/docker.rb +9 -23
  51. data/lib/bolt/config/transport/local.rb +6 -44
  52. data/lib/bolt/config/transport/options.rb +460 -0
  53. data/lib/bolt/config/transport/orch.rb +9 -18
  54. data/lib/bolt/config/transport/remote.rb +3 -6
  55. data/lib/bolt/config/transport/ssh.rb +78 -119
  56. data/lib/bolt/config/transport/winrm.rb +18 -47
  57. data/lib/bolt/executor.rb +14 -1
  58. data/lib/bolt/inventory/group.rb +1 -1
  59. data/lib/bolt/inventory/inventory.rb +4 -14
  60. data/lib/bolt/inventory/target.rb +22 -5
  61. data/lib/bolt/logger.rb +15 -1
  62. data/lib/bolt/outputter.rb +3 -0
  63. data/lib/bolt/outputter/rainbow.rb +84 -0
  64. data/lib/bolt/pal.rb +23 -9
  65. data/lib/bolt/project.rb +75 -55
  66. data/lib/bolt/resource_instance.rb +5 -1
  67. data/lib/bolt/shell/bash.rb +30 -42
  68. data/lib/bolt/shell/powershell.rb +13 -8
  69. data/lib/bolt/shell/powershell/snippets.rb +23 -6
  70. data/lib/bolt/transport/docker.rb +9 -5
  71. data/lib/bolt/transport/local/connection.rb +2 -1
  72. data/lib/bolt/transport/orch.rb +8 -0
  73. data/lib/bolt/transport/ssh.rb +7 -1
  74. data/lib/bolt/transport/ssh/connection.rb +35 -0
  75. data/lib/bolt/transport/ssh/exec_connection.rb +1 -1
  76. data/lib/bolt/version.rb +1 -1
  77. data/lib/bolt_spec/bolt_context.rb +1 -1
  78. data/lib/bolt_spec/run.rb +1 -1
  79. metadata +22 -18
@@ -12,7 +12,9 @@ Puppet::Functions.create_function(:'file::join') do
12
12
  end
13
13
 
14
14
  def join(*paths)
15
+ # Send Analytics Report
15
16
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
17
+
16
18
  File.join(paths)
17
19
  end
18
20
  end
@@ -8,7 +8,7 @@ Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunc
8
8
  # @example Read a file from disk
9
9
  # file::read('/tmp/i_dumped_this_here')
10
10
  # @example Read a file from the modulepath
11
- # file::read('example/files/VERSION')
11
+ # file::read('example/VERSION')
12
12
  dispatch :read do
13
13
  scope_param
14
14
  required_param 'String', :filename
@@ -16,7 +16,9 @@ Puppet::Functions.create_function(:'file::read', Puppet::Functions::InternalFunc
16
16
  end
17
17
 
18
18
  def read(scope, filename)
19
+ # Send Analytics Report
19
20
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
21
+
20
22
  found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
21
23
  unless found && Puppet::FileSystem.exist?(found)
22
24
  raise Puppet::ParseErrorWithIssue.from_issue_and_stack(
@@ -9,7 +9,7 @@ Puppet::Functions.create_function(:'file::readable', Puppet::Functions::Internal
9
9
  # @example Check a file on disk
10
10
  # file::readable('/tmp/i_dumped_this_here')
11
11
  # @example check a file from the modulepath
12
- # file::readable('example/files/VERSION')
12
+ # file::readable('example/VERSION')
13
13
  dispatch :readable do
14
14
  scope_param
15
15
  required_param 'String', :filename
@@ -17,7 +17,9 @@ Puppet::Functions.create_function(:'file::readable', Puppet::Functions::Internal
17
17
  end
18
18
 
19
19
  def readable(scope, filename)
20
+ # Send Analytics Report
20
21
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
22
+
21
23
  found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
22
24
  found ? File.readable?(found) : false
23
25
  end
@@ -15,7 +15,9 @@ Puppet::Functions.create_function(:'file::write') do
15
15
  end
16
16
 
17
17
  def write(filename, content)
18
+ # Send Analytics Report
18
19
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
20
+
19
21
  File.write(filename, content)
20
22
  nil
21
23
  end
@@ -23,7 +23,9 @@ Puppet::Functions.create_function(:'out::message') do
23
23
  end
24
24
 
25
25
  executor = Puppet.lookup(:bolt_executor)
26
+ # Send Analytics Report
26
27
  executor.report_function_call(self.class.name)
28
+
27
29
  executor.publish_event(type: :message, message: message)
28
30
 
29
31
  nil
@@ -30,6 +30,7 @@ Puppet::Functions.create_function(:prompt) do
30
30
  options = options.transform_keys(&:to_sym)
31
31
 
32
32
  executor = Puppet.lookup(:bolt_executor)
33
+ # Send analytics report
33
34
  executor.report_function_call(self.class.name)
34
35
 
35
36
  response = executor.prompt(prompt, options)
@@ -12,7 +12,9 @@ Puppet::Functions.create_function(:'system::env') do
12
12
  end
13
13
 
14
14
  def env(name)
15
+ # Send analytics report
15
16
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
17
+
16
18
  ENV[name]
17
19
  end
18
20
  end
@@ -18,10 +18,9 @@ module Bolt
18
18
  pdb_client, hiera_config, max_compiles, apply_settings)
19
19
  # lazy-load expensive gem code
20
20
  require 'concurrent'
21
-
22
21
  @inventory = inventory
23
22
  @executor = executor
24
- @modulepath = modulepath
23
+ @modulepath = modulepath || []
25
24
  @plugin_dirs = plugin_dirs
26
25
  @project = project
27
26
  @pdb_client = pdb_client
@@ -30,17 +29,6 @@ module Bolt
30
29
 
31
30
  @pool = Concurrent::ThreadPoolExecutor.new(max_threads: max_compiles)
32
31
  @logger = Logging.logger[self]
33
- @plugin_tarball = Concurrent::Delay.new do
34
- build_plugin_tarball do |mod|
35
- search_dirs = []
36
- search_dirs << mod.plugins if mod.plugins?
37
- search_dirs << mod.pluginfacts if mod.pluginfacts?
38
- search_dirs << mod.files if mod.files?
39
- type_files = "#{mod.path}/types"
40
- search_dirs << type_files if File.exist?(type_files)
41
- search_dirs
42
- end
43
- end
44
32
  end
45
33
 
46
34
  private def libexec
@@ -106,6 +94,16 @@ module Bolt
106
94
  out, err, stat = Open3.capture3('ruby', bolt_catalog_exe, 'compile', stdin_data: catalog_input.to_json)
107
95
  ENV['PATH'] = old_path
108
96
 
97
+ # If bolt_catalog does not return valid JSON, we should print stderr to
98
+ # see what happened
99
+ print_logs = stat.success?
100
+ result = begin
101
+ JSON.parse(out)
102
+ rescue JSON::ParserError
103
+ print_logs = true
104
+ { 'message' => "Something's gone terribly wrong! STDERR is logged." }
105
+ end
106
+
109
107
  # Any messages logged by Puppet will be on stderr as JSON hashes, so we
110
108
  # parse those and store them here. Any message on stderr that is not
111
109
  # properly JSON formatted is assumed to be an error message. If
@@ -119,17 +117,15 @@ module Bolt
119
117
  { 'level' => 'err', 'message' => line }
120
118
  end
121
119
 
122
- result = JSON.parse(out)
123
- if stat.success?
120
+ if print_logs
124
121
  logs.each do |log|
125
122
  bolt_level = Bolt::Util::PuppetLogLevel::MAPPING[log['level'].to_sym]
126
123
  message = log['message'].chomp
127
124
  @logger.send(bolt_level, "#{target.name}: #{message}")
128
125
  end
129
- result
130
- else
131
- raise ApplyError.new(target.name, result['message'])
132
126
  end
127
+ raise ApplyError.new(target.name, result['message']) unless stat.success?
128
+ result
133
129
  end
134
130
 
135
131
  def validate_hiera_config(hiera_config)
@@ -146,6 +142,7 @@ module Bolt
146
142
 
147
143
  def apply(args, apply_body, scope)
148
144
  raise(ArgumentError, 'apply requires a TargetSpec') if args.empty?
145
+ raise(ArgumentError, 'apply requires at least one statement in the apply block') if apply_body.nil?
149
146
  type0 = Puppet.lookup(:pal_script_compiler).type('TargetSpec')
150
147
  Puppet::Pal.assert_type(type0, args[0], 'apply targets')
151
148
 
@@ -179,7 +176,6 @@ module Bolt
179
176
 
180
177
  def apply_ast(raw_ast, targets, options, plan_vars = {})
181
178
  ast = Puppet::Pops::Serialization::ToDataConverter.convert(raw_ast, rich_data: true, symbol_to_string: true)
182
-
183
179
  # Serialize as pcore for *Result* objects
184
180
  plan_vars = Puppet::Pops::Serialization::ToDataConverter.convert(plan_vars,
185
181
  rich_data: true,
@@ -187,19 +183,37 @@ module Bolt
187
183
  type_by_reference: true,
188
184
  local_reference: true)
189
185
 
186
+ bolt_project = @project if @project&.name
190
187
  scope = {
191
188
  code_ast: ast,
192
189
  modulepath: @modulepath,
193
- project: @project.to_h,
190
+ project: bolt_project.to_h,
194
191
  pdb_config: @pdb_client.config.to_hash,
195
192
  hiera_config: @hiera_config,
196
193
  plan_vars: plan_vars,
197
194
  # This data isn't available on the target config hash
198
195
  config: @inventory.transport_data_get
199
196
  }
200
-
201
197
  description = options[:description] || 'apply catalog'
202
198
 
199
+ required_modules = options[:required_modules].nil? ? nil : Array(options[:required_modules])
200
+ if required_modules&.any?
201
+ @logger.debug("Syncing only required modules: #{required_modules.join(',')}.")
202
+ end
203
+
204
+ @plugin_tarball = Concurrent::Delay.new do
205
+ build_plugin_tarball do |mod|
206
+ next unless required_modules.nil? || required_modules.include?(mod.name)
207
+ search_dirs = []
208
+ search_dirs << mod.plugins if mod.plugins?
209
+ search_dirs << mod.pluginfacts if mod.pluginfacts?
210
+ search_dirs << mod.files if mod.files?
211
+ type_files = "#{mod.path}/types"
212
+ search_dirs << type_files if File.exist?(type_files)
213
+ search_dirs
214
+ end
215
+ end
216
+
203
217
  r = @executor.log_action(description, targets) do
204
218
  futures = targets.map do |target|
205
219
  Concurrent::Future.execute(executor: @pool) do
@@ -226,6 +240,7 @@ module Bolt
226
240
  result
227
241
  end
228
242
  else
243
+
229
244
  arguments = {
230
245
  'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
231
246
  'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
@@ -70,6 +70,10 @@ module Bolt
70
70
  @targets[target.name].features
71
71
  end
72
72
 
73
+ def resource(target, type, title)
74
+ @targets[target.name].resource(type, title)
75
+ end
76
+
73
77
  def add_to_group(*_params)
74
78
  raise InvalidFunctionCall, 'add_to_group'
75
79
  end
@@ -20,7 +20,7 @@ module Bolt
20
20
  error_hash['msg'] =~ /The term 'ruby.exe' is not recognized as the name of a cmdlet/)
21
21
  # Windows does not have Ruby present
22
22
  {
23
- 'msg' => "Puppet is not installed on the target in $env:ProgramFiles, please install it to enable 'apply'",
23
+ 'msg' => "Puppet was not found on the target or in $env:ProgramFiles, please install it to enable 'apply'",
24
24
  'kind' => 'bolt/apply-error'
25
25
  }
26
26
  elsif exit_code == 1 && error_hash['msg'] =~ /cannot load such file -- puppet \(LoadError\)/
@@ -62,6 +62,10 @@ module Bolt
62
62
  @safe_name
63
63
  end
64
64
 
65
+ def resource(type, title)
66
+ resources[Bolt::ResourceInstance.format_reference(type, title)]
67
+ end
68
+
65
69
  def parse_uri(string)
66
70
  require 'addressable/uri'
67
71
  if string.nil?
@@ -10,10 +10,10 @@ module Bolt
10
10
  authentication: %w[user password password-prompt private-key host-key-check ssl ssl-verify],
11
11
  escalation: %w[run-as sudo-password sudo-password-prompt sudo-executable],
12
12
  run_context: %w[concurrency inventoryfile save-rerun cleanup],
13
- global_config_setters: %w[modulepath boltdir configfile],
14
- transports: %w[transport connect-timeout tty ssh-command copy-command],
13
+ global_config_setters: %w[modulepath project configfile],
14
+ transports: %w[transport connect-timeout tty native-ssh ssh-command copy-command],
15
15
  display: %w[format color verbose trace],
16
- global: %w[help version debug] }.freeze
16
+ global: %w[help version debug log-level] }.freeze
17
17
 
18
18
  ACTION_OPTS = OPTIONS.values.flatten.freeze
19
19
 
@@ -422,7 +422,7 @@ module Bolt
422
422
 
423
423
  ACTIONS
424
424
  generate-types Generate type references to register in plans
425
- install Install modules from a Puppetfile into a Boltdir
425
+ install Install modules from a Puppetfile into a project
426
426
  show-modules List modules available to the Bolt project
427
427
  HELP
428
428
 
@@ -445,7 +445,7 @@ module Bolt
445
445
  bolt puppetfile install [options]
446
446
 
447
447
  DESCRIPTION
448
- Install modules from a Puppetfile into a Boltdir
448
+ Install modules from a Puppetfile into a project
449
449
  HELP
450
450
 
451
451
  PUPPETFILE_SHOWMODULES_HELP = <<~HELP
@@ -709,29 +709,29 @@ module Bolt
709
709
  File.expand_path(moduledir)
710
710
  end
711
711
  end
712
- define('--boltdir FILEPATH',
713
- 'Specify what Boltdir to load config from (default: autodiscovered from current working dir)') do |path|
712
+ define('--project PATH', '--boltdir PATH',
713
+ 'Specify what project to load config from (default: autodiscovered from current working dir)') do |path|
714
714
  @options[:boltdir] = path
715
715
  end
716
- define('--configfile FILEPATH',
716
+ define('--configfile PATH',
717
717
  'Specify where to load config from (default: ~/.puppetlabs/bolt/bolt.yaml).',
718
- 'Directory containing bolt.yaml will be used as the Boltdir.') do |path|
718
+ 'Directory containing bolt.yaml will be used as the project directory.') do |path|
719
719
  @options[:configfile] = path
720
720
  end
721
- define('--hiera-config FILEPATH',
721
+ define('--hiera-config PATH',
722
722
  'Specify where to load Hiera config from (default: ~/.puppetlabs/bolt/hiera.yaml)') do |path|
723
723
  @options[:'hiera-config'] = File.expand_path(path)
724
724
  end
725
- define('-i', '--inventoryfile FILEPATH',
725
+ define('-i', '--inventoryfile PATH',
726
726
  'Specify where to load inventory from (default: ~/.puppetlabs/bolt/inventory.yaml)') do |path|
727
727
  if ENV.include?(Bolt::Inventory::ENVIRONMENT_VAR)
728
728
  raise Bolt::CLIError, "Cannot pass inventory file when #{Bolt::Inventory::ENVIRONMENT_VAR} is set"
729
729
  end
730
730
  @options[:inventoryfile] = Pathname.new(File.expand_path(path))
731
731
  end
732
- define('--puppetfile FILEPATH',
732
+ define('--puppetfile PATH',
733
733
  'Specify a Puppetfile to use when installing modules. (default: ~/.puppetlabs/bolt/Puppetfile)',
734
- 'Modules are installed in the current Boltdir.') do |path|
734
+ 'Modules are installed in the current project.') do |path|
735
735
  @options[:puppetfile_path] = Pathname.new(File.expand_path(path))
736
736
  end
737
737
  define('--[no-]save-rerun', 'Whether to update the rerun file after this command.') do |save|
@@ -743,11 +743,15 @@ module Bolt
743
743
  "Specify a default transport: #{TRANSPORTS.keys.join(', ')}") do |t|
744
744
  @options[:transport] = t
745
745
  end
746
- define('--ssh-command EXEC', "Executable to use instead of the net-ssh ruby library. ",
746
+ define('--[no-]native-ssh', 'Whether to shell out to native SSH or use the net-ssh Ruby library.',
747
+ 'This option is experimental') do |bool|
748
+ @options[:'native-ssh'] = bool
749
+ end
750
+ define('--ssh-command EXEC', "Executable to use instead of the net-ssh Ruby library. ",
747
751
  "This option is experimental.") do |exec|
748
752
  @options[:'ssh-command'] = exec
749
753
  end
750
- define('--copy-command EXEC', "Command to copy files to remote hosts if using external SSH. ",
754
+ define('--copy-command EXEC', "Command to copy files to remote hosts if using native SSH. ",
751
755
  "This option is experimental.") do |exec|
752
756
  @options[:'copy-command'] = exec
753
757
  end
@@ -803,8 +807,15 @@ module Bolt
803
807
  end
804
808
  define('--debug', 'Display debug logging') do |_|
805
809
  @options[:debug] = true
810
+ # We don't actually set '--log-level debug' here, but once the options are evaluated by
811
+ # the config class the end result is the same.
812
+ @warnings << { msg: "Command line option '--debug' is deprecated, set '--log-level debug' instead." }
813
+ end
814
+ define('--log-level LEVEL',
815
+ "Set the log level for the console. Available options are",
816
+ "debug, info, notice, warn, error, fatal, any.") do |level|
817
+ @options[:log] = { 'console' => { 'level' => level } }
806
818
  end
807
-
808
819
  define('--plugin PLUGIN', 'Select the plugin to use') do |plug|
809
820
  @options[:plugin] = plug
810
821
  end
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt/apply_inventory'
3
4
  require 'bolt/apply_target'
4
5
  require 'bolt/config'
5
6
  require 'bolt/error'
6
7
  require 'bolt/inventory'
7
- require 'bolt/apply_inventory'
8
8
  require 'bolt/pal'
9
9
  require 'bolt/puppetdb'
10
10
  require 'bolt/util'
@@ -19,7 +19,7 @@ module Bolt
19
19
  @log_level = log_level
20
20
  end
21
21
 
22
- def with_puppet_settings(hiera_config = {})
22
+ def with_puppet_settings(overrides = {})
23
23
  Dir.mktmpdir('bolt') do |dir|
24
24
  cli = []
25
25
  Puppet::Settings::REQUIRED_APP_SETTINGS.each do |setting|
@@ -31,7 +31,9 @@ module Bolt
31
31
  Puppet.settings.override_default(:vendormoduledir, '')
32
32
 
33
33
  Puppet.initialize_settings(cli)
34
- Puppet.settings[:hiera_config] = hiera_config
34
+ overrides.each do |setting, value|
35
+ Puppet.settings[setting] = value
36
+ end
35
37
 
36
38
  # Use a special logdest that serializes all log messages and their level to stderr.
37
39
  Puppet::Util::Log.newdestination(:stderr)
@@ -54,83 +56,51 @@ module Bolt
54
56
  end
55
57
 
56
58
  def compile_catalog(request)
57
- pal_main = request['code_ast'] || request['code_string']
58
- target = request['target']
59
59
  pdb_client = Bolt::PuppetDB::Client.new(Bolt::PuppetDB::Config.new(request['pdb_config']))
60
- options = request['puppet_config'] || {}
61
60
  project = request['project'] || {}
62
61
  bolt_project = Struct.new(:name, :path).new(project['name'], project['path']) unless project.empty?
63
- with_puppet_settings(request['hiera_config']) do
64
- Puppet[:rich_data] = true
65
- Puppet[:node_name_value] = target['name']
66
- env_conf = { modulepath: request['modulepath'] || [],
67
- facts: target['facts'] || {} }
68
- env_conf[:variables] = {}
62
+ inv = Bolt::ApplyInventory.new(request['config'])
63
+ puppet_overrides = {
64
+ bolt_pdb_client: pdb_client,
65
+ bolt_inventory: inv,
66
+ bolt_project: bolt_project
67
+ }
68
+
69
+ # Facts will be set by the catalog compiler, so we need to ensure
70
+ # that any plan or target variables with the same name are not
71
+ # passed into the apply block to avoid a redefinition error.
72
+ # Filter out plan and target vars separately and raise a Puppet
73
+ # warning if there are any collisions for either. Puppet warning
74
+ # is the only way to log a message that will make it back to Bolt
75
+ # to be printed.
76
+ target = request['target']
77
+ plan_vars = shadow_vars('plan', request['plan_vars'], target['facts'])
78
+ target_vars = shadow_vars('target', target['variables'], target['facts'])
79
+ topscope_vars = target_vars.merge(plan_vars)
80
+ env_conf = { modulepath: request['modulepath'],
81
+ facts: target['facts'],
82
+ variables: topscope_vars }
83
+
84
+ puppet_settings = {
85
+ node_name_value: target['name'],
86
+ hiera_config: request['hiera_config']
87
+ }
88
+
89
+ with_puppet_settings(puppet_settings) do
69
90
  Puppet::Pal.in_tmp_environment('bolt_catalog', env_conf) do |pal|
70
- inv = Bolt::ApplyInventory.new(request['config'])
71
- Puppet.override(bolt_pdb_client: pdb_client,
72
- bolt_inventory: inv,
73
- bolt_project: bolt_project) do
91
+ Puppet.override(puppet_overrides) do
74
92
  Puppet.lookup(:pal_current_node).trusted_data = target['trusted']
75
93
  pal.with_catalog_compiler do |compiler|
76
- # Deserializing needs to happen inside the catalog compiler so
77
- # loaders are initialized for loading
78
- plan_vars = Puppet::Pops::Serialization::FromDataConverter.convert(request['plan_vars'])
79
-
80
- # Facts will be set by the catalog compiler, so we need to ensure
81
- # that any plan or target variables with the same name are not
82
- # passed into the apply block to avoid a redefinition error.
83
- # Filter out plan and target vars separately and raise a Puppet
84
- # warning if there are any collisions for either. Puppet warning
85
- # is the only way to log a message that will make it back to Bolt
86
- # to be printed.
87
- pv_collisions, pv_filtered = plan_vars.partition do |k, _|
88
- target['facts'].keys.include?(k)
89
- end.map(&:to_h)
90
- unless pv_collisions.empty?
91
- print_pv = pv_collisions.keys.map { |k| "$#{k}" }.join(', ')
92
- plural = pv_collisions.keys.length == 1 ? '' : 's'
93
- Puppet.warning("Plan variable#{plural} #{print_pv} will be overridden by fact#{plural} " \
94
- "of the same name in the apply block")
95
- end
96
-
97
- tv_collisions, tv_filtered = target['variables'].partition do |k, _|
98
- target['facts'].keys.include?(k)
99
- end.map(&:to_h)
100
- unless tv_collisions.empty?
101
- print_tv = tv_collisions.keys.map { |k| "$#{k}" }.join(', ')
102
- plural = tv_collisions.keys.length == 1 ? '' : 's'
103
- Puppet.warning("Target variable#{plural} #{print_tv} " \
104
- "will be overridden by fact#{plural} of the same name in the apply block")
105
- end
106
-
107
- pal.send(:add_variables, compiler.send(:topscope), tv_filtered.merge(pv_filtered))
108
-
94
+ options = request['puppet_config'] || {}
109
95
  # Configure language strictness in the CatalogCompiler. We want Bolt to be able
110
96
  # to compile most Puppet 4+ manifests, so we default to allowing deprecated functions.
111
97
  Puppet[:strict] = options['strict'] || :warning
112
98
  Puppet[:strict_variables] = options['strict_variables'] || false
113
- ast = Puppet::Pops::Serialization::FromDataConverter.convert(pal_main)
114
- # This will be a Program when running via `bolt apply`, but will
115
- # only be a subset of the AST when compiling an apply block in a
116
- # plan. In that case, we need to discover the definitions (which
117
- # would ordinarily be stored on the Program) and construct a Program object.
118
- unless ast.is_a?(Puppet::Pops::Model::Program)
119
- # Node definitions must be at the top level of the apply block.
120
- # That means the apply body either a) consists of just a
121
- # NodeDefinition, b) consists of a BlockExpression which may
122
- # contain NodeDefinitions, or c) doesn't contain NodeDefinitions.
123
- definitions = if ast.is_a?(Puppet::Pops::Model::BlockExpression)
124
- ast.statements.select { |st| st.is_a?(Puppet::Pops::Model::NodeDefinition) }
125
- elsif ast.is_a?(Puppet::Pops::Model::NodeDefinition)
126
- [ast]
127
- else
128
- []
129
- end
130
- ast = Puppet::Pops::Model::Factory.PROGRAM(ast, definitions, ast.locator).model
131
- end
99
+
100
+ pal_main = request['code_ast'] || request['code_string']
101
+ ast = build_program(pal_main)
132
102
  compiler.evaluate(ast)
133
- compiler.instance_variable_get(:@internal_compiler).send(:evaluate_ast_node)
103
+ compiler.evaluate_ast_node
134
104
  compiler.compile_additions
135
105
  compiler.with_json_encoding(&:encode)
136
106
  end
@@ -138,5 +108,45 @@ module Bolt
138
108
  end
139
109
  end
140
110
  end
111
+
112
+ # Warn and remove variables that will be shadowed by facts of the same
113
+ # name, which are set in scope earlier.
114
+ def shadow_vars(type, vars, facts)
115
+ collisions, valid = vars.partition do |k, _|
116
+ facts.include?(k)
117
+ end
118
+ if collisions.any?
119
+ names = collisions.map { |k, _| "$#{k}" }.join(', ')
120
+ plural = collisions.length == 1 ? '' : 's'
121
+ Puppet.warning("#{type.capitalize} variable#{plural} #{names} will be overridden by fact#{plural} " \
122
+ "of the same name in the apply block")
123
+ end
124
+ valid.to_h
125
+ end
126
+
127
+ def build_program(code)
128
+ ast = Puppet::Pops::Serialization::FromDataConverter.convert(code)
129
+
130
+ # This will be a Program when running via `bolt apply`, but will
131
+ # only be a subset of the AST when compiling an apply block in a
132
+ # plan. In that case, we need to discover the definitions (which
133
+ # would ordinarily be stored on the Program) and construct a Program object.
134
+ if ast.is_a?(Puppet::Pops::Model::Program)
135
+ ast
136
+ else
137
+ # Node definitions must be at the top level of the apply block.
138
+ # That means the apply body either a) consists of just a
139
+ # NodeDefinition, b) consists of a BlockExpression which may
140
+ # contain NodeDefinitions, or c) doesn't contain NodeDefinitions.
141
+ definitions = if ast.is_a?(Puppet::Pops::Model::BlockExpression)
142
+ ast.statements.select { |st| st.is_a?(Puppet::Pops::Model::NodeDefinition) }
143
+ elsif ast.is_a?(Puppet::Pops::Model::NodeDefinition)
144
+ [ast]
145
+ else
146
+ []
147
+ end
148
+ Puppet::Pops::Model::Factory.PROGRAM(ast, definitions, ast.locator).model
149
+ end
150
+ end
141
151
  end
142
152
  end