bolt 2.11.1 → 2.16.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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +1 -1
  3. data/bolt-modules/boltlib/lib/puppet/datatypes/resourceinstance.rb +3 -2
  4. data/bolt-modules/boltlib/lib/puppet/functions/add_facts.rb +1 -0
  5. data/bolt-modules/boltlib/lib/puppet/functions/add_to_group.rb +1 -0
  6. data/bolt-modules/boltlib/lib/puppet/functions/apply_prep.rb +1 -1
  7. data/bolt-modules/boltlib/lib/puppet/functions/catch_errors.rb +1 -0
  8. data/bolt-modules/boltlib/lib/puppet/functions/facts.rb +1 -0
  9. data/bolt-modules/boltlib/lib/puppet/functions/fail_plan.rb +1 -0
  10. data/bolt-modules/boltlib/lib/puppet/functions/get_resources.rb +2 -1
  11. data/bolt-modules/boltlib/lib/puppet/functions/get_target.rb +1 -0
  12. data/bolt-modules/boltlib/lib/puppet/functions/get_targets.rb +1 -0
  13. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_fact.rb +1 -0
  14. data/bolt-modules/boltlib/lib/puppet/functions/puppetdb_query.rb +1 -0
  15. data/bolt-modules/boltlib/lib/puppet/functions/remove_from_group.rb +1 -0
  16. data/bolt-modules/boltlib/lib/puppet/functions/resolve_references.rb +1 -0
  17. data/bolt-modules/boltlib/lib/puppet/functions/resource.rb +53 -0
  18. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +1 -0
  19. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +67 -1
  20. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +1 -0
  21. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +6 -3
  22. data/bolt-modules/boltlib/lib/puppet/functions/run_task_with.rb +8 -2
  23. data/bolt-modules/boltlib/lib/puppet/functions/set_config.rb +1 -0
  24. data/bolt-modules/boltlib/lib/puppet/functions/set_feature.rb +1 -0
  25. data/bolt-modules/boltlib/lib/puppet/functions/set_resources.rb +66 -43
  26. data/bolt-modules/boltlib/lib/puppet/functions/set_var.rb +1 -0
  27. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -0
  28. data/bolt-modules/boltlib/lib/puppet/functions/vars.rb +1 -0
  29. data/bolt-modules/boltlib/lib/puppet/functions/wait_until_available.rb +1 -0
  30. data/bolt-modules/boltlib/lib/puppet/functions/without_default_logging.rb +1 -0
  31. data/bolt-modules/boltlib/lib/puppet/functions/write_file.rb +1 -0
  32. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/do_until.rb +2 -0
  33. data/bolt-modules/ctrl/lib/puppet/functions/ctrl/sleep.rb +2 -0
  34. data/bolt-modules/file/lib/puppet/functions/file/exists.rb +2 -1
  35. data/bolt-modules/file/lib/puppet/functions/file/join.rb +2 -0
  36. data/bolt-modules/file/lib/puppet/functions/file/read.rb +3 -1
  37. data/bolt-modules/file/lib/puppet/functions/file/readable.rb +3 -1
  38. data/bolt-modules/file/lib/puppet/functions/file/write.rb +2 -0
  39. data/bolt-modules/out/lib/puppet/functions/out/message.rb +2 -0
  40. data/bolt-modules/prompt/lib/puppet/functions/prompt.rb +1 -0
  41. data/bolt-modules/system/lib/puppet/functions/system/env.rb +2 -0
  42. data/lib/bolt/analytics.rb +21 -2
  43. data/lib/bolt/applicator.rb +20 -7
  44. data/lib/bolt/apply_inventory.rb +4 -0
  45. data/lib/bolt/apply_target.rb +4 -0
  46. data/lib/bolt/bolt_option_parser.rb +11 -10
  47. data/lib/bolt/catalog.rb +81 -68
  48. data/lib/bolt/cli.rb +18 -8
  49. data/lib/bolt/config.rb +152 -120
  50. data/lib/bolt/config/options.rb +321 -0
  51. data/lib/bolt/config/transport/base.rb +16 -16
  52. data/lib/bolt/config/transport/docker.rb +9 -23
  53. data/lib/bolt/config/transport/local.rb +6 -44
  54. data/lib/bolt/config/transport/options.rb +305 -0
  55. data/lib/bolt/config/transport/orch.rb +9 -18
  56. data/lib/bolt/config/transport/remote.rb +3 -6
  57. data/lib/bolt/config/transport/ssh.rb +59 -114
  58. data/lib/bolt/config/transport/winrm.rb +18 -47
  59. data/lib/bolt/executor.rb +14 -1
  60. data/lib/bolt/inventory/group.rb +1 -1
  61. data/lib/bolt/inventory/inventory.rb +4 -14
  62. data/lib/bolt/inventory/target.rb +22 -5
  63. data/lib/bolt/outputter.rb +3 -0
  64. data/lib/bolt/outputter/rainbow.rb +80 -0
  65. data/lib/bolt/pal.rb +6 -1
  66. data/lib/bolt/project.rb +66 -46
  67. data/lib/bolt/resource_instance.rb +10 -3
  68. data/lib/bolt/shell/bash.rb +9 -9
  69. data/lib/bolt/shell/powershell.rb +2 -1
  70. data/lib/bolt/shell/powershell/snippets.rb +8 -0
  71. data/lib/bolt/transport/docker.rb +1 -1
  72. data/lib/bolt/transport/local/connection.rb +2 -1
  73. data/lib/bolt/transport/ssh/connection.rb +35 -0
  74. data/lib/bolt/version.rb +1 -1
  75. data/lib/bolt_spec/bolt_context.rb +1 -1
  76. data/lib/bolt_spec/run.rb +1 -1
  77. metadata +23 -5
@@ -27,6 +27,7 @@ Puppet::Functions.create_function(:set_var) do
27
27
 
28
28
  inventory = Puppet.lookup(:bolt_inventory)
29
29
  executor = Puppet.lookup(:bolt_executor)
30
+ # Send Analytics Report
30
31
  executor.report_function_call(self.class.name)
31
32
 
32
33
  var_hash = { key => value }
@@ -67,6 +67,7 @@ Puppet::Functions.create_function(:upload_file, Puppet::Functions::InternalFunct
67
67
  executor = Puppet.lookup(:bolt_executor)
68
68
  inventory = Puppet.lookup(:bolt_inventory)
69
69
 
70
+ # Send Analytics Report
70
71
  executor.report_function_call(self.class.name)
71
72
 
72
73
  found = Puppet::Parser::Files.find_file(source, scope.compiler.environment)
@@ -21,6 +21,7 @@ Puppet::Functions.create_function(:vars) do
21
21
  inventory = Puppet.lookup(:bolt_inventory)
22
22
  # Bolt executor not expected when invoked from apply block
23
23
  executor = Puppet.lookup(:bolt_executor) { nil }
24
+ # Send Analytics Report
24
25
  executor&.report_function_call(self.class.name)
25
26
 
26
27
  inventory.vars(target)
@@ -33,6 +33,7 @@ Puppet::Functions.create_function(:wait_until_available) do
33
33
  executor = Puppet.lookup(:bolt_executor)
34
34
  inventory = Puppet.lookup(:bolt_inventory)
35
35
 
36
+ # Send Analytics Report
36
37
  executor.report_function_call(self.class.name)
37
38
 
38
39
  # Ensure that given targets are all Target instances
@@ -29,6 +29,7 @@ Puppet::Functions.create_function(:without_default_logging) do
29
29
  end
30
30
 
31
31
  executor = Puppet.lookup(:bolt_executor)
32
+ # Send Analytics Report
32
33
  executor.report_function_call(self.class.name)
33
34
 
34
35
  executor.without_default_logging do
@@ -31,6 +31,7 @@ Puppet::Functions.create_function(:write_file) do
31
31
  end
32
32
 
33
33
  executor = Puppet.lookup(:bolt_executor)
34
+ # Send Analytics Report
34
35
  executor.report_function_call(self.class.name)
35
36
 
36
37
  inventory = Puppet.lookup(:bolt_inventory)
@@ -20,7 +20,9 @@ Puppet::Functions.create_function(:'ctrl::do_until') do
20
20
  end
21
21
 
22
22
  def do_until(options = { 'limit' => 0 })
23
+ # Send Analytics Report
23
24
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
25
+
24
26
  limit = options['limit']
25
27
  i = 0
26
28
  until (x = yield)
@@ -11,7 +11,9 @@ Puppet::Functions.create_function(:'ctrl::sleep') do
11
11
  end
12
12
 
13
13
  def sleeper(period)
14
+ # Send Analytics Report
14
15
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
16
+
15
17
  sleep(period)
16
18
  nil
17
19
  end
@@ -9,7 +9,7 @@ Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFu
9
9
  # @example Check a file on disk
10
10
  # file::exists('/tmp/i_dumped_this_here')
11
11
  # @example check a file from the modulepath
12
- # file::exists('example/files/VERSION')
12
+ # file::exists('example/VERSION')
13
13
  dispatch :exists do
14
14
  scope_param
15
15
  required_param 'String', :filename
@@ -17,6 +17,7 @@ Puppet::Functions.create_function(:'file::exists', Puppet::Functions::InternalFu
17
17
  end
18
18
 
19
19
  def exists(scope, filename)
20
+ # Send Analytics Report
20
21
  Puppet.lookup(:bolt_executor) {}&.report_function_call(self.class.name)
21
22
  found = Puppet::Parser::Files.find_file(filename, scope.compiler.environment)
22
23
  found ? Puppet::FileSystem.exist?(found) : false
@@ -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
@@ -29,7 +29,7 @@ module Bolt
29
29
  def self.build_client
30
30
  logger = Logging.logger[self]
31
31
  begin
32
- config_file = File.expand_path('~/.puppetlabs/bolt/analytics.yaml')
32
+ config_file = config_path(logger)
33
33
  config = load_config(config_file, logger)
34
34
  rescue ArgumentError
35
35
  config = { 'disabled' => true }
@@ -51,6 +51,25 @@ module Bolt
51
51
  NoopClient.new
52
52
  end
53
53
 
54
+ def self.config_path(logger)
55
+ path = File.expand_path(File.join('~', '.puppetlabs', 'etc', 'bolt', 'analytics.yaml'))
56
+ old_path = File.expand_path(File.join('~', '.puppetlabs', 'bolt', 'analytics.yaml'))
57
+
58
+ if File.exist?(path)
59
+ if File.exist?(old_path)
60
+ message = "Detected analytics configuration files at '#{old_path}' and '#{path}'. Loading "\
61
+ "analytics configuration from '#{path}'."
62
+ logger.warn(message)
63
+ end
64
+
65
+ path
66
+ elsif File.exist?(old_path)
67
+ old_path
68
+ else
69
+ path
70
+ end
71
+ end
72
+
54
73
  def self.load_config(filename, logger)
55
74
  if File.exist?(filename)
56
75
  YAML.load_file(filename)
@@ -59,7 +78,7 @@ module Bolt
59
78
  logger.warn <<~ANALYTICS
60
79
  Bolt collects data about how you use it. You can opt out of providing this data.
61
80
 
62
- To disable analytics data collection, add this line to ~/.puppetlabs/bolt/analytics.yaml :
81
+ To disable analytics data collection, add this line to ~/.puppetlabs/etc/bolt/analytics.yaml :
63
82
  disabled: true
64
83
 
65
84
  Read more about what data Bolt collects and why here:
@@ -14,14 +14,16 @@ require 'open3'
14
14
 
15
15
  module Bolt
16
16
  class Applicator
17
- def initialize(inventory, executor, modulepath, plugin_dirs, pdb_client, hiera_config, max_compiles, apply_settings)
17
+ def initialize(inventory, executor, modulepath, plugin_dirs, project,
18
+ pdb_client, hiera_config, max_compiles, apply_settings)
18
19
  # lazy-load expensive gem code
19
20
  require 'concurrent'
20
21
 
21
22
  @inventory = inventory
22
23
  @executor = executor
23
- @modulepath = modulepath
24
+ @modulepath = modulepath || []
24
25
  @plugin_dirs = plugin_dirs
26
+ @project = project
25
27
  @pdb_client = pdb_client
26
28
  @hiera_config = hiera_config ? validate_hiera_config(hiera_config) : nil
27
29
  @apply_settings = apply_settings || {}
@@ -104,6 +106,16 @@ module Bolt
104
106
  out, err, stat = Open3.capture3('ruby', bolt_catalog_exe, 'compile', stdin_data: catalog_input.to_json)
105
107
  ENV['PATH'] = old_path
106
108
 
109
+ # If bolt_catalog does not return valid JSON, we should print stderr to
110
+ # see what happened
111
+ print_logs = stat.success?
112
+ result = begin
113
+ JSON.parse(out)
114
+ rescue JSON::ParserError
115
+ print_logs = true
116
+ { 'message' => "Something's gone terribly wrong! STDERR is logged." }
117
+ end
118
+
107
119
  # Any messages logged by Puppet will be on stderr as JSON hashes, so we
108
120
  # parse those and store them here. Any message on stderr that is not
109
121
  # properly JSON formatted is assumed to be an error message. If
@@ -117,17 +129,15 @@ module Bolt
117
129
  { 'level' => 'err', 'message' => line }
118
130
  end
119
131
 
120
- result = JSON.parse(out)
121
- if stat.success?
132
+ if print_logs
122
133
  logs.each do |log|
123
134
  bolt_level = Bolt::Util::PuppetLogLevel::MAPPING[log['level'].to_sym]
124
135
  message = log['message'].chomp
125
136
  @logger.send(bolt_level, "#{target.name}: #{message}")
126
137
  end
127
- result
128
- else
129
- raise ApplyError.new(target.name, result['message'])
130
138
  end
139
+ raise ApplyError.new(target.name, result['message']) unless stat.success?
140
+ result
131
141
  end
132
142
 
133
143
  def validate_hiera_config(hiera_config)
@@ -144,6 +154,7 @@ module Bolt
144
154
 
145
155
  def apply(args, apply_body, scope)
146
156
  raise(ArgumentError, 'apply requires a TargetSpec') if args.empty?
157
+ raise(ArgumentError, 'apply requires at least one statement in the apply block') if apply_body.nil?
147
158
  type0 = Puppet.lookup(:pal_script_compiler).type('TargetSpec')
148
159
  Puppet::Pal.assert_type(type0, args[0], 'apply targets')
149
160
 
@@ -185,9 +196,11 @@ module Bolt
185
196
  type_by_reference: true,
186
197
  local_reference: true)
187
198
 
199
+ bolt_project = @project if @project&.name
188
200
  scope = {
189
201
  code_ast: ast,
190
202
  modulepath: @modulepath,
203
+ project: bolt_project.to_h,
191
204
  pdb_config: @pdb_client.config.to_hash,
192
205
  hiera_config: @hiera_config,
193
206
  plan_vars: plan_vars,
@@ -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
@@ -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,7 +10,7 @@ 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],
13
+ global_config_setters: %w[modulepath project configfile],
14
14
  transports: %w[transport connect-timeout tty ssh-command copy-command],
15
15
  display: %w[format color verbose trace],
16
16
  global: %w[help version debug] }.freeze
@@ -20,7 +20,7 @@ module Bolt
20
20
  def get_help_text(subcommand, action = nil)
21
21
  case subcommand
22
22
  when 'apply'
23
- { flags: ACTION_OPTS + %w[noop execute compile-concurrency],
23
+ { flags: ACTION_OPTS + %w[noop execute compile-concurrency hiera-config],
24
24
  banner: APPLY_HELP }
25
25
  when 'command'
26
26
  case action
@@ -172,13 +172,14 @@ module Bolt
172
172
  apply
173
173
 
174
174
  USAGE
175
- bolt apply <manifest.pp> [options]
175
+ bolt apply [manifest.pp] [options]
176
176
 
177
177
  DESCRIPTION
178
178
  Apply Puppet manifest code on the specified targets.
179
179
 
180
180
  EXAMPLES
181
- bolt apply manifest.pp --targets target1,target2
181
+ bolt apply manifest.pp -t target
182
+ bolt apply -e "file { '/etc/puppetlabs': ensure => present }" -t target
182
183
  HELP
183
184
 
184
185
  COMMAND_HELP = <<~HELP
@@ -421,7 +422,7 @@ module Bolt
421
422
 
422
423
  ACTIONS
423
424
  generate-types Generate type references to register in plans
424
- install Install modules from a Puppetfile into a Boltdir
425
+ install Install modules from a Puppetfile into a project
425
426
  show-modules List modules available to the Bolt project
426
427
  HELP
427
428
 
@@ -444,7 +445,7 @@ module Bolt
444
445
  bolt puppetfile install [options]
445
446
 
446
447
  DESCRIPTION
447
- Install modules from a Puppetfile into a Boltdir
448
+ Install modules from a Puppetfile into a project
448
449
  HELP
449
450
 
450
451
  PUPPETFILE_SHOWMODULES_HELP = <<~HELP
@@ -708,13 +709,13 @@ module Bolt
708
709
  File.expand_path(moduledir)
709
710
  end
710
711
  end
711
- define('--boltdir FILEPATH',
712
- 'Specify what Boltdir to load config from (default: autodiscovered from current working dir)') do |path|
712
+ define('--project FILEPATH', '--boltdir FILEPATH',
713
+ 'Specify what project to load config from (default: autodiscovered from current working dir)') do |path|
713
714
  @options[:boltdir] = path
714
715
  end
715
716
  define('--configfile FILEPATH',
716
717
  'Specify where to load config from (default: ~/.puppetlabs/bolt/bolt.yaml).',
717
- '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|
718
719
  @options[:configfile] = path
719
720
  end
720
721
  define('--hiera-config FILEPATH',
@@ -730,7 +731,7 @@ module Bolt
730
731
  end
731
732
  define('--puppetfile FILEPATH',
732
733
  'Specify a Puppetfile to use when installing modules. (default: ~/.puppetlabs/bolt/Puppetfile)',
733
- 'Modules are installed in the current Boltdir.') do |path|
734
+ 'Modules are installed in the current project.') do |path|
734
735
  @options[:puppetfile_path] = Pathname.new(File.expand_path(path))
735
736
  end
736
737
  define('--[no-]save-rerun', 'Whether to update the rerun file after this command.') do |save|
@@ -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,80 +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
- with_puppet_settings(request['hiera_config']) do
62
- Puppet[:rich_data] = true
63
- Puppet[:node_name_value] = target['name']
64
- env_conf = { modulepath: request['modulepath'] || [],
65
- facts: target['facts'] || {} }
66
- env_conf[:variables] = {}
60
+ project = request['project'] || {}
61
+ bolt_project = Struct.new(:name, :path).new(project['name'], project['path']) unless project.empty?
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
67
90
  Puppet::Pal.in_tmp_environment('bolt_catalog', env_conf) do |pal|
68
- inv = Bolt::ApplyInventory.new(request['config'])
69
- Puppet.override(bolt_pdb_client: pdb_client,
70
- bolt_inventory: inv) do
91
+ Puppet.override(puppet_overrides) do
71
92
  Puppet.lookup(:pal_current_node).trusted_data = target['trusted']
72
93
  pal.with_catalog_compiler do |compiler|
73
- # Deserializing needs to happen inside the catalog compiler so
74
- # loaders are initialized for loading
75
- plan_vars = Puppet::Pops::Serialization::FromDataConverter.convert(request['plan_vars'])
76
-
77
- # Facts will be set by the catalog compiler, so we need to ensure
78
- # that any plan or target variables with the same name are not
79
- # passed into the apply block to avoid a redefinition error.
80
- # Filter out plan and target vars separately and raise a Puppet
81
- # warning if there are any collisions for either. Puppet warning
82
- # is the only way to log a message that will make it back to Bolt
83
- # to be printed.
84
- pv_collisions, pv_filtered = plan_vars.partition do |k, _|
85
- target['facts'].keys.include?(k)
86
- end.map(&:to_h)
87
- unless pv_collisions.empty?
88
- print_pv = pv_collisions.keys.map { |k| "$#{k}" }.join(', ')
89
- plural = pv_collisions.keys.length == 1 ? '' : 's'
90
- Puppet.warning("Plan variable#{plural} #{print_pv} will be overridden by fact#{plural} " \
91
- "of the same name in the apply block")
92
- end
93
-
94
- tv_collisions, tv_filtered = target['variables'].partition do |k, _|
95
- target['facts'].keys.include?(k)
96
- end.map(&:to_h)
97
- unless tv_collisions.empty?
98
- print_tv = tv_collisions.keys.map { |k| "$#{k}" }.join(', ')
99
- plural = tv_collisions.keys.length == 1 ? '' : 's'
100
- Puppet.warning("Target variable#{plural} #{print_tv} " \
101
- "will be overridden by fact#{plural} of the same name in the apply block")
102
- end
103
-
104
- pal.send(:add_variables, compiler.send(:topscope), tv_filtered.merge(pv_filtered))
105
-
94
+ options = request['puppet_config'] || {}
106
95
  # Configure language strictness in the CatalogCompiler. We want Bolt to be able
107
96
  # to compile most Puppet 4+ manifests, so we default to allowing deprecated functions.
108
97
  Puppet[:strict] = options['strict'] || :warning
109
98
  Puppet[:strict_variables] = options['strict_variables'] || false
110
- ast = Puppet::Pops::Serialization::FromDataConverter.convert(pal_main)
111
- # This will be a Program when running via `bolt apply`, but will
112
- # only be a subset of the AST when compiling an apply block in a
113
- # plan. In that case, we need to discover the definitions (which
114
- # would ordinarily be stored on the Program) and construct a Program object.
115
- unless ast.is_a?(Puppet::Pops::Model::Program)
116
- # Node definitions must be at the top level of the apply block.
117
- # That means the apply body either a) consists of just a
118
- # NodeDefinition, b) consists of a BlockExpression which may
119
- # contain NodeDefinitions, or c) doesn't contain NodeDefinitions.
120
- definitions = if ast.is_a?(Puppet::Pops::Model::BlockExpression)
121
- ast.statements.select { |st| st.is_a?(Puppet::Pops::Model::NodeDefinition) }
122
- elsif ast.is_a?(Puppet::Pops::Model::NodeDefinition)
123
- [ast]
124
- else
125
- []
126
- end
127
- ast = Puppet::Pops::Model::Factory.PROGRAM(ast, definitions, ast.locator).model
128
- end
99
+
100
+ pal_main = request['code_ast'] || request['code_string']
101
+ ast = build_program(pal_main)
129
102
  compiler.evaluate(ast)
130
- compiler.instance_variable_get(:@internal_compiler).send(:evaluate_ast_node)
103
+ compiler.evaluate_ast_node
131
104
  compiler.compile_additions
132
105
  compiler.with_json_encoding(&:encode)
133
106
  end
@@ -135,5 +108,45 @@ module Bolt
135
108
  end
136
109
  end
137
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
138
151
  end
139
152
  end