bolt 2.17.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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d7b3fbf02e3b0162ead0c7ca8994eb6a96f5f8d49d9b27aa4620a54c111396d
4
- data.tar.gz: 3ec22c605ca39808ffdc1f9589bdc05921889f4cdf7ef7079682614bd1298a81
3
+ metadata.gz: 87eb513700afc2d9fefa9f2497e8718f0e14e511ea2004c59eecfcb19e41bdbc
4
+ data.tar.gz: 499d4c8a1a3c0d4b883d83f6478019467a44999beba83712ce49a30ee2ff1fc8
5
5
  SHA512:
6
- metadata.gz: 71c5644b0b1bcaee1aaf5d74b6e1ae895e82ffa56dc52df9b4fc2995f1fb7541c9da8c3a0a4d31b63b72e04ab2245d4fd377f3bf7eaf18ea4de7f8c7c2f46c6b
7
- data.tar.gz: 503755fc3f2196d53fc26d6bfbe662671689ed6208ccf9ab06a384eb254d878f2e00d62b182f3c2185d09fc558bed8a2dfa8d2b942024bd62977b0e50e8c0d33
6
+ metadata.gz: 6046f666fe9bccc4df2a70b191582b14b834063caf31987c1131e7e7c57ac9280e0bf76d719f9b82f27f5de7205b744a2dd3d485f53e9aa867661b824a5b0945
7
+ data.tar.gz: fc77aa2acd93b4aab739f468a2aa6a6a2da2c9c10bbc4927005f60aeb20148ecb29884d6f2c6cf86bf84d76af276a55f1a8588734157444ab5844c0b9d9878c9
@@ -13,10 +13,13 @@ require 'bolt/task'
13
13
  # > **Note:** Not available in apply block
14
14
  Puppet::Functions.create_function(:apply_prep) do
15
15
  # @param targets A pattern or array of patterns identifying a set of targets.
16
+ # @param options Options hash.
17
+ # @option options [Array] _required_modules An array of modules to sync to the target.
16
18
  # @example Prepare targets by name.
17
19
  # apply_prep('target1,target2')
18
20
  dispatch :apply_prep do
19
21
  param 'Boltlib::TargetSpec', :targets
22
+ optional_param 'Hash[String, Data]', :options
20
23
  end
21
24
 
22
25
  def script_compiler
@@ -60,18 +63,34 @@ Puppet::Functions.create_function(:apply_prep) do
60
63
  @executor ||= Puppet.lookup(:bolt_executor)
61
64
  end
62
65
 
63
- def apply_prep(target_spec)
66
+ def apply_prep(target_spec, options = {})
64
67
  unless Puppet[:tasks]
65
68
  raise Puppet::ParseErrorWithIssue
66
69
  .from_issue_and_stack(Bolt::PAL::Issues::PLAN_OPERATION_NOT_SUPPORTED_WHEN_COMPILING, action: 'apply_prep')
67
70
  end
68
71
 
72
+ options = options.transform_keys { |k| k.sub(/^_/, '').to_sym }
73
+
69
74
  applicator = Puppet.lookup(:apply_executor)
70
75
 
71
76
  executor.report_function_call(self.class.name)
72
77
 
73
78
  targets = inventory.get_targets(target_spec)
74
79
 
80
+ required_modules = options[:required_modules].nil? ? nil : Array(options[:required_modules])
81
+ if required_modules&.any?
82
+ Puppet.debug("Syncing only required modules: #{required_modules.join(',')}.")
83
+ end
84
+
85
+ # Gather facts, including custom facts
86
+ plugins = applicator.build_plugin_tarball do |mod|
87
+ next unless required_modules.nil? || required_modules.include?(mod.name)
88
+ search_dirs = []
89
+ search_dirs << mod.plugins if mod.plugins?
90
+ search_dirs << mod.pluginfacts if mod.pluginfacts?
91
+ search_dirs
92
+ end
93
+
75
94
  executor.log_action('install puppet and gather facts', targets) do
76
95
  executor.without_default_logging do
77
96
  # Skip targets that include the puppet-agent feature, as we know an agent will be available.
@@ -109,14 +128,6 @@ Puppet::Functions.create_function(:apply_prep) do
109
128
  need_install_targets.each { |target| set_agent_feature(target) }
110
129
  end
111
130
 
112
- # Gather facts, including custom facts
113
- plugins = applicator.build_plugin_tarball do |mod|
114
- search_dirs = []
115
- search_dirs << mod.plugins if mod.plugins?
116
- search_dirs << mod.pluginfacts if mod.pluginfacts?
117
- search_dirs
118
- end
119
-
120
131
  task = applicator.custom_facts_task
121
132
  arguments = { 'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins) }
122
133
  results = executor.run_task(targets, task, arguments)
@@ -18,7 +18,6 @@ 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
23
  @modulepath = modulepath || []
@@ -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
@@ -188,7 +176,6 @@ module Bolt
188
176
 
189
177
  def apply_ast(raw_ast, targets, options, plan_vars = {})
190
178
  ast = Puppet::Pops::Serialization::ToDataConverter.convert(raw_ast, rich_data: true, symbol_to_string: true)
191
-
192
179
  # Serialize as pcore for *Result* objects
193
180
  plan_vars = Puppet::Pops::Serialization::ToDataConverter.convert(plan_vars,
194
181
  rich_data: true,
@@ -207,9 +194,26 @@ module Bolt
207
194
  # This data isn't available on the target config hash
208
195
  config: @inventory.transport_data_get
209
196
  }
210
-
211
197
  description = options[:description] || 'apply catalog'
212
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
+
213
217
  r = @executor.log_action(description, targets) do
214
218
  futures = targets.map do |target|
215
219
  Concurrent::Future.execute(executor: @pool) do
@@ -236,6 +240,7 @@ module Bolt
236
240
  result
237
241
  end
238
242
  else
243
+
239
244
  arguments = {
240
245
  'catalog' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(catalog),
241
246
  'plugins' => Puppet::Pops::Types::PSensitiveType::Sensitive.new(plugins),
@@ -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\)/
@@ -11,7 +11,7 @@ module Bolt
11
11
  escalation: %w[run-as sudo-password sudo-password-prompt sudo-executable],
12
12
  run_context: %w[concurrency inventoryfile save-rerun cleanup],
13
13
  global_config_setters: %w[modulepath project configfile],
14
- transports: %w[transport connect-timeout tty ssh-command copy-command],
14
+ transports: %w[transport connect-timeout tty native-ssh ssh-command copy-command],
15
15
  display: %w[format color verbose trace],
16
16
  global: %w[help version debug log-level] }.freeze
17
17
 
@@ -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
@@ -46,7 +46,6 @@ module Bolt
46
46
  Bolt::Logger.initialize_logging
47
47
  @logger = Logging.logger[self]
48
48
  @argv = argv
49
- @config = Bolt::Config.default
50
49
  @options = {}
51
50
  end
52
51
 
@@ -77,61 +76,74 @@ module Bolt
77
76
  private :help?
78
77
 
79
78
  def parse
80
- parser = BoltOptionParser.new(options)
81
- # This part aims to handle both `bolt <mode> --help` and `bolt help <mode>`.
82
- remaining = handle_parser_errors { parser.permute(@argv) } unless @argv.empty?
83
- if @argv.empty? || help?(remaining)
84
- # Update the parser for the subcommand (or lack thereof)
85
- parser.update
86
- puts parser.help
87
- raise Bolt::CLIExit
88
- end
89
-
90
- options[:object] = remaining.shift
91
-
92
- # Only parse task_options for task or plan
93
- if %w[task plan].include?(options[:subcommand])
94
- task_options, remaining = remaining.partition { |s| s =~ /.+=/ }
95
- if options[:task_options]
96
- unless task_options.empty?
97
- raise Bolt::CLIError,
98
- "Parameters must be specified through either the --params " \
99
- "option or param=value pairs, not both"
100
- end
101
- options[:params_parsed] = true
102
- elsif task_options.any?
103
- options[:params_parsed] = false
104
- options[:task_options] = Hash[task_options.map { |a| a.split('=', 2) }]
105
- else
106
- options[:params_parsed] = true
107
- options[:task_options] = {}
79
+ begin
80
+ parser = BoltOptionParser.new(options)
81
+ # This part aims to handle both `bolt <mode> --help` and `bolt help <mode>`.
82
+ remaining = handle_parser_errors { parser.permute(@argv) } unless @argv.empty?
83
+ if @argv.empty? || help?(remaining)
84
+ # Update the parser for the subcommand (or lack thereof)
85
+ parser.update
86
+ puts parser.help
87
+ raise Bolt::CLIExit
108
88
  end
109
- end
110
- options[:leftovers] = remaining
111
89
 
112
- validate(options)
90
+ options[:object] = remaining.shift
113
91
 
114
- @config = if options[:configfile]
115
- Bolt::Config.from_file(options[:configfile], options)
116
- else
117
- project = if options[:boltdir]
118
- dir = Pathname.new(options[:boltdir])
119
- if (dir + Bolt::Project::BOLTDIR_NAME).directory?
120
- Bolt::Project.create_project(dir + Bolt::Project::BOLTDIR_NAME)
92
+ # Only parse task_options for task or plan
93
+ if %w[task plan].include?(options[:subcommand])
94
+ task_options, remaining = remaining.partition { |s| s =~ /.+=/ }
95
+ if options[:task_options]
96
+ unless task_options.empty?
97
+ raise Bolt::CLIError,
98
+ "Parameters must be specified through either the --params " \
99
+ "option or param=value pairs, not both"
100
+ end
101
+ options[:params_parsed] = true
102
+ elsif task_options.any?
103
+ options[:params_parsed] = false
104
+ options[:task_options] = Hash[task_options.map { |a| a.split('=', 2) }]
105
+ else
106
+ options[:params_parsed] = true
107
+ options[:task_options] = {}
108
+ end
109
+ end
110
+ options[:leftovers] = remaining
111
+
112
+ validate(options)
113
+
114
+ @config = if ENV['BOLT_PROJECT']
115
+ project = Bolt::Project.create_project(ENV['BOLT_PROJECT'], 'environment')
116
+ Bolt::Config.from_project(project, options)
117
+ elsif options[:configfile]
118
+ Bolt::Config.from_file(options[:configfile], options)
119
+ else
120
+ project = if options[:boltdir]
121
+ dir = Pathname.new(options[:boltdir])
122
+ if (dir + Bolt::Project::BOLTDIR_NAME).directory?
123
+ Bolt::Project.create_project(dir + Bolt::Project::BOLTDIR_NAME)
124
+ else
125
+ Bolt::Project.create_project(dir)
126
+ end
121
127
  else
122
- Bolt::Project.create_project(dir)
128
+ Bolt::Project.find_boltdir(Dir.pwd)
123
129
  end
124
- else
125
- Bolt::Project.find_boltdir(Dir.pwd)
126
- end
127
- Bolt::Config.from_project(project, options)
128
- end
129
-
130
- Bolt::Logger.configure(config.log, config.color)
130
+ Bolt::Config.from_project(project, options)
131
+ end
132
+
133
+ Bolt::Logger.configure(config.log, config.color)
134
+ rescue Bolt::Error => e
135
+ if $stdout.isatty
136
+ # Print the error message in red, mimicking outputter.fatal_error
137
+ $stdout.puts("\033[31m#{e.message}\033[0m")
138
+ else
139
+ $stdout.puts(e.message)
140
+ end
141
+ raise e
142
+ end
131
143
 
132
144
  # Logger must be configured before checking path case and project file, otherwise warnings will not display
133
- @config.check_path_case('modulepath', @config.modulepath)
134
- @config.project.check_deprecated_file
145
+ config.check_path_case('modulepath', config.modulepath)
146
+ config.project.check_deprecated_file
135
147
 
136
148
  # Log the file paths for loaded config files
137
149
  config_loaded
@@ -281,12 +293,12 @@ module Bolt
281
293
  def warn_inventory_overrides_cli(opts)
282
294
  inventory_source = if ENV[Bolt::Inventory::ENVIRONMENT_VAR]
283
295
  Bolt::Inventory::ENVIRONMENT_VAR
284
- elsif @config.inventoryfile && Bolt::Util.file_stat(@config.inventoryfile)
285
- @config.inventoryfile
296
+ elsif config.inventoryfile && Bolt::Util.file_stat(config.inventoryfile)
297
+ config.inventoryfile
286
298
  else
287
299
  begin
288
- Bolt::Util.file_stat(@config.default_inventoryfile)
289
- @config.default_inventoryfile
300
+ Bolt::Util.file_stat(config.default_inventoryfile)
301
+ config.default_inventoryfile
290
302
  rescue Errno::ENOENT
291
303
  nil
292
304
  end
@@ -390,7 +402,7 @@ module Bolt
390
402
  if options[:action] == 'generate-types'
391
403
  code = generate_types
392
404
  elsif options[:action] == 'install'
393
- code = install_puppetfile(@config.puppetfile_config, @config.puppetfile, @config.modulepath)
405
+ code = install_puppetfile(config.puppetfile_config, config.puppetfile, config.modulepath)
394
406
  end
395
407
  when 'secret'
396
408
  code = Bolt::Secret.execute(plugins, outputter, options)
@@ -802,7 +814,7 @@ module Bolt
802
814
  end
803
815
 
804
816
  def rerun
805
- @rerun ||= Bolt::Rerun.new(@config.rerunfile, @config.save_rerun)
817
+ @rerun ||= Bolt::Rerun.new(config.rerunfile, config.save_rerun)
806
818
  end
807
819
 
808
820
  def outputter
@@ -28,7 +28,7 @@ module Bolt
28
28
  DEFAULT_DEFAULT_CONCURRENCY = 100
29
29
 
30
30
  def self.default
31
- new(Bolt::Project.create_project('.'), {})
31
+ new(Bolt::Project.default_project, {})
32
32
  end
33
33
 
34
34
  def self.from_project(project, overrides = {})
@@ -121,9 +121,22 @@ module Bolt
121
121
  )
122
122
  end
123
123
 
124
- # Move data under transport-config to top-level so it can be easily merged with
125
- # config from other sources.
124
+ # Move data under inventory-config to top-level so it can be easily merged with
125
+ # config from other sources. Error early if inventory-config is not a hash or
126
+ # has a plugin reference.
126
127
  if data.key?('inventory-config')
128
+ unless data['inventory-config'].is_a?(Hash)
129
+ raise Bolt::ValidationError,
130
+ "Option 'inventory-config' must be of type Hash, received #{data['inventory-config']} "\
131
+ "#{data['inventory-config']} (file: #{filepath})"
132
+ end
133
+
134
+ if data['inventory-config'].key?('_plugin')
135
+ raise Bolt::ValidationError,
136
+ "Found unsupported key '_plugin' for option 'inventory-config'; supported keys are "\
137
+ "'#{INVENTORY_OPTIONS.keys.join("', '")}' (file: #{filepath})"
138
+ end
139
+
127
140
  data = data.merge(data.delete('inventory-config'))
128
141
  end
129
142
 
@@ -175,7 +175,7 @@ module Bolt
175
175
  "log" => {
176
176
  description: "A map of configuration for the logfile output. Under `log`, you can configure log options "\
177
177
  "for `console` and add configuration for individual log files, such as "\
178
- "~/.puppetlabs/bolt/debug.log`. Individual log files must be valid filepaths. If the log "\
178
+ "`~/.puppetlabs/bolt/debug.log`. Individual log files must be valid filepaths. If the log "\
179
179
  "file does not exist, then Bolt will create it before logging information.",
180
180
  type: Hash,
181
181
  properties: {
@@ -440,6 +440,7 @@ module Bolt
440
440
  concurrency
441
441
  format
442
442
  inventory-config
443
+ log
443
444
  plugin_hooks
444
445
  plugins
445
446
  puppetdb
@@ -108,17 +108,16 @@ module Bolt
108
108
  },
109
109
  "copy-command" => {
110
110
  type: [Array, String],
111
- description: "The command to use when copying files using ssh-command. Bolt runs `<copy-command> <src> "\
111
+ description: "The command to use when copying files using native SSH. Bolt runs `<copy-command> <src> "\
112
112
  "<dest>`. This option is used when you need support for features or algorithms that are not "\
113
113
  "supported by the net-ssh Ruby library. **This option is experimental.** You can read more "\
114
- "about this option in [External SSH "\
115
- "transport](experimental_features.md#external-ssh-transport).",
114
+ "about this option in [Native SSH transport](experimental_features.md#native-ssh-transport).",
116
115
  items: {
117
116
  type: String
118
117
  },
119
118
  _plugin: true,
120
- _default: "scp -r",
121
- _example: "scp -r -F ~/ssh-config/myconf"
119
+ _default: %w[scp -r],
120
+ _example: %w[scp -r -F ~/ssh-config/myconf]
122
121
  },
123
122
  "disconnect-timeout" => {
124
123
  type: Integer,
@@ -270,6 +269,13 @@ module Bolt
270
269
  _plugin: true,
271
270
  _example: %w[defaults hmac-md5]
272
271
  },
272
+ "native-ssh" => {
273
+ type: [TrueClass, FalseClass],
274
+ description: "This enables the native SSH transport, which shells out to SSH instead of using the "\
275
+ "net-ssh Ruby library",
276
+ _default: false,
277
+ _example: true
278
+ },
273
279
  "password" => {
274
280
  type: String,
275
281
  description: "The password to use to login.",
@@ -368,16 +374,16 @@ module Bolt
368
374
  },
369
375
  "ssh-command" => {
370
376
  type: [Array, String],
371
- description: "The command and flags to use when SSHing. This enables the external SSH transport, which "\
372
- "shells out to the specified command. This option is used when you need support for "\
377
+ description: "The command and flags to use when SSHing. This option is used when you need support for "\
373
378
  "features or algorithms that are not supported by the net-ssh Ruby library. **This option "\
374
- "is experimental.** You can read more about this option in [External SSH "\
375
- "transport](experimental_features.md#external-ssh-transport).",
379
+ "is experimental.** You can read more about this option in [Native SSH "\
380
+ "transport](experimental_features.md#native-ssh-transport).",
376
381
  items: {
377
382
  type: String
378
383
  },
379
384
  _plugin: true,
380
- _example: "ssh"
385
+ _default: 'ssh',
386
+ _example: %w[ssh -o Ciphers=chacha20-poly1305@openssh.com]
381
387
  },
382
388
  "ssl" => {
383
389
  type: [TrueClass, FalseClass],
@@ -32,13 +32,14 @@ module Bolt
32
32
  user
33
33
  ].concat(RUN_AS_OPTIONS).sort.freeze
34
34
 
35
- # Options available when using the external ssh transport
36
- EXTERNAL_OPTIONS = %w[
35
+ # Options available when using the native ssh transport
36
+ NATIVE_OPTIONS = %w[
37
37
  cleanup
38
38
  copy-command
39
39
  host
40
40
  host-key-check
41
41
  interpreters
42
+ native-ssh
42
43
  port
43
44
  private-key
44
45
  script-dir
@@ -56,17 +57,30 @@ module Bolt
56
57
  "tty" => false
57
58
  }.freeze
58
59
 
59
- # The set of options available for the ssh and external ssh transports overlap, so we
60
+ # The set of options available for the ssh and native ssh transports overlap, so we
60
61
  # need to check which transport is used before fully initializing, otherwise options
61
62
  # may not be filtered correctly.
62
63
  def initialize(data = {}, project = nil)
63
64
  assert_hash_or_config(data)
64
- @external = true if data['ssh-command']
65
+ @native = true if data['native-ssh']
65
66
  super(data, project)
66
67
  end
67
68
 
69
+ # This method is used to filter CLI options in the Config class. This
70
+ # should include `ssh-command` so that we can later warn if the option
71
+ # is present without `native-ssh`
72
+ def self.options
73
+ %w[ssh-command native-ssh].concat(OPTIONS)
74
+ end
75
+
68
76
  private def filter(unfiltered)
69
- @external ? unfiltered.slice(*EXTERNAL_OPTIONS) : unfiltered.slice(*OPTIONS)
77
+ # Because we filter before merging config together it's impossible to
78
+ # know whether both ssh-command *and* native-ssh will be specified
79
+ # unless they are both in the filter. However, we can't add
80
+ # ssh-command to OPTIONS since that's used for documenting available
81
+ # options. This makes it so that ssh-command is preserved so we can
82
+ # warn once all config is resolved if native-ssh isn't set.
83
+ @native ? unfiltered.slice(*NATIVE_OPTIONS) : unfiltered.slice(*self.class.options)
70
84
  end
71
85
 
72
86
  private def validate
@@ -83,11 +97,11 @@ module Bolt
83
97
  @config['private-key'] = File.expand_path(key_opt, @project)
84
98
 
85
99
  # We have an explicit test for this to only warn if using net-ssh transport
86
- Bolt::Util.validate_file('ssh key', @config['private-key']) if @config['ssh-command']
100
+ Bolt::Util.validate_file('ssh key', @config['private-key']) if @config['native-ssh']
87
101
  end
88
102
 
89
- if key_opt.instance_of?(Hash) && @config['ssh-command']
90
- raise Bolt::ValidationError, 'private-key must be a filepath when using ssh-command'
103
+ if key_opt.instance_of?(Hash) && @config['native-ssh']
104
+ raise Bolt::ValidationError, 'private-key must be a filepath when using native-ssh'
91
105
  end
92
106
  end
93
107
 
@@ -117,8 +131,8 @@ module Bolt
117
131
  end
118
132
  end
119
133
 
120
- if @config['ssh-command'] && !@config['load-config']
121
- msg = 'Cannot use external SSH transport with load-config set to false'
134
+ if @config['native-ssh'] && !@config['load-config']
135
+ msg = 'Cannot use native SSH transport with load-config set to false'
122
136
  raise Bolt::ValidationError, msg
123
137
  end
124
138
  end
@@ -15,6 +15,7 @@ module Bolt
15
15
  return if Logging.initialized?
16
16
 
17
17
  Logging.init :debug, :info, :notice, :warn, :error, :fatal, :any
18
+ @mutex = Mutex.new
18
19
 
19
20
  Logging.color_scheme(
20
21
  'bolt',
@@ -102,5 +103,16 @@ module Bolt
102
103
  def self.reset_logging
103
104
  Logging.reset
104
105
  end
106
+
107
+ def self.warn_once(type, msg)
108
+ @mutex.synchronize {
109
+ @warnings ||= []
110
+ @logger ||= Logging.logger[self]
111
+ unless @warnings.include?(type)
112
+ @logger.warn(msg)
113
+ @warnings << type
114
+ end
115
+ }
116
+ end
105
117
  end
106
118
  end
@@ -31,6 +31,7 @@ module Bolt
31
31
  # hierarchy, falling back to the default if we reach the root.
32
32
  def self.find_boltdir(dir)
33
33
  dir = Pathname.new(dir)
34
+
34
35
  if (dir + BOLTDIR_NAME).directory?
35
36
  create_project(dir + BOLTDIR_NAME, 'embedded')
36
37
  elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
@@ -44,6 +45,15 @@ module Bolt
44
45
 
45
46
  def self.create_project(path, type = 'option')
46
47
  fullpath = Pathname.new(path).expand_path
48
+
49
+ if !Bolt::Util.windows? && type != 'environment' && fullpath.world_writable?
50
+ raise Bolt::Error.new(
51
+ "Project directory '#{fullpath}' is world-writable which poses a security risk. Set "\
52
+ "BOLT_PROJECT='#{fullpath}' to force the use of this project directory.",
53
+ "bolt/world-writable-error"
54
+ )
55
+ end
56
+
47
57
  project_file = File.join(fullpath, 'bolt-project.yaml')
48
58
  data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
49
59
  new(data, path, type)
@@ -51,6 +61,7 @@ module Bolt
51
61
 
52
62
  def initialize(raw_data, path, type = 'option')
53
63
  @path = Pathname.new(path).expand_path
64
+
54
65
  @project_file = @path + 'bolt-project.yaml'
55
66
 
56
67
  @warnings = []
@@ -85,12 +85,21 @@ module Bolt
85
85
 
86
86
  def shell_init
87
87
  <<~PS
88
- $ENV:PATH += ";${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\bin\\;" +
89
- "${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\puppet\\bin;" +
90
- "${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\sys\\ruby\\bin\\"
91
- $ENV:RUBYLIB = "${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\puppet\\lib;" +
92
- "${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\facter\\lib;" +
93
- "${ENV:ProgramFiles}\\Puppet Labs\\Puppet\\hiera\\lib;" +
88
+ $installRegKey = Get-ItemProperty -Path "HKLM:\\Software\\Puppet Labs\\Puppet" -ErrorAction 0
89
+ if(![string]::IsNullOrEmpty($installRegKey.RememberedInstallDir64)){
90
+ $boltBaseDir = $installRegKey.RememberedInstallDir64
91
+ }elseif(![string]::IsNullOrEmpty($installRegKey.RememberedInstallDir)){
92
+ $boltBaseDir = $installRegKey.RememberedInstallDir
93
+ }else{
94
+ $boltBaseDir = "${ENV:ProgramFiles}\\Puppet Labs\\Puppet"
95
+ }
96
+
97
+ $ENV:PATH += ";${boltBaseDir}\\bin\\;" +
98
+ "${boltBaseDir}\\puppet\\bin;" +
99
+ "${boltBaseDir}\\sys\\ruby\\bin\\"
100
+ $ENV:RUBYLIB = "${boltBaseDir}\\puppet\\lib;" +
101
+ "${boltBaseDir}\\facter\\lib;" +
102
+ "${boltBaseDir}\\hiera\\lib;" +
94
103
  $ENV:RUBYLIB
95
104
 
96
105
  Add-Type -AssemblyName System.ServiceModel.Web, System.Runtime.Serialization
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'bolt/logger'
3
4
  require 'bolt/node/errors'
4
5
  require 'bolt/transport/simple'
5
6
 
@@ -21,7 +22,12 @@ module Bolt
21
22
  end
22
23
 
23
24
  def with_connection(target)
24
- conn = if target.transport_config['ssh-command']
25
+ if target.transport_config['ssh-command'] && !target.transport_config['native-ssh']
26
+ Bolt::Logger.warn_once("ssh-command and native-ssh conflict",
27
+ "native-ssh must be true to use ssh-command")
28
+ end
29
+
30
+ conn = if target.transport_config['native-ssh']
25
31
  ExecConnection.new(target)
26
32
  else
27
33
  Connection.new(target, @transport_logger)
@@ -58,7 +58,7 @@ module Bolt
58
58
  end
59
59
 
60
60
  def build_ssh_command(command)
61
- ssh_conf = @target.transport_config['ssh-command']
61
+ ssh_conf = @target.transport_config['ssh-command'] || 'ssh'
62
62
  ssh_cmd = Array(ssh_conf)
63
63
  ssh_cmd += ssh_opts
64
64
  ssh_cmd << userhost
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bolt
4
- VERSION = '2.17.0'
4
+ VERSION = '2.18.0'
5
5
  end
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: 2.17.0
4
+ version: 2.18.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-07-07 00:00:00.000000000 Z
11
+ date: 2020-07-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable