bolt 2.1.0 → 2.2.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/Puppetfile +5 -5
  3. data/bolt-modules/boltlib/lib/puppet/functions/run_command.rb +1 -1
  4. data/bolt-modules/boltlib/lib/puppet/functions/run_plan.rb +1 -1
  5. data/bolt-modules/boltlib/lib/puppet/functions/run_script.rb +1 -1
  6. data/bolt-modules/boltlib/lib/puppet/functions/run_task.rb +1 -1
  7. data/bolt-modules/boltlib/lib/puppet/functions/upload_file.rb +1 -1
  8. data/lib/bolt/applicator.rb +2 -2
  9. data/lib/bolt/bolt_option_parser.rb +1 -1
  10. data/lib/bolt/cli.rb +1 -1
  11. data/lib/bolt/config.rb +201 -206
  12. data/lib/bolt/config/transport/base.rb +142 -0
  13. data/lib/bolt/config/transport/docker.rb +42 -0
  14. data/lib/bolt/config/transport/local.rb +73 -0
  15. data/lib/bolt/config/transport/orch.rb +47 -0
  16. data/lib/bolt/config/transport/remote.rb +25 -0
  17. data/lib/bolt/config/transport/ssh.rb +105 -0
  18. data/lib/bolt/config/transport/winrm.rb +80 -0
  19. data/lib/bolt/executor.rb +17 -0
  20. data/lib/bolt/inventory.rb +12 -5
  21. data/lib/bolt/inventory/group.rb +1 -1
  22. data/lib/bolt/inventory/inventory.rb +16 -22
  23. data/lib/bolt/inventory/target.rb +26 -29
  24. data/lib/bolt/plugin.rb +5 -5
  25. data/lib/bolt/plugin/module.rb +1 -1
  26. data/lib/bolt/plugin/pkcs7.rb +1 -1
  27. data/lib/bolt/result.rb +1 -1
  28. data/lib/bolt/target.rb +5 -2
  29. data/lib/bolt/transport/base.rb +0 -18
  30. data/lib/bolt/transport/docker.rb +0 -26
  31. data/lib/bolt/transport/local.rb +0 -30
  32. data/lib/bolt/transport/local_windows.rb +4 -36
  33. data/lib/bolt/transport/orch.rb +0 -20
  34. data/lib/bolt/transport/remote.rb +0 -20
  35. data/lib/bolt/transport/ssh.rb +0 -85
  36. data/lib/bolt/transport/sudoable.rb +0 -7
  37. data/lib/bolt/transport/winrm.rb +0 -66
  38. data/lib/bolt/util.rb +11 -0
  39. data/lib/bolt/version.rb +1 -1
  40. data/lib/bolt_server/transport_app.rb +1 -0
  41. data/lib/bolt_spec/plans.rb +1 -1
  42. data/lib/bolt_spec/plans/action_stubs/command_stub.rb +1 -1
  43. data/lib/bolt_spec/plans/action_stubs/script_stub.rb +1 -1
  44. data/lib/bolt_spec/plans/action_stubs/task_stub.rb +2 -2
  45. data/lib/bolt_spec/plans/action_stubs/upload_stub.rb +1 -1
  46. data/lib/bolt_spec/run.rb +1 -1
  47. data/libexec/apply_catalog.rb +1 -1
  48. data/libexec/custom_facts.rb +1 -1
  49. data/libexec/query_resources.rb +1 -1
  50. metadata +9 -2
@@ -10,8 +10,25 @@ require 'bolt/result'
10
10
  require 'bolt/config'
11
11
  require 'bolt/result_set'
12
12
  require 'bolt/puppetdb'
13
+ # Load transports
14
+ require 'bolt/transport/ssh'
15
+ require 'bolt/transport/winrm'
16
+ require 'bolt/transport/orch'
17
+ require 'bolt/transport/local'
18
+ require 'bolt/transport/local_windows'
19
+ require 'bolt/transport/docker'
20
+ require 'bolt/transport/remote'
13
21
 
14
22
  module Bolt
23
+ TRANSPORTS = {
24
+ ssh: Bolt::Transport::SSH,
25
+ winrm: Bolt::Transport::WinRM,
26
+ pcp: Bolt::Transport::Orch,
27
+ local: Bolt::Util.windows? ? Bolt::Transport::LocalWindows : Bolt::Transport::Local,
28
+ docker: Bolt::Transport::Docker,
29
+ remote: Bolt::Transport::Remote
30
+ }.freeze
31
+
15
32
  class Executor
16
33
  attr_reader :noop, :transports
17
34
  attr_accessor :run_as
@@ -60,25 +60,32 @@ module Bolt
60
60
  end
61
61
  end
62
62
 
63
- inventory = create_version(data, config, plugins)
63
+ # Resolve plugin references from transport config
64
+ config.transports.each_value do |t|
65
+ t.resolve(plugins) unless t.resolved?
66
+ end
67
+
68
+ inventory = create_version(data, config.transport, config.transports, plugins)
64
69
  inventory.validate
65
70
  inventory
66
71
  end
67
72
 
68
- def self.create_version(data, config, plugins)
73
+ def self.create_version(data, transport, transports, plugins)
69
74
  version = (data || {}).delete('version') { 2 }
75
+
70
76
  case version
71
77
  when 2
72
- Bolt::Inventory::Inventory.new(data, config, plugins: plugins)
78
+ Bolt::Inventory::Inventory.new(data, transport, transports, plugins)
73
79
  else
74
80
  raise ValidationError.new("Unsupported version #{version} specified in inventory", nil)
75
81
  end
76
82
  end
77
83
 
78
84
  def self.empty
79
- config = Bolt::Config.default
85
+ config = Bolt::Config.default
80
86
  plugins = Bolt::Plugin.setup(config, nil, nil, Bolt::Analytics::NoopClient)
81
- create_version({}, config, plugins)
87
+
88
+ create_version({}, config.transport, config.transports, plugins)
82
89
  end
83
90
  end
84
91
  end
@@ -15,7 +15,7 @@ module Bolt
15
15
  DATA_KEYS = %w[config facts vars features plugin_hooks].freeze
16
16
  TARGET_KEYS = DATA_KEYS + %w[name alias uri]
17
17
  GROUP_KEYS = DATA_KEYS + %w[name groups targets]
18
- CONFIG_KEYS = Bolt::TRANSPORTS.keys.map(&:to_s) + ['transport']
18
+ CONFIG_KEYS = Bolt::Config::TRANSPORT_CONFIG.keys + ['transport']
19
19
 
20
20
  def initialize(input, plugins)
21
21
  @logger = Logging.logger[self]
@@ -6,24 +6,26 @@ require 'bolt/inventory/target'
6
6
  module Bolt
7
7
  class Inventory
8
8
  class Inventory
9
- attr_reader :targets, :plugins, :config
9
+ attr_reader :targets, :plugins, :config, :transport
10
10
  class WildcardError < Bolt::Error
11
11
  def initialize(target)
12
12
  super("Found 0 targets matching wildcard pattern #{target}", 'bolt.inventory/wildcard-error')
13
13
  end
14
14
  end
15
15
 
16
- def initialize(data, config = nil, plugins:)
17
- @logger = Logging.logger[self]
18
- # Config is saved to add config options to targets
19
- @config = config || Bolt::Config.default
20
- @data = data || {}
21
- @groups = Group.new(@data.merge('name' => 'all'), plugins)
22
- @plugins = plugins
16
+ # TODO: Pass transport config instead of config object
17
+ def initialize(data, transport, transports, plugins)
18
+ @logger = Logging.logger[self]
19
+ @data = data || {}
20
+ @transport = transport
21
+ @config = transports
22
+ @plugins = plugins
23
+ @groups = Group.new(@data.merge('name' => 'all'), plugins)
23
24
  @group_lookup = {}
24
- # The targets hash is the canonical source for all targets in inventory
25
- @targets = {}
25
+ @targets = {}
26
+
26
27
  @groups.resolve_string_targets(@groups.target_aliases, @groups.all_targets)
28
+
27
29
  collect_groups
28
30
  end
29
31
 
@@ -71,18 +73,6 @@ module Bolt
71
73
  target_array.first
72
74
  end
73
75
 
74
- def data_hash
75
- {
76
- data: {},
77
- target_hash: {
78
- target_vars: {},
79
- target_facts: {},
80
- target_features: {}
81
- },
82
- config: @config.transport_data_get
83
- }
84
- end
85
-
86
76
  def self.localhost_defaults(data)
87
77
  defaults = {
88
78
  'config' => {
@@ -284,6 +274,10 @@ module Bolt
284
274
  end
285
275
  end
286
276
 
277
+ def transport_data_get
278
+ { transport: transport, transports: config.transform_values(&:to_h) }
279
+ end
280
+
287
281
  def set_var(target, var_hash)
288
282
  @targets[target.name].set_var(var_hash)
289
283
  end
@@ -91,6 +91,7 @@ module Bolt
91
91
  end
92
92
  location[key] = value
93
93
  end
94
+
94
95
  invalidate_config_cache!
95
96
  end
96
97
 
@@ -101,28 +102,8 @@ module Bolt
101
102
  end
102
103
 
103
104
  def invalidate_config_cache!
104
- @transport_config_cache = nil
105
- end
106
-
107
- # Computing the transport config is expensive as it requires cloning the
108
- # base config, so we cache the effective config
109
- def transport_config_cache
110
- if @transport_config_cache.nil?
111
- merged_config = Bolt::Util.deep_merge(group_cache['config'], @config)
112
- base_config = @inventory.config
113
- transport_data = Bolt::Util.deep_clone(base_config.transports)
114
- Bolt::Config.update_transport_hash(base_config.boltdir.path, transport_data, merged_config)
115
- transport = merged_config['transport'] || base_config.transport
116
- Bolt::TRANSPORTS.each do |name, impl|
117
- impl.validate(transport_data[name])
118
- end
119
- @transport_config_cache = {
120
- 'transport' => transport,
121
- 'transports' => transport_data
122
- }
123
- end
124
-
125
- @transport_config_cache
105
+ @transport = nil
106
+ @transport_config = nil
126
107
  end
127
108
 
128
109
  # Validate the target. This implicitly also primes the group and config
@@ -135,6 +116,8 @@ module Bolt
135
116
  unless transport.nil? || Bolt::TRANSPORTS.include?(transport.to_sym)
136
117
  raise Bolt::UnknownTransportError.new(transport, uri)
137
118
  end
119
+
120
+ transport_config
138
121
  end
139
122
 
140
123
  def host
@@ -156,15 +139,23 @@ module Bolt
156
139
  # For remote targets, the transport is always 'remote'. Otherwise, it
157
140
  # will be either the URI scheme or set explicitly.
158
141
  def transport
159
- if remote?
160
- 'remote'
161
- else
162
- @uri_obj.scheme || transport_config_cache['transport']
142
+ if @transport.nil?
143
+ config_transport = @config['transport'] ||
144
+ group_cache.dig('config', 'transport') ||
145
+ @inventory.transport
146
+
147
+ @transport = if @uri_obj.scheme == 'remote' || config_transport == 'remote'
148
+ 'remote'
149
+ else
150
+ @uri_obj.scheme || config_transport
151
+ end
163
152
  end
153
+
154
+ @transport
164
155
  end
165
156
 
166
157
  def remote?
167
- @uri_obj.scheme == 'remote' || transport_config_cache['transport'] == 'remote'
158
+ transport == 'remote'
168
159
  end
169
160
 
170
161
  def user
@@ -176,13 +167,19 @@ module Bolt
176
167
  end
177
168
 
178
169
  def options
179
- transport_config.dup
170
+ transport_config
180
171
  end
181
172
 
182
173
  # We only want to look up transport config keys for the configured
183
174
  # transport
184
175
  def transport_config
185
- transport_config_cache['transports'][transport.to_sym]
176
+ if @transport_config.nil?
177
+ config = @inventory.config[transport]
178
+ .merge(group_cache.dig('config', transport), @config[transport])
179
+ @transport_config = config
180
+ end
181
+
182
+ @transport_config
186
183
  end
187
184
 
188
185
  def config
@@ -126,14 +126,14 @@ module Bolt
126
126
  # we can just add it first.
127
127
  plugins.add_plugin(Bolt::Plugin::Puppetdb.new(pdb_client))
128
128
 
129
- # Initialize any plugins referenced in config. This will also indirectly
129
+ # Initialize any plugins referenced in plugin config. This will also indirectly
130
130
  # initialize any plugins they depend on.
131
131
  if plugins.reference?(config.plugins)
132
132
  msg = "The 'plugins' setting cannot be set by a plugin reference"
133
133
  raise PluginError.new(msg, 'bolt/plugin-error')
134
134
  end
135
135
 
136
- config.plugins.keys.each do |plugin|
136
+ config.plugins.each_key do |plugin|
137
137
  plugins.by_name(plugin)
138
138
  end
139
139
 
@@ -263,9 +263,9 @@ module Bolt
263
263
  if data.is_a?(Array)
264
264
  data.flat_map { |elem| resolve_top_level_references(elem) }
265
265
  elsif reference?(data)
266
- partially_resolved = data.map do |k, v|
267
- [k, resolve_references(v)]
268
- end.to_h
266
+ partially_resolved = data.transform_values do |v|
267
+ resolve_references(v)
268
+ end
269
269
  fully_resolved = resolve_single_reference(partially_resolved)
270
270
  # The top-level reference may have returned more references, so repeat the process
271
271
  resolve_top_level_references(fully_resolved)
@@ -91,7 +91,7 @@ module Bolt
91
91
  end
92
92
 
93
93
  def validate_config(config, config_schema)
94
- config.keys.each do |key|
94
+ config.each_key do |key|
95
95
  msg = "Config for #{name} plugin contains unexpected key #{key}"
96
96
  raise Bolt::ValidationError, msg unless config_schema.include?(key)
97
97
  end
@@ -14,7 +14,7 @@ module Bolt
14
14
  end
15
15
  end
16
16
 
17
- config.keys.each do |key|
17
+ config.each_key do |key|
18
18
  unless known_keys.include?(key)
19
19
  raise Bolt::ValidationError, "Unpexpected key in pkcs7 plugin config: #{key}"
20
20
  end
@@ -78,7 +78,7 @@ module Bolt
78
78
 
79
79
  def _pcore_init_from_hash(init_hash)
80
80
  opts = init_hash.reject { |k, _v| k == 'target' }
81
- initialize(init_hash['target'], opts.map { |k, v| [k.to_sym, v] }.to_h)
81
+ initialize(init_hash['target'], opts.transform_keys(&:to_sym))
82
82
  end
83
83
 
84
84
  def _pcore_init_hash
@@ -76,7 +76,7 @@ module Bolt
76
76
  end
77
77
 
78
78
  def to_h
79
- options.merge(
79
+ options.to_h.merge(
80
80
  'name' => name,
81
81
  'uri' => uri,
82
82
  'protocol' => protocol,
@@ -92,7 +92,10 @@ module Bolt
92
92
  'name' => name,
93
93
  'uri' => uri,
94
94
  'alias' => target_alias,
95
- 'config' => Bolt::Util.deep_merge(config, 'transport' => transport, transport => options),
95
+ 'config' => {
96
+ 'transport' => transport,
97
+ transport => options.to_h
98
+ },
96
99
  'vars' => vars,
97
100
  'features' => features,
98
101
  'facts' => facts,
@@ -42,24 +42,6 @@ module Bolt
42
42
 
43
43
  attr_reader :logger
44
44
 
45
- # Returns options this transport supports
46
- def self.options
47
- raise NotImplementedError,
48
- "self.options() or self.filter_options(unfiltered) must be implemented by the transport class"
49
- end
50
-
51
- def self.default_options
52
- {}
53
- end
54
-
55
- def self.filter_options(unfiltered)
56
- unfiltered.select { |k| options.include?(k) }
57
- end
58
-
59
- def self.validate(_options)
60
- raise NotImplementedError, "self.validate() must be implemented by the transport class"
61
- end
62
-
63
45
  def initialize
64
46
  @logger = Logging.logger[self]
65
47
  end
@@ -7,36 +7,10 @@ require 'bolt/transport/base'
7
7
  module Bolt
8
8
  module Transport
9
9
  class Docker < Base
10
- OPTIONS = {
11
- "host" => "Host name.",
12
- "interpreters" => "A map of an extension name to the absolute path of an executable, "\
13
- "enabling you to override the shebang defined in a task executable. The "\
14
- "extension can optionally be specified with the `.` character (`.py` and "\
15
- "`py` both map to a task executable `task.py`) and the extension is case "\
16
- "sensitive. When a target's name is `localhost`, Ruby tasks run with the "\
17
- "Bolt Ruby interpreter by default.",
18
- "service-url" => "URL of the Docker host used for API requests.",
19
- "shell-command" => "A shell command to wrap any Docker exec commands in, such as `bash -lc`.",
20
- "tmpdir" => "The directory to upload and execute temporary files on the target.",
21
- "tty" => "Whether to enable tty on exec commands."
22
- }.freeze
23
-
24
- def self.options
25
- OPTIONS.keys
26
- end
27
-
28
10
  def provided_features
29
11
  ['shell']
30
12
  end
31
13
 
32
- def self.validate(options)
33
- if (url = options['service-url'])
34
- unless url.instance_of?(String)
35
- raise Bolt::ValidationError, 'service-url must be a string'
36
- end
37
- end
38
- end
39
-
40
14
  def with_connection(target)
41
15
  conn = Connection.new(target)
42
16
  conn.connect
@@ -3,40 +3,10 @@
3
3
  module Bolt
4
4
  module Transport
5
5
  class Local < Sudoable
6
- OPTIONS = {
7
- "interpreters" => "A map of an extension name to the absolute path of an executable, "\
8
- "enabling you to override the shebang defined in a task executable. The "\
9
- "extension can optionally be specified with the `.` character (`.py` and "\
10
- "`py` both map to a task executable `task.py`) and the extension is case "\
11
- "sensitive. When a target's name is `localhost`, Ruby tasks run with the "\
12
- "Bolt Ruby interpreter by default.",
13
- "run-as" => "A different user to run commands as after login.",
14
- "run-as-command" => "The command to elevate permissions. Bolt appends the user and command "\
15
- "strings to the configured `run-as-command` before running it on the target. "\
16
- "This command must not require an interactive password prompt, and the "\
17
- "`sudo-password` option is ignored when `run-as-command` is specified. The "\
18
- "`run-as-command` must be specified as an array.",
19
- "sudo-executable" => "The executable to use when escalating to the configured `run-as` user. This "\
20
- "is useful when you want to escalate using the configured `sudo-password`, since "\
21
- "`run-as-command` does not use `sudo-password` or support prompting. The command "\
22
- "executed on the target is `<sudo-executable> -S -u <user> -p custom_bolt_prompt "\
23
- "<command>`. **This option is experimental.**",
24
- "sudo-password" => "Password to use when changing users via `run-as`.",
25
- "tmpdir" => "The directory to copy and execute temporary files."
26
- }.freeze
27
-
28
- def self.options
29
- OPTIONS.keys
30
- end
31
-
32
6
  def provided_features
33
7
  ['shell']
34
8
  end
35
9
 
36
- def self.validate(options)
37
- validate_sudo_options(options)
38
- end
39
-
40
10
  def with_connection(target, *_args)
41
11
  conn = Shell.new(target)
42
12
  yield conn
@@ -12,27 +12,6 @@ require 'bolt/util'
12
12
  module Bolt
13
13
  module Transport
14
14
  class LocalWindows < Base
15
- OPTIONS = {
16
- "interpreters" => "A map of an extension name to the absolute path of an executable, "\
17
- "enabling you to override the shebang defined in a task executable. The "\
18
- "extension can optionally be specified with the `.` character (`.py` and "\
19
- "`py` both map to a task executable `task.py`) and the extension is case "\
20
- "sensitive. When a target's name is `localhost`, Ruby tasks run with the "\
21
- "Bolt Ruby interpreter by default.",
22
- "run-as" => "A different user to run commands as after login.",
23
- "run-as-command" => "The command to elevate permissions. Bolt appends the user and command "\
24
- "strings to the configured `run-as-command` before running it on the target. "\
25
- "This command must not require an interactive password prompt, and the "\
26
- "`sudo-password` option is ignored when `run-as-command` is specified. The "\
27
- "`run-as-command` must be specified as an array.",
28
- "sudo-password" => "Password to use when changing users via `run-as`.",
29
- "tmpdir" => "The directory to copy and execute temporary files."
30
- }.freeze
31
-
32
- def self.options
33
- OPTIONS.keys
34
- end
35
-
36
15
  def provided_features
37
16
  ['powershell']
38
17
  end
@@ -42,13 +21,6 @@ module Bolt
42
21
  input_method
43
22
  end
44
23
 
45
- def self.validate(options)
46
- logger = Logging.logger[self]
47
- if options['sudo-password'] || options['run-as'] || options['run-as-command'] || options[:run_as]
48
- logger.warn("run-as is not supported for Windows hosts using the local transport")
49
- end
50
- end
51
-
52
24
  def in_tmpdir(base)
53
25
  args = base ? [nil, base] : []
54
26
  dir = begin
@@ -98,14 +70,12 @@ module Bolt
98
70
  result_output
99
71
  end
100
72
 
101
- def upload(target, source, destination, options = {})
102
- self.class.validate(options)
73
+ def upload(target, source, destination, _options = {})
103
74
  copy_file(source, destination)
104
75
  Bolt::Result.for_upload(target, source, destination)
105
76
  end
106
77
 
107
- def run_command(target, command, options = {})
108
- self.class.validate(options)
78
+ def run_command(target, command, _options = {})
109
79
  in_tmpdir(target.options['tmpdir']) do |dir|
110
80
  output = execute(command, dir: dir)
111
81
  Bolt::Result.for_command(target,
@@ -116,8 +86,7 @@ module Bolt
116
86
  end
117
87
  end
118
88
 
119
- def run_script(target, script, arguments, options = {})
120
- self.class.validate(options)
89
+ def run_script(target, script, arguments, _options = {})
121
90
  with_tmpscript(File.absolute_path(script), target.options['tmpdir']) do |file, dir|
122
91
  logger.debug "Running '#{file}' with #{arguments.to_json}"
123
92
 
@@ -141,8 +110,7 @@ module Bolt
141
110
  end
142
111
  end
143
112
 
144
- def run_task(target, task, arguments, options = {})
145
- self.class.validate(options)
113
+ def run_task(target, task, arguments, _options = {})
146
114
  implementation = select_implementation(target, task)
147
115
  executable = implementation['path']
148
116
  input_method = implementation['input_method']