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
@@ -115,7 +115,7 @@ module Bolt
115
115
  Bolt::Config.from_file(options[:configfile], options)
116
116
  else
117
117
  project = if options[:boltdir]
118
- Bolt::Project.new(options[:boltdir])
118
+ Bolt::Project.create_project(options[:boltdir])
119
119
  else
120
120
  Bolt::Project.find_boltdir(Dir.pwd)
121
121
  end
@@ -219,7 +219,7 @@ module Bolt
219
219
  end
220
220
 
221
221
  if options[:boltdir] && options[:configfile]
222
- raise Bolt::CLIError, "Only one of '--boltdir' or '--configfile' may be specified"
222
+ raise Bolt::CLIError, "Only one of '--boltdir', '--project', or '--configfile' may be specified"
223
223
  end
224
224
 
225
225
  if options[:noop] &&
@@ -392,7 +392,7 @@ module Bolt
392
392
  end
393
393
  code = apply_manifest(options[:code], options[:targets], options[:object], options[:noop])
394
394
  else
395
- executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop])
395
+ executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
396
396
  targets = options[:targets]
397
397
 
398
398
  results = nil
@@ -512,8 +512,8 @@ module Bolt
512
512
  params: plan_arguments }
513
513
  plan_context[:description] = options[:description] if options[:description]
514
514
 
515
- executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop])
516
- if options.fetch(:format, 'human') == 'human'
515
+ executor = Bolt::Executor.new(config.concurrency, analytics, options[:noop], config.modified_concurrency)
516
+ if %w[human rainbow].include?(options.fetch(:format, 'human'))
517
517
  executor.subscribe(outputter)
518
518
  else
519
519
  # Only subscribe to out::message events for JSON outputter
@@ -537,7 +537,18 @@ module Bolt
537
537
  Puppet[:tasks] = false
538
538
  ast = pal.parse_manifest(code, filename)
539
539
 
540
- executor = Bolt::Executor.new(config.concurrency, analytics, noop)
540
+ if defined?(ast.body) &&
541
+ (ast.body.is_a?(Puppet::Pops::Model::HostClassDefinition) ||
542
+ ast.body.is_a?(Puppet::Pops::Model::ResourceTypeDefinition))
543
+ message = "Manifest only contains definitions and will result in no changes on the targets. "\
544
+ "Definitions must be declared for their resources to be applied. You can read more "\
545
+ "about defining and declaring classes and types in the Puppet documentation at "\
546
+ "https://puppet.com/docs/puppet/latest/lang_classes.html and "\
547
+ "https://puppet.com/docs/puppet/latest/lang_defined_types.html"
548
+ @logger.warn(message)
549
+ end
550
+
551
+ executor = Bolt::Executor.new(config.concurrency, analytics, noop, config.modified_concurrency)
541
552
  executor.subscribe(outputter) if options.fetch(:format, 'human') == 'human'
542
553
  executor.subscribe(log_outputter)
543
554
  # apply logging looks like plan logging, so tell the outputter we're in a
@@ -760,14 +771,13 @@ module Bolt
760
771
  end
761
772
 
762
773
  def pal
763
- project = config.project.load_as_module? ? config.project : nil
764
774
  @pal ||= Bolt::PAL.new(config.modulepath,
765
775
  config.hiera_config,
766
776
  config.project.resource_types,
767
777
  config.compile_concurrency,
768
778
  config.trusted_external,
769
779
  config.apply_settings,
770
- project)
780
+ config.project)
771
781
  end
772
782
 
773
783
  def convert_plan(plan)
@@ -13,6 +13,7 @@ require 'bolt/config/transport/orch'
13
13
  require 'bolt/config/transport/local'
14
14
  require 'bolt/config/transport/docker'
15
15
  require 'bolt/config/transport/remote'
16
+ require 'bolt/config/options'
16
17
 
17
18
  module Bolt
18
19
  class UnknownTransportError < Bolt::Error
@@ -23,8 +24,15 @@ module Bolt
23
24
  end
24
25
 
25
26
  class Config
26
- attr_reader :config_files, :warnings, :data, :transports, :project
27
+ include Bolt::Config::Options
27
28
 
29
+ attr_reader :config_files, :warnings, :data, :transports, :project, :modified_concurrency
30
+
31
+ BOLT_CONFIG_NAME = 'bolt.yaml'
32
+ BOLT_DEFAULTS_NAME = 'bolt-defaults.yaml'
33
+
34
+ # Transport config classes. Used to load default transport config which
35
+ # gets passed along to the inventory.
28
36
  TRANSPORT_CONFIG = {
29
37
  'ssh' => Bolt::Config::Transport::SSH,
30
38
  'winrm' => Bolt::Config::Transport::WinRM,
@@ -34,133 +42,158 @@ module Bolt
34
42
  'remote' => Bolt::Config::Transport::Remote
35
43
  }.freeze
36
44
 
37
- # NOTE: All configuration options should have a corresponding schema property
38
- # in schemas/bolt-config.schema.json
39
- OPTIONS = {
40
- "apply_settings" => "A map of Puppet settings to use when applying Puppet code",
41
- "color" => "Whether to use colored output when printing messages to the console.",
42
- "compile-concurrency" => "The maximum number of simultaneous manifest block compiles.",
43
- "concurrency" => "The number of threads to use when executing on remote targets.",
44
- "format" => "The format to use when printing results. Options are `human` and `json`.",
45
- "hiera-config" => "The path to your Hiera config.",
46
- "inventoryfile" => "The path to a structured data inventory file used to refer to groups of "\
47
- "targets on the command line and from plans.",
48
- "log" => "The configuration of the logfile output. Configuration can be set for "\
49
- "`console` and the path to a log file, such as `~/.puppetlabs/bolt/debug.log`.",
50
- "modulepath" => "The module path for loading tasks and plan code. This is either an array "\
51
- "of directories or a string containing a list of directories separated by the "\
52
- "OS-specific PATH separator.",
53
- "plugin_hooks" => "Which plugins a specific hook should use.",
54
- "plugins" => "A map of plugins and their configuration data.",
55
- "puppetdb" => "A map containing options for configuring the Bolt PuppetDB client.",
56
- "puppetfile" => "A map containing options for the `bolt puppetfile install` command.",
57
- "save-rerun" => "Whether to update `.rerun.json` in the Bolt project directory. If "\
58
- "your target names include passwords, set this value to `false` to avoid "\
59
- "writing passwords to disk.",
60
- "transport" => "The default transport to use when the transport for a target is not "\
61
- "specified in the URL or inventory.",
62
- "trusted-external-command" => "The path to an executable on the Bolt controller that can produce "\
63
- "external trusted facts. **External trusted facts are experimental in both "\
64
- "Puppet and Bolt and this API may change or be removed.**"
65
- }.freeze
66
-
67
- DEFAULT_OPTIONS = {
68
- "color" => true,
69
- "compile-concurrency" => "Number of cores",
70
- "concurrency" => "100 or one-third of the ulimit, whichever is lower",
71
- "format" => "human",
72
- "hiera-config" => "Boltdir/hiera.yaml",
73
- "inventoryfile" => "Boltdir/inventory.yaml",
74
- "modulepath" => ["Boltdir/modules", "Boltdir/site-modules", "Boltdir/site"],
75
- "save-rerun" => true
76
- }.freeze
77
-
78
- PUPPETFILE_OPTIONS = {
79
- "forge" => "A subsection that can have its own `proxy` setting to set an HTTP proxy for Forge operations "\
80
- "only, and a `baseurl` setting to specify a different Forge host.",
81
- "proxy" => "The HTTP proxy to use for Git and Forge operations."
82
- }.freeze
83
-
84
- LOG_OPTIONS = {
85
- "append" => "Add output to an existing log file. Available only for logs output to a "\
86
- "filepath.",
87
- "level" => "The type of information in the log. Either `debug`, `info`, `notice`, "\
88
- "`warn`, or `error`."
89
- }.freeze
90
-
91
- DEFAULT_LOG_OPTIONS = {
92
- "append" => true,
93
- "level" => "`warn` for console, `notice` for file"
94
- }.freeze
95
-
96
- APPLY_SETTINGS = {
97
- "show_diff" => "Whether to log and report a contextual diff when files are being replaced. "\
98
- "See [Puppet documentation](https://puppet.com/docs/puppet/latest/configuration.html#showdiff) "\
99
- "for details"
100
- }.freeze
101
-
102
- DEFAULT_APPLY_SETTINGS = {
103
- "show_diff" => false
104
- }.freeze
105
-
45
+ # The default concurrency value that is used when the ulimit is not low (i.e. < 700)
106
46
  DEFAULT_DEFAULT_CONCURRENCY = 100
107
47
 
108
48
  def self.default
109
- new(Bolt::Project.new('.'), {})
49
+ new(Bolt::Project.create_project('.'), {})
110
50
  end
111
51
 
112
52
  def self.from_project(project, overrides = {})
113
- data = {
114
- filepath: project.config_file,
115
- data: Bolt::Util.read_optional_yaml_hash(project.config_file, 'config')
116
- }
53
+ conf = if project.project_file == project.config_file
54
+ project.data
55
+ else
56
+ Bolt::Util.read_optional_yaml_hash(project.config_file, 'config')
57
+ end
117
58
 
118
- data = load_defaults.push(data).select { |config| config[:data]&.any? }
59
+ data = load_defaults(project).push(
60
+ filepath: project.config_file,
61
+ data: conf,
62
+ warnings: []
63
+ )
119
64
 
120
65
  new(project, data, overrides)
121
66
  end
122
67
 
123
68
  def self.from_file(configfile, overrides = {})
124
- project = Bolt::Project.new(Pathname.new(configfile).expand_path.dirname)
69
+ project = Bolt::Project.create_project(Pathname.new(configfile).expand_path.dirname)
70
+
71
+ conf = if project.project_file == project.config_file
72
+ project.data
73
+ else
74
+ Bolt::Util.read_yaml_hash(configfile, 'config')
75
+ end
125
76
 
126
- data = {
77
+ data = load_defaults(project).push(
127
78
  filepath: project.config_file,
128
- data: Bolt::Util.read_yaml_hash(configfile, 'config')
129
- }
130
- data = load_defaults.push(data).select { |config| config[:data]&.any? }
79
+ data: conf,
80
+ warnings: []
81
+ )
131
82
 
132
83
  new(project, data, overrides)
133
84
  end
134
85
 
135
- def self.load_defaults
86
+ def self.system_path
136
87
  # Lazy-load expensive gem code
137
88
  require 'win32/dir' if Bolt::Util.windows?
138
89
 
139
- system_path = if Bolt::Util.windows?
140
- Pathname.new(File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'bolt', 'etc', 'bolt.yaml'))
141
- else
142
- Pathname.new(File.join('/etc', 'puppetlabs', 'bolt', 'bolt.yaml'))
143
- end
144
- user_path = begin
145
- Pathname.new(File.expand_path(File.join('~', '.puppetlabs', 'etc', 'bolt', 'bolt.yaml')))
146
- rescue ArgumentError
147
- nil
148
- end
149
-
150
- confs = [{ filepath: system_path, data: Bolt::Util.read_optional_yaml_hash(system_path, 'config') }]
151
- confs << { filepath: user_path, data: Bolt::Util.read_optional_yaml_hash(user_path, 'config') } if user_path
90
+ if Bolt::Util.windows?
91
+ Pathname.new(File.join(Dir::COMMON_APPDATA, 'PuppetLabs', 'bolt', 'etc'))
92
+ else
93
+ Pathname.new(File.join('/etc', 'puppetlabs', 'bolt'))
94
+ end
95
+ end
96
+
97
+ def self.user_path
98
+ Pathname.new(File.expand_path(File.join('~', '.puppetlabs', 'etc', 'bolt')))
99
+ rescue StandardError
100
+ nil
101
+ end
102
+
103
+ # Loads a 'bolt-defaults.yaml' file, which contains default configuration that applies to all
104
+ # projects. This file does not allow project-specific configuration such as 'hiera-config' and
105
+ # 'inventoryfile', and nests all default inventory configuration under an 'inventory-config' key.
106
+ def self.load_bolt_defaults_yaml(dir)
107
+ filepath = dir + BOLT_DEFAULTS_NAME
108
+ data = Bolt::Util.read_yaml_hash(filepath, 'config')
109
+ warnings = []
110
+
111
+ # Warn if 'bolt.yaml' detected in same directory.
112
+ if File.exist?(bolt_yaml = dir + BOLT_CONFIG_NAME)
113
+ warnings.push(
114
+ msg: "Detected multiple configuration files: ['#{bolt_yaml}', '#{filepath}']. '#{bolt_yaml}' "\
115
+ "will be ignored."
116
+ )
117
+ end
118
+
119
+ # Remove project-specific config such as hiera-config, etc.
120
+ project_config = data.slice(*(BOLT_PROJECT_OPTIONS - BOLT_DEFAULTS_OPTIONS))
121
+
122
+ if project_config.any?
123
+ data.reject! { |key, _| project_config.include?(key) }
124
+ warnings.push(
125
+ msg: "Unsupported project configuration detected in '#{filepath}': #{project_config.keys}. "\
126
+ "Project configuration should be set in 'bolt-project.yaml'."
127
+ )
128
+ end
129
+
130
+ # Remove top-level transport config such as transport, ssh, etc.
131
+ transport_config = data.slice(*INVENTORY_OPTIONS.keys)
132
+
133
+ if transport_config.any?
134
+ data.reject! { |key, _| transport_config.include?(key) }
135
+ warnings.push(
136
+ msg: "Unsupported inventory configuration detected in '#{filepath}': #{transport_config.keys}. "\
137
+ "Transport configuration should be set under the 'inventory-config' option or "\
138
+ "in 'inventory.yaml'."
139
+ )
140
+ end
141
+
142
+ # Move data under transport-config to top-level so it can be easily merged with
143
+ # config from other sources.
144
+ if data.key?('inventory-config')
145
+ data = data.merge(data.delete('inventory-config'))
146
+ end
147
+
148
+ { filepath: filepath, data: data, warnings: warnings }
149
+ end
150
+
151
+ # Loads a 'bolt.yaml' file, the legacy configuration file. There's no special munging needed
152
+ # here since Bolt::Config will just ignore any invalid keys.
153
+ def self.load_bolt_yaml(dir)
154
+ filepath = dir + BOLT_CONFIG_NAME
155
+ data = Bolt::Util.read_yaml_hash(filepath, 'config')
156
+ warnings = [msg: "Configuration file #{filepath} is deprecated and will be removed in a future version "\
157
+ "of Bolt. Use '#{dir + BOLT_DEFAULTS_NAME}' instead."]
158
+
159
+ { filepath: filepath, data: data, warnings: warnings }
160
+ end
161
+
162
+ def self.load_defaults(project)
163
+ confs = []
164
+
165
+ # Load system-level config. Prefer a 'bolt-defaults.yaml' file, but fall back to the
166
+ # legacy 'bolt.yaml' file. If the project-level config file is also the system-level
167
+ # config file, don't load it a second time.
168
+ if File.exist?(system_path + BOLT_DEFAULTS_NAME)
169
+ confs << load_bolt_defaults_yaml(system_path)
170
+ elsif File.exist?(system_path + BOLT_CONFIG_NAME) &&
171
+ (system_path + BOLT_CONFIG_NAME) != project.config_file
172
+ confs << load_bolt_yaml(system_path)
173
+ end
174
+
175
+ # Load user-level config if there is a homedir. Prefer a 'bolt-defaults.yaml' file, but
176
+ # fall back to the legacy 'bolt.yaml' file.
177
+ if user_path
178
+ if File.exist?(user_path + BOLT_DEFAULTS_NAME)
179
+ confs << load_bolt_defaults_yaml(user_path)
180
+ elsif File.exist?(user_path + BOLT_CONFIG_NAME)
181
+ confs << load_bolt_yaml(user_path)
182
+ end
183
+ end
184
+
152
185
  confs
153
186
  end
154
187
 
155
188
  def initialize(project, config_data, overrides = {})
156
189
  unless config_data.is_a?(Array)
157
- config_data = [{ filepath: project.config_file, data: config_data }]
190
+ config_data = [{ filepath: project.config_file, data: config_data, warnings: [] }]
158
191
  end
159
192
 
160
- @logger = Logging.logger[self]
161
- @warnings = []
162
- @project = project
163
- @transports = {}
193
+ @logger = Logging.logger[self]
194
+ @project = project
195
+ @warnings = @project.warnings.dup
196
+ @transports = {}
164
197
  @config_files = []
165
198
 
166
199
  default_data = {
@@ -178,24 +211,22 @@ module Bolt
178
211
  'transport' => 'ssh'
179
212
  }
180
213
 
181
- loaded_data = config_data.map do |config|
182
- @config_files.push(config[:filepath])
183
- config[:data]
214
+ loaded_data = config_data.each_with_object([]) do |data, acc|
215
+ @warnings.concat(data[:warnings]) if data[:warnings].any?
216
+
217
+ if data[:data].any?
218
+ @config_files.push(data[:filepath])
219
+ acc.push(data[:data])
220
+ end
184
221
  end
185
222
 
186
223
  override_data = normalize_overrides(overrides)
187
224
 
188
225
  # If we need to lower concurrency and concurrency is not configured
189
226
  ld_concurrency = loaded_data.map(&:keys).flatten.include?('concurrency')
190
- if default_concurrency != DEFAULT_DEFAULT_CONCURRENCY &&
191
- !ld_concurrency &&
192
- !override_data.key?('concurrency')
193
- concurrency_warning = { option: 'concurrency',
194
- msg: "Concurrency will default to #{default_concurrency} because ulimit "\
195
- "is low: #{Etc.sysconf(Etc::SC_OPEN_MAX)}. Set concurrency with "\
196
- "'--concurrency', or set your ulimit with 'ulimit -n <limit>'" }
197
- @warnings << concurrency_warning
198
- end
227
+ @modified_concurrency = default_concurrency != DEFAULT_DEFAULT_CONCURRENCY &&
228
+ !ld_concurrency &&
229
+ !override_data.key?('concurrency')
199
230
 
200
231
  @data = merge_config_layers(default_data, *loaded_data, override_data)
201
232
 
@@ -212,12 +243,13 @@ module Bolt
212
243
  def normalize_overrides(options)
213
244
  opts = options.transform_keys(&:to_s)
214
245
 
215
- # Pull out config options
216
- overrides = opts.slice(*OPTIONS.keys)
246
+ # Pull out config options. We need to add 'transport' as it's not part of the
247
+ # OPTIONS hash but is a valid option that can be set with the --transport CLI option
248
+ overrides = opts.slice(*OPTIONS.keys, 'transport')
217
249
 
218
250
  # Pull out transport config options
219
251
  TRANSPORT_CONFIG.each do |transport, config|
220
- overrides[transport] = opts.slice(*config.options.keys)
252
+ overrides[transport] = opts.slice(*config.options)
221
253
  end
222
254
 
223
255
  # Set console log to debug if in debug mode
@@ -284,8 +316,8 @@ module Bolt
284
316
  end
285
317
 
286
318
  # Filter hashes to only include valid options
287
- @data['apply_settings'] = @data['apply_settings'].slice(*APPLY_SETTINGS.keys)
288
- @data['puppetfile'] = @data['puppetfile'].slice(*PUPPETFILE_OPTIONS.keys)
319
+ @data['apply_settings'] = @data['apply_settings'].slice(*SUBOPTIONS['apply_settings'].keys)
320
+ @data['puppetfile'] = @data['puppetfile'].slice(*SUBOPTIONS['puppetfile'].keys)
289
321
  end
290
322
 
291
323
  private def normalize_log(target)
@@ -299,7 +331,7 @@ module Bolt
299
331
  next unless val.is_a?(Hash)
300
332
 
301
333
  name = normalize_log(key)
302
- acc[name] = val.slice(*LOG_OPTIONS.keys)
334
+ acc[name] = val.slice(*SUBOPTIONS['log'].keys)
303
335
  .transform_keys(&:to_sym)
304
336
 
305
337
  if (v = acc[name][:level])
@@ -351,7 +383,7 @@ module Bolt
351
383
  raise Bolt::ValidationError, "Compilation is CPU-intensive, set concurrency less than #{compile_limit}"
352
384
  end
353
385
 
354
- unless %w[human json].include? format
386
+ if (format == 'rainbow' && Bolt::Util.windows?) || !(%w[human json rainbow].include? format)
355
387
  raise Bolt::ValidationError, "Unsupported format: '#{format}'"
356
388
  end
357
389
 
@@ -483,7 +515,7 @@ module Bolt
483
515
  @default_concurrency ||= if !sc_open_max_available? || Etc.sysconf(Etc::SC_OPEN_MAX) >= 300
484
516
  DEFAULT_DEFAULT_CONCURRENCY
485
517
  else
486
- Etc.sysconf(Etc::SC_OPEN_MAX) / 3
518
+ Etc.sysconf(Etc::SC_OPEN_MAX) / 7
487
519
  end
488
520
  end
489
521
  end
@@ -0,0 +1,321 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bolt
4
+ class Config
5
+ module Options
6
+ # The following constants define the various configuration options available to Bolt.
7
+ # Each constant is a hash where keys are the configuration option and values are the
8
+ # data describing the option. Data includes the following keys:
9
+ # :def The **documented** default value. This is the value that is displayed
10
+ # in the reference docs and is not used by Bolt to actually set a default
11
+ # value.
12
+ # :desc The text description of the option that is displayed in documentation.
13
+ # :exmp An example value for the option. This is used to generate an example
14
+ # configuration file in the reference docs.
15
+ # :type The option's expected type. If an option accepts multiple types, this is
16
+ # an array of the accepted types. Any options that accept a Boolean value
17
+ # should use the [TrueClass, FalseClass] type.
18
+ OPTIONS = {
19
+ "apply_settings" => {
20
+ desc: "A map of Puppet settings to use when applying Puppet code using the `apply` "\
21
+ "plan function or the `bolt apply` command.",
22
+ type: Hash
23
+ },
24
+ "color" => {
25
+ desc: "Whether to use colored output when printing messages to the console.",
26
+ type: [TrueClass, FalseClass],
27
+ exmp: false,
28
+ def: true
29
+ },
30
+ "compile-concurrency" => {
31
+ desc: "The maximum number of simultaneous manifest block compiles.",
32
+ type: Integer,
33
+ exmp: 4,
34
+ def: "Number of cores."
35
+ },
36
+ "concurrency" => {
37
+ desc: "The number of threads to use when executing on remote targets.",
38
+ type: Integer,
39
+ exmp: 50,
40
+ def: "100 or 1/7 the ulimit, whichever is lower."
41
+ },
42
+ "format" => {
43
+ desc: "The format to use when printing results. Options are `human`, `json`, and `rainbow`.",
44
+ type: String,
45
+ exmp: "json",
46
+ def: "human"
47
+ },
48
+ "hiera-config" => {
49
+ desc: "The path to your Hiera config.",
50
+ type: String,
51
+ def: "project/hiera.yaml",
52
+ exmp: "~/.puppetlabs/bolt/hiera.yaml"
53
+ },
54
+ "inventory-config" => {
55
+ desc: "A map of default configuration options for the inventory. This includes options "\
56
+ "for setting the default transport to use when connecting to targets, as well as "\
57
+ "options for configuring the default behavior of each transport.",
58
+ type: Hash
59
+ },
60
+ "inventoryfile" => {
61
+ desc: "The path to a structured data inventory file used to refer to groups of targets on the command "\
62
+ "line and from plans. Read more about using inventory files in [Inventory "\
63
+ "files](inventory_file_v2.md).",
64
+ type: String,
65
+ def: "project/inventory.yaml",
66
+ exmp: "~/.puppetlabs/bolt/inventory.yaml"
67
+ },
68
+ "log" => {
69
+ desc: "A map of configuration for the logfile output. Configuration can be set for "\
70
+ "`console` and individual log files, such as `~/.puppetlabs/bolt/debug.log`. "\
71
+ "Each key in the map is the logfile output to configure, with the corresponding "\
72
+ "value configuring the logfile output.",
73
+ type: Hash,
74
+ exmp: { "console" => { "level" => "info" },
75
+ "~/logs/debug.log" => { "append" => false, "level" => "debug" } }
76
+ },
77
+ "modulepath" => {
78
+ desc: "An array of directories that Bolt loads content such as plans and tasks from. Read more "\
79
+ "about modules in [Module structure](module_structure.md).",
80
+ type: [Array, String],
81
+ def: ["project/modules", "project/site-modules", "project/site"],
82
+ exmp: ["~/.puppetlabs/bolt/modules", "~/.puppetlabs/bolt/site-modules"]
83
+ },
84
+ "name" => {
85
+ desc: "The name of the Bolt project. When this option is configured, the project is considered a "\
86
+ "[Bolt project](experimental_features.md#bolt-projects), allowing Bolt to load content from "\
87
+ "the project directory as though it were a module.",
88
+ type: String,
89
+ exmp: "myproject"
90
+ },
91
+ "plans" => {
92
+ desc: "A list of plan names to show in `bolt plan show` output, if they exist. This option is used to "\
93
+ "limit the visibility of plans for users of the project. For example, project authors might want to "\
94
+ "limit the visibility of plans that are bundled with Bolt or plans that should only be run as "\
95
+ "part of another plan. When this option is not configured, all plans are visible. This "\
96
+ "option does not prevent users from running plans that are not part of this list.",
97
+ type: Array,
98
+ exmp: ["myproject", "myproject::foo", "myproject::bar"]
99
+ },
100
+ "plugin_hooks" => {
101
+ desc: "A map of [plugin hooks](writing_plugins.md#hooks) and which plugins a hook should use. "\
102
+ "The only configurable plugin hook is `puppet_library`, which can use two possible plugins: "\
103
+ "[`puppet_agent`](https://github.com/puppetlabs/puppetlabs-puppet_agent#puppet_agentinstall) "\
104
+ "and [`task`](using_plugins.md#task).",
105
+ type: Hash,
106
+ exmp: { "puppet_library" => { "plugin" => "puppet_agent", "version" => "6.15.0", "_run_as" => "root" } }
107
+ },
108
+ "plugins" => {
109
+ desc: "A map of plugins and their configuration data, where each key is the name of a plugin and its "\
110
+ "value is a map of configuration data. Configurable options are specified by the plugin. Read "\
111
+ "more about configuring plugins in [Using plugins](using_plugins.md#configuring-plugins).",
112
+ type: Hash,
113
+ exmp: { "pkcs7" => { "keysize" => 1024 } }
114
+ },
115
+ "puppetdb" => {
116
+ desc: "A map containing options for [configuring the Bolt PuppetDB client](bolt_connect_puppetdb.md).",
117
+ type: Hash
118
+ },
119
+ "puppetfile" => {
120
+ desc: "A map containing options for the `bolt puppetfile install` command.",
121
+ type: Hash
122
+ },
123
+ "save-rerun" => {
124
+ desc: "Whether to update `.rerun.json` in the Bolt project directory. If "\
125
+ "your target names include passwords, set this value to `false` to avoid "\
126
+ "writing passwords to disk.",
127
+ type: [TrueClass, FalseClass],
128
+ exmp: false,
129
+ def: true
130
+ },
131
+ "tasks" => {
132
+ desc: "A list of task names to show in `bolt task show` output, if they exist. This option is used to "\
133
+ "limit the visibility of tasks for users of the project. For example, project authors might want to "\
134
+ "limit the visibility of tasks that are bundled with Bolt or plans that should only be run as "\
135
+ "part of a larger workflow. When this option is not configured, all tasks are visible. This "\
136
+ "option does not prevent users from running tasks that are not part of this list.",
137
+ type: Array,
138
+ exmp: ["myproject", "myproject::foo", "myproject::bar"]
139
+ },
140
+ "trusted-external-command" => {
141
+ desc: "The path to an executable on the Bolt controller that can produce external trusted facts. "\
142
+ "**External trusted facts are experimental in both Puppet and Bolt and this API may change or "\
143
+ "be removed.**",
144
+ type: String,
145
+ exmp: "/etc/puppetlabs/facts/trusted_external.sh"
146
+ }
147
+ }.freeze
148
+
149
+ # Options that configure the inventory, specifically the default transport
150
+ # used by targets and the transports themselves. These options are used in
151
+ # bolt.yaml, under a 'config' key in inventory.yaml, and under the
152
+ # 'inventory-config' key in bolt-defaults.yaml.
153
+ INVENTORY_OPTIONS = {
154
+ "transport" => {
155
+ desc: "The default transport to use when the transport for a target is not "\
156
+ "specified in the URI.",
157
+ type: String,
158
+ def: "ssh",
159
+ exmp: "winrm"
160
+ },
161
+ "docker" => {
162
+ desc: "A map of configuration options for the docker transport.",
163
+ type: Hash,
164
+ exmp: { "cleanup" => false, "service-url" => "https://docker.example.com" }
165
+ },
166
+ "local" => {
167
+ desc: "A map of configuration options for the local transport. The set of available options is "\
168
+ "platform dependent.",
169
+ type: Hash,
170
+ exmp: { "cleanup" => false, "tmpdir" => "/tmp/bolt" }
171
+ },
172
+ "pcp" => {
173
+ desc: "A map of configuration options for the pcp transport.",
174
+ type: Hash,
175
+ exmp: { "job-poll-interval" => 15, "job-poll-timeout" => 30 }
176
+ },
177
+ "remote" => {
178
+ desc: "A map of configuration options for the remote transport.",
179
+ type: Hash,
180
+ exmp: { "run-on" => "proxy_target" }
181
+ },
182
+ "ssh" => {
183
+ desc: "A map of configuration options for the ssh transport.",
184
+ type: Hash,
185
+ exmp: { "password" => "hunter2!", "user" => "bolt" }
186
+ },
187
+ "winrm" => {
188
+ desc: "A map of configuration options for the winrm transport.",
189
+ type: Hash,
190
+ exmp: { "password" => "hunter2!", "user" => "bolt" }
191
+ }
192
+ }.freeze
193
+
194
+ # Suboptions for options that accept hashes.
195
+ SUBOPTIONS = {
196
+ "apply_settings" => {
197
+ "show_diff" => {
198
+ desc: "Whether to log and report a contextual diff when files are being replaced. See "\
199
+ "[Puppet documentation](https://puppet.com/docs/puppet/latest/configuration.html#showdiff) "\
200
+ "for details.",
201
+ type: [TrueClass, FalseClass],
202
+ exmp: true,
203
+ def: false
204
+ }
205
+ },
206
+ "inventory-config" => INVENTORY_OPTIONS,
207
+ "log" => {
208
+ "append" => {
209
+ desc: "Add output to an existing log file. Available only for logs output to a "\
210
+ "filepath.",
211
+ type: [TrueClass, FalseClass],
212
+ def: true
213
+ },
214
+ "level" => {
215
+ desc: "The type of information in the log. Either `debug`, `info`, `notice`, "\
216
+ "`warn`, or `error`.",
217
+ type: String,
218
+ def: "`warn` for console, `notice` for file"
219
+ }
220
+ },
221
+ "puppetdb" => {
222
+ "cacert" => {
223
+ desc: "The path to the ca certificate for PuppetDB.",
224
+ type: String,
225
+ exmp: "/etc/puppetlabs/puppet/ssl/certs/ca.pem"
226
+ },
227
+ "cert" => {
228
+ desc: "The path to the client certificate file to use for authentication.",
229
+ type: String,
230
+ exmp: "/etc/puppetlabs/puppet/ssl/certs/my-host.example.com.pem"
231
+ },
232
+ "key" => {
233
+ desc: "The private key for the certificate.",
234
+ type: String,
235
+ exmp: "/etc/puppetlabs/puppet/ssl/private_keys/my-host.example.com.pem"
236
+ },
237
+ "server_urls" => {
238
+ desc: "An array containing the PuppetDB host to connect to. Include the protocol `https` "\
239
+ "and the port, which is usually `8081`. For example, "\
240
+ "`https://my-master.example.com:8081`.",
241
+ type: Array,
242
+ exmp: ["https://puppet.example.com:8081"]
243
+ },
244
+ "token" => {
245
+ desc: "The path to the PE RBAC Token.",
246
+ type: String,
247
+ exmp: "~/.puppetlabs/token"
248
+ }
249
+ },
250
+ "puppetfile" => {
251
+ "forge" => {
252
+ desc: "A subsection that can have its own `proxy` setting to set an HTTP proxy for Forge "\
253
+ "operations only, and a `baseurl` setting to specify a different Forge host.",
254
+ type: Hash,
255
+ exmp: { "baseurl" => "https://forge.example.com", "proxy" => "https://forgeapi.example.com" }
256
+ },
257
+ "proxy" => {
258
+ desc: "The HTTP proxy to use for Git and Forge operations.",
259
+ type: String,
260
+ exmp: "https://forgeapi.example.com"
261
+ }
262
+ }
263
+ }.freeze
264
+
265
+ # Options that are available in a bolt.yaml file
266
+ BOLT_OPTIONS = %w[
267
+ apply_settings
268
+ color
269
+ compile-concurrency
270
+ concurrency
271
+ format
272
+ hiera-config
273
+ inventoryfile
274
+ log
275
+ modulepath
276
+ plugin_hooks
277
+ plugins
278
+ puppetdb
279
+ puppetfile
280
+ save-rerun
281
+ trusted-external-command
282
+ ].freeze
283
+
284
+ # Options that are available in a bolt-defaults.yaml file
285
+ BOLT_DEFAULTS_OPTIONS = %w[
286
+ color
287
+ compile-concurrency
288
+ concurrency
289
+ format
290
+ inventory-config
291
+ plugin_hooks
292
+ plugins
293
+ puppetdb
294
+ puppetfile
295
+ save-rerun
296
+ ].freeze
297
+
298
+ # Options that are available in a bolt-project.yaml file
299
+ BOLT_PROJECT_OPTIONS = %w[
300
+ apply_settings
301
+ color
302
+ compile-concurrency
303
+ concurrency
304
+ format
305
+ hiera-config
306
+ inventoryfile
307
+ log
308
+ modulepath
309
+ name
310
+ plans
311
+ plugin_hooks
312
+ plugins
313
+ puppetdb
314
+ puppetfile
315
+ save-rerun
316
+ tasks
317
+ trusted-external-command
318
+ ].freeze
319
+ end
320
+ end
321
+ end