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
@@ -8,6 +8,8 @@ module Bolt
8
8
  Bolt::Outputter::Human.new(color, verbose, trace)
9
9
  when 'json'
10
10
  Bolt::Outputter::JSON.new(color, verbose, trace)
11
+ when 'rainbow'
12
+ Bolt::Outputter::Rainbow.new(color, verbose, trace)
11
13
  when nil
12
14
  raise "Cannot use outputter before parsing."
13
15
  end
@@ -24,4 +26,5 @@ end
24
26
 
25
27
  require 'bolt/outputter/human'
26
28
  require 'bolt/outputter/json'
29
+ require 'bolt/outputter/rainbow'
27
30
  require 'bolt/outputter/logger'
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bolt/pal'
4
+ require 'paint'
5
+
6
+ module Bolt
7
+ class Outputter
8
+ class Rainbow < Bolt::Outputter::Human
9
+ def initialize(color, verbose, trace, stream = $stdout)
10
+ super
11
+ @line_color = 0
12
+ @color = 0
13
+ @state = :normal
14
+ end
15
+
16
+ # The algorithm is from lolcat (https://github.com/busyloop/lolcat)
17
+ # lolcat is released with WTFPL
18
+ def rainbow
19
+ red = Math.sin(0.3 * @color + 0) * 127 + 128
20
+ green = Math.sin(0.3 * @color + 2 * Math::PI / 3) * 127 + 128
21
+ blue = Math.sin(0.3 * @color + 4 * Math::PI / 3) * 127 + 128
22
+ @color += 1 / 8.0
23
+ format("%<red>02X%<green>02X%<blue>02X", red: red, green: green, blue: blue)
24
+ end
25
+
26
+ def colorize(color, string)
27
+ if @color && @stream.isatty
28
+ if %i[green rainbow].include?(color)
29
+ a = string.chars.map do |c|
30
+ case @state
31
+ when :normal
32
+ if c == "\e"
33
+ @state = :ansi
34
+ elsif c == "\n"
35
+ @line_color += 1
36
+ @color = @line_color
37
+ c
38
+ else
39
+ Paint[c, rainbow]
40
+ end
41
+ when :ansi
42
+ @state = :normal if c == 'm'
43
+ end
44
+ end
45
+ a.join('')
46
+ else
47
+ "\033[#{COLORS[color]}m#{string}\033[0m"
48
+ end
49
+ else
50
+ string
51
+ end
52
+ end
53
+
54
+ def print_summary(results, elapsed_time = nil)
55
+ ok_set = results.ok_set
56
+ unless ok_set.empty?
57
+ @stream.puts colorize(:rainbow, format('Successful on %<size>d target%<plural>s: %<names>s',
58
+ size: ok_set.size,
59
+ plural: ok_set.size == 1 ? '' : 's',
60
+ names: ok_set.targets.map(&:safe_name).join(',')))
61
+ end
62
+
63
+ error_set = results.error_set
64
+ unless error_set.empty?
65
+ @stream.puts colorize(:red,
66
+ format('Failed on %<size>d target%<plural>s: %<names>s',
67
+ size: error_set.size,
68
+ plural: error_set.size == 1 ? '' : 's',
69
+ names: error_set.targets.map(&:safe_name).join(',')))
70
+ end
71
+
72
+ total_msg = format('Ran on %<size>d target%<plural>s',
73
+ size: results.size,
74
+ plural: results.size == 1 ? '' : 's')
75
+ total_msg << " in #{duration_to_string(elapsed_time)}" unless elapsed_time.nil?
76
+ @stream.puts colorize(:rainbow, total_msg)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -137,7 +137,9 @@ module Bolt
137
137
  # TODO: If we always call this inside a bolt_executor we can remove this here
138
138
  setup
139
139
  r = Puppet::Pal.in_tmp_environment('bolt', modulepath: @modulepath, facts: {}) do |pal|
140
- Puppet.override(bolt_project: @project,
140
+ # Only load the project if it a) exists, b) has a name it can be loaded with
141
+ bolt_project = @project if @project&.name
142
+ Puppet.override(bolt_project: bolt_project,
141
143
  yaml_plan_instantiator: Bolt::PAL::YamlPlan::Loader) do
142
144
  pal.with_script_compiler do |compiler|
143
145
  alias_types(compiler)
@@ -190,6 +192,7 @@ module Bolt
190
192
  # versions of "core" types, which are already present on the agent,
191
193
  # but may cause issues on Puppet 5 agents.
192
194
  @original_modulepath,
195
+ @project,
193
196
  pdb_client,
194
197
  @hiera_config,
195
198
  @max_compiles,
@@ -359,6 +362,7 @@ module Bolt
359
362
  name = param.name
360
363
  if signature_params.include?(name)
361
364
  params[name] = { 'type' => param.types.first }
365
+ params[name]['sensitive'] = param.types.first =~ /\ASensitive(\[.*\])?\z/ ? true : false
362
366
  params[name]['default_value'] = defaults[name] if defaults.key?(name)
363
367
  params[name]['description'] = param.text unless param.text.empty?
364
368
  else
@@ -390,6 +394,7 @@ module Bolt
390
394
  param.type_expr
391
395
  end
392
396
  params[name] = { 'type' => type_str }
397
+ params[name]['sensitive'] = param.type_expr.instance_of?(Puppet::Pops::Types::PSensitiveType)
393
398
  params[name]['default_value'] = param.value
394
399
  params[name]['description'] = param.description if param.description
395
400
  end
@@ -2,21 +2,27 @@
2
2
 
3
3
  require 'pathname'
4
4
  require 'bolt/pal'
5
+ require 'bolt/config'
5
6
 
6
7
  module Bolt
7
8
  class Project
8
9
  BOLTDIR_NAME = 'Boltdir'
9
10
  PROJECT_SETTINGS = {
10
11
  "name" => "The name of the project",
11
- "plans" => "An array of plan names to whitelist. Whitelisted plans are included in `bolt plan show` output",
12
- "tasks" => "An array of task names to whitelist. Whitelisted plans are included in `bolt task show` output"
12
+ "plans" => "An array of plan names to show, if they exist in the project."\
13
+ "These plans are included in `bolt plan show` output",
14
+ "tasks" => "An array of task names to show, if they exist in the project."\
15
+ "These tasks are included in `bolt task show` output"
13
16
  }.freeze
14
17
 
15
- attr_reader :path, :config_file, :inventory_file, :modulepath, :hiera_config,
16
- :puppetfile, :rerunfile, :type, :resource_types
18
+ attr_reader :path, :data, :config_file, :inventory_file, :modulepath, :hiera_config,
19
+ :puppetfile, :rerunfile, :type, :resource_types, :warnings, :project_file
17
20
 
18
21
  def self.default_project
19
- Project.new(File.join('~', '.puppetlabs', 'bolt'), 'user')
22
+ create_project(File.expand_path(File.join('~', '.puppetlabs', 'bolt')), 'user')
23
+ # If homedir isn't defined use the system config path
24
+ rescue ArgumentError
25
+ create_project(Bolt::Config.system_path, 'system')
20
26
  end
21
27
 
22
28
  # Search recursively up the directory hierarchy for the Project. Look for a
@@ -26,9 +32,9 @@ module Bolt
26
32
  def self.find_boltdir(dir)
27
33
  dir = Pathname.new(dir)
28
34
  if (dir + BOLTDIR_NAME).directory?
29
- new(dir + BOLTDIR_NAME, 'embedded')
35
+ create_project(dir + BOLTDIR_NAME, 'embedded')
30
36
  elsif (dir + 'bolt.yaml').file? || (dir + 'bolt-project.yaml').file?
31
- new(dir, 'local')
37
+ create_project(dir, 'local')
32
38
  elsif dir.root?
33
39
  default_project
34
40
  else
@@ -36,9 +42,25 @@ module Bolt
36
42
  end
37
43
  end
38
44
 
39
- def initialize(path, type = 'option')
45
+ def self.create_project(path, type = 'option')
46
+ fullpath = Pathname.new(path).expand_path
47
+ project_file = File.join(fullpath, 'bolt-project.yaml')
48
+ data = Bolt::Util.read_optional_yaml_hash(File.expand_path(project_file), 'project')
49
+ new(data, path, type)
50
+ end
51
+
52
+ def initialize(raw_data, path, type = 'option')
40
53
  @path = Pathname.new(path).expand_path
41
- @config_file = @path + 'bolt.yaml'
54
+ @project_file = @path + 'bolt-project.yaml'
55
+
56
+ @warnings = []
57
+ if (@path + 'bolt.yaml').file? && project_file?
58
+ msg = "Project-level configuration in bolt.yaml is deprecated if using bolt-project.yaml. "\
59
+ "Transport config should be set in inventory.yaml, all other config should be set in "\
60
+ "bolt-project.yaml."
61
+ @warnings << { msg: msg }
62
+ end
63
+
42
64
  @inventory_file = @path + 'inventory.yaml'
43
65
  @modulepath = [(@path + 'modules').to_s, (@path + 'site-modules').to_s, (@path + 'site').to_s]
44
66
  @hiera_config = @path + 'hiera.yaml'
@@ -47,9 +69,26 @@ module Bolt
47
69
  @resource_types = @path + '.resource_types'
48
70
  @type = type
49
71
 
50
- @project_file = @path + 'bolt-project.yaml'
51
- @data = Bolt::Util.read_optional_yaml_hash(File.expand_path(@project_file), 'project') || {}
52
- validate if load_as_module?
72
+ tc = Bolt::Config::INVENTORY_OPTIONS.keys & raw_data.keys
73
+ if tc.any?
74
+ msg = "Transport configuration isn't supported in bolt-project.yaml. Ignoring keys #{tc}"
75
+ @warnings << { msg: msg }
76
+ end
77
+
78
+ @data = raw_data.reject { |k, _| Bolt::Config::INVENTORY_OPTIONS.include?(k) }
79
+
80
+ # Once bolt.yaml deprecation is removed, this attribute should be removed
81
+ # and replaced with .project_file in lib/bolt/config.rb
82
+ @config_file = if (Bolt::Config::BOLT_OPTIONS & @data.keys).any?
83
+ if (@path + 'bolt.yaml').file?
84
+ msg = "bolt-project.yaml contains valid config keys, bolt.yaml will be ignored"
85
+ @warnings << { msg: msg }
86
+ end
87
+ @project_file
88
+ else
89
+ @path + 'bolt.yaml'
90
+ end
91
+ validate if project_file?
53
92
  end
54
93
 
55
94
  def to_s
@@ -67,15 +106,12 @@ module Bolt
67
106
  end
68
107
  alias == eql?
69
108
 
70
- def load_as_module?
109
+ def project_file?
71
110
  @project_file.file?
72
111
  end
73
112
 
74
113
  def name
75
- # If the project is in mymod/Boltdir/bolt-project.yaml, use mymod as the project name
76
- dirname = @path.basename.to_s == 'Boltdir' ? @path.parent.basename.to_s : @path.basename.to_s
77
- pname = @data['name'] || dirname
78
- pname.include?('-') ? pname.split('-', 2)[1] : pname
114
+ @data['name']
79
115
  end
80
116
 
81
117
  def tasks
@@ -86,36 +122,20 @@ module Bolt
86
122
  @data['plans']
87
123
  end
88
124
 
89
- def project_directory_name?(name)
90
- # it must match an installed project name according to forge validator
91
- name =~ /^[a-z][a-z0-9_]*$/
92
- end
93
-
94
- def project_namespaced_name?(name)
95
- # it must match the full project name according to forge validator
96
- name =~ /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
97
- end
98
-
99
125
  def validate
100
- n = @data['name']
101
- if n && !project_directory_name?(n) && !project_namespaced_name?(n)
102
- raise Bolt::ValidationError, <<~ERROR_STRING
103
- Invalid project name '#{n}' in bolt-project.yaml; project names must match either:
104
- An installed project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
105
- A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
106
- ERROR_STRING
107
- elsif !project_directory_name?(name) && !project_namespaced_name?(name)
108
- raise Bolt::ValidationError, <<~ERROR_STRING
109
- Invalid project name '#{name}'; project names must match either:
110
- A project name (ex. projectname) matching the expression /^[a-z][a-z0-9_]*$/ -or-
111
- A namespaced project name (ex. author-projectname) matching the expression /^[a-zA-Z0-9]+[-][a-z][a-z0-9_]*$/
112
-
113
- Configure project name in <project_dir>/bolt-project.yaml
114
- ERROR_STRING
115
- # If the project name is the same as one of the built-in modules raise a warning
116
- elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
117
- raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
118
- "with a built-in Bolt module of the same name."
126
+ if name
127
+ name_regex = /^[a-z][a-z0-9_]*$/
128
+ if name !~ name_regex
129
+ raise Bolt::ValidationError, <<~ERROR_STRING
130
+ Invalid project name '#{name}' in bolt-project.yaml; project name must match #{name_regex.inspect}
131
+ ERROR_STRING
132
+ elsif Dir.children(Bolt::PAL::BOLTLIB_PATH).include?(name)
133
+ raise Bolt::ValidationError, "The project '#{name}' will not be loaded. The project name conflicts "\
134
+ "with a built-in Bolt module of the same name."
135
+ end
136
+ else
137
+ message = "No project name is specified in bolt-project.yaml. Project-level content will not be available."
138
+ @warnings << { msg: message }
119
139
  end
120
140
 
121
141
  %w[tasks plans].each do |conf|
@@ -30,8 +30,7 @@ module Bolt
30
30
  @target = resource_hash['target']
31
31
  @type = resource_hash['type'].to_s.capitalize
32
32
  @title = resource_hash['title']
33
- # get_resources() returns observed state under the 'parameters' key
34
- @state = resource_hash['state'] || resource_hash['parameters'] || {}
33
+ @state = resource_hash['state'] || {}
35
34
  @desired_state = resource_hash['desired_state'] || {}
36
35
  @events = resource_hash['events'] || []
37
36
  end
@@ -84,11 +83,19 @@ module Bolt
84
83
  to_hash.to_json(opts)
85
84
  end
86
85
 
86
+ def self.format_reference(type, title)
87
+ "#{type.capitalize}[#{title}]"
88
+ end
89
+
87
90
  def reference
88
- "#{type}[#{title}]"
91
+ self.class.format_reference(@type, @title)
89
92
  end
90
93
  alias to_s reference
91
94
 
95
+ def [](attribute)
96
+ @state[attribute]
97
+ end
98
+
92
99
  def add_event(event)
93
100
  @events << event
94
101
  end
@@ -35,7 +35,7 @@ module Bolt
35
35
  def upload(source, destination, options = {})
36
36
  running_as(options[:run_as]) do
37
37
  with_tmpdir do |dir|
38
- basename = File.basename(destination)
38
+ basename = File.basename(source)
39
39
  tmpfile = File.join(dir.to_s, basename)
40
40
  conn.copy_file(source, tmpfile)
41
41
  # pass over file ownership if we're using run-as to be a different user
@@ -332,14 +332,14 @@ module Bolt
332
332
  end
333
333
 
334
334
  if escalate
335
- if use_sudo
336
- sudo_exec = target.options['sudo-executable'] || "sudo"
337
- sudo_flags = [sudo_exec, "-S", "-H", "-u", run_as, "-p", sudo_prompt]
338
- sudo_flags += ["-E"] if options[:environment]
339
- sudo_str = Shellwords.shelljoin(sudo_flags)
340
- else
341
- sudo_str = Shellwords.shelljoin(@target.options['run-as-command'] + [run_as])
342
- end
335
+ sudo_str = if use_sudo
336
+ sudo_exec = target.options['sudo-executable'] || "sudo"
337
+ sudo_flags = [sudo_exec, "-S", "-H", "-u", run_as, "-p", sudo_prompt]
338
+ sudo_flags += ["-E"] if options[:environment]
339
+ Shellwords.shelljoin(sudo_flags)
340
+ else
341
+ Shellwords.shelljoin(@target.options['run-as-command'] + [run_as])
342
+ end
343
343
  command_str = build_sudoable_command_str(command_str, sudo_str, @sudo_id, options)
344
344
  end
345
345
 
@@ -111,7 +111,8 @@ module Bolt
111
111
  end
112
112
 
113
113
  def mkdirs(dirs)
114
- mkdir_command = "mkdir -Force #{dirs.uniq.sort.join(',')}"
114
+ paths = dirs.uniq.sort.join('","')
115
+ mkdir_command = "mkdir -Force -Path (\"#{paths}\")"
115
116
  result = execute(mkdir_command)
116
117
  if result.exit_code != 0
117
118
  message = "Could not create directories: #{result.stderr.string}"
@@ -20,6 +20,14 @@ module Bolt
20
20
  PS
21
21
  end
22
22
 
23
+ def exit_with_code(command)
24
+ <<~PS
25
+ #{command}
26
+ if (-not $? -and ($LASTEXITCODE -eq $null)) { exit 1 }
27
+ exit $LASTEXITCODE
28
+ PS
29
+ end
30
+
23
31
  def make_tmpdir(parent)
24
32
  <<~PS
25
33
  $parent = #{parent}
@@ -20,7 +20,7 @@ module Bolt
20
20
  def upload(target, source, destination, _options = {})
21
21
  with_connection(target) do |conn|
22
22
  conn.with_remote_tmpdir do |dir|
23
- basename = File.basename(destination)
23
+ basename = File.basename(source)
24
24
  tmpfile = "#{dir}/#{basename}"
25
25
  if File.directory?(source)
26
26
  conn.write_remote_directory(source, tmpfile)
@@ -50,7 +50,8 @@ module Bolt
50
50
  # If it's already a powershell command then invoke it normally.
51
51
  # Otherwise, wrap it in powershell.exe.
52
52
  unless command.start_with?('powershell.exe')
53
- command = ['powershell.exe', *Bolt::Shell::Powershell::PS_ARGS, '-Command', command]
53
+ cmd = Bolt::Shell::Powershell::Snippets.exit_with_code(command)
54
+ command = ['powershell.exe', *Bolt::Shell::Powershell::PS_ARGS, '-Command', cmd]
54
55
  end
55
56
  end
56
57
 
@@ -78,6 +78,28 @@ module Bolt
78
78
 
79
79
  options[:proxy] = Net::SSH::Proxy::Jump.new(target.options['proxyjump']) if target.options['proxyjump']
80
80
 
81
+ # Override the default supported algorithms for net-ssh. By default, a subset of supported algorithms
82
+ # are enabled in 6.x, while several are deprecated and not enabled by default. The *-algorithms
83
+ # options can be used to specify a list of algorithms to enable in net-ssh. Any algorithms not in the
84
+ # list are disabled, including ones that are normally enabled by default. Support for deprecated
85
+ # algorithms will be removed in 7.x.
86
+ # https://github.com/net-ssh/net-ssh#supported-algorithms
87
+ if target.options['encryption-algorithms']
88
+ options[:encryption] = net_ssh_algorithms(:encryption, target.options['encryption-algorithms'])
89
+ end
90
+
91
+ if target.options['host-key-algorithms']
92
+ options[:host_key] = net_ssh_algorithms(:host_key, target.options['host-key-algorithms'])
93
+ end
94
+
95
+ if target.options['kex-algorithms']
96
+ options[:kex] = net_ssh_algorithms(:kex, target.options['kex-algorithms'])
97
+ end
98
+
99
+ if target.options['mac-algorithms']
100
+ options[:hmac] = net_ssh_algorithms(:hmac, target.options['mac-algorithms'])
101
+ end
102
+
81
103
  # This option was to address discrepency betwen net-ssh host-key-check and ssh(1)
82
104
  # For the net-ssh 5.x series it defaults to true, in 6.x it will default to false, and will be removed in 7.x
83
105
  # https://github.com/net-ssh/net-ssh/pull/663#issuecomment-469979931
@@ -246,6 +268,19 @@ module Bolt
246
268
  end
247
269
  end
248
270
 
271
+ # Add all default algorithms if the 'defaults' key is present and filter
272
+ # out any unsupported algorithms.
273
+ def net_ssh_algorithms(type, algorithms)
274
+ if algorithms.include?('defaults')
275
+ defaults = Net::SSH::Transport::Algorithms::DEFAULT_ALGORITHMS[type]
276
+ algorithms += defaults
277
+ end
278
+
279
+ known = Net::SSH::Transport::Algorithms::ALGORITHMS[type]
280
+
281
+ algorithms & known
282
+ end
283
+
249
284
  def shell
250
285
  @shell ||= if target.options['login-shell'] == 'powershell'
251
286
  Bolt::Shell::Powershell.new(target, self)