Class: Bolt::Transport::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/bolt/transport/base.rb

Overview

This class provides the default behavior for Transports. A Transport is responsible for uploading files and running commands, scripts, and tasks on Targets.

Bolt executes work on the Transport in “batches”. To do that, it calls the batches() method, which is responsible for dividing the list of Targets into batches according to how it wants to handle them. It will then call Transport#batch_task, or the corresponding method for another operation, passing a list of Targets. The Transport returns a list of Bolt::Result objects, one per Target. Each batch is executed on a separate thread, controlled by the `concurrency` setting, so many batches may be running in parallel.

The default batch implementation splits the list of Targets into batches of 1. It then calls run_task(), or a corresponding method for other operations, passing in the single Target.

Most Transport implementations, like the SSH and WinRM transports, don't need to do their own batching, since they only operate on a single Target at a time. Those Transports can implement the run_task() and related methods, which will automatically handle running many Targets in parallel, and will handle publishing start and finish events for each Target.

Transports that need their own batching, like the Orch transport, can instead override the batches() method to split Targets into sets that can be executed together, and override the batch_task() and related methods to execute a batch of targets. In that case, those Transports should accept a block argument and call it with a :node_start event for each Target before executing, and a :node_result event for each Target after execution.

Direct Known Subclasses

Orch, Remote, Simple

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeBase

Returns a new instance of Base.


42
43
44
# File 'lib/bolt/transport/base.rb', line 42

def initialize
  @logger = Bolt::Logger.logger(self)
end

Instance Attribute Details

#loggerObject (readonly)

Returns the value of attribute logger.


40
41
42
# File 'lib/bolt/transport/base.rb', line 40

def logger
  @logger
end

Instance Method Details

#assert_batch_size_one(method, targets) ⇒ Object

Raises an error if more than one target was given in the batch.

The default implementations of batch_* strictly assume the transport is using the default batch size of 1. This method ensures that is the case and raises an error if it's not.


82
83
84
85
86
87
# File 'lib/bolt/transport/base.rb', line 82

def assert_batch_size_one(method, targets)
  if targets.length > 1
    message = "#{self.class.name} must implement #{method} to support batches (got #{targets.length} targets)"
    raise NotImplementedError, message
  end
end

#batch_command(targets, command, options = {}, position = [], &callback) ⇒ Object

Runs the given command on a batch of targets.

The default implementation only supports batches of size 1 and will fail otherwise.

Transports may override this method to implement their own batch processing.


124
125
126
127
128
129
130
131
# File 'lib/bolt/transport/base.rb', line 124

def batch_command(targets, command, options = {}, position = [], &callback)
  assert_batch_size_one("batch_command()", targets)
  target = targets.first
  with_events(target, callback, 'command', position) do
    @logger.debug("Running command '#{command}' on #{target.safe_name}")
    run_command(target, command, options, position)
  end
end

#batch_connected?(targets) ⇒ Boolean

Returns:

  • (Boolean)

180
181
182
183
# File 'lib/bolt/transport/base.rb', line 180

def batch_connected?(targets)
  assert_batch_size_one("connected?()", targets)
  connected?(targets.first)
end

#batch_download(targets, source, destination, options = {}, position = [], &callback) ⇒ Object

Downloads the given source file from a batch of targets to the destination location on the host.

The default implementation only supports batches of size 1 and will fail otherwise.

Transports may override this method to implement their own batch processing.


167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/bolt/transport/base.rb', line 167

def batch_download(targets, source, destination, options = {}, position = [], &callback)
  require 'erb'

  assert_batch_size_one("batch_download()", targets)
  target = targets.first
  with_events(target, callback, 'download', position) do
    escaped_name = ERB::Util.url_encode(target.safe_name)
    target_destination = File.expand_path(escaped_name, destination)
    @logger.debug { "Downloading: '#{source}' on #{target.safe_name} to #{target_destination}" }
    download(target, source, target_destination, options)
  end
end

#batch_script(targets, script, arguments, options = {}, position = [], &callback) ⇒ Object

Runs the given script on a batch of targets.

The default implementation only supports batches of size 1 and will fail otherwise.

Transports may override this method to implement their own batch processing.


138
139
140
141
142
143
144
145
# File 'lib/bolt/transport/base.rb', line 138

def batch_script(targets, script, arguments, options = {}, position = [], &callback)
  assert_batch_size_one("batch_script()", targets)
  target = targets.first
  with_events(target, callback, 'script', position) do
    @logger.debug { "Running script '#{script}' on #{target.safe_name}" }
    run_script(target, script, arguments, options, position)
  end
end

#batch_task(targets, task, arguments, options = {}, position = [], &callback) ⇒ Object

Runs the given task on a batch of targets.

The default implementation only supports batches of size 1 and will fail otherwise.

Transports may override this method to implement their own batch processing.


94
95
96
97
98
99
100
101
# File 'lib/bolt/transport/base.rb', line 94

def batch_task(targets, task, arguments, options = {}, position = [], &callback)
  assert_batch_size_one("batch_task()", targets)
  target = targets.first
  with_events(target, callback, 'task', position) do
    @logger.debug { "Running task '#{task.name}' on #{target.safe_name}" }
    run_task(target, task, arguments, options, position)
  end
end

#batch_task_with(targets, task, target_mapping, options = {}, position = [], &callback) ⇒ Object

Runs the given task on a batch of targets with variable parameters.

The default implementation only supports batches of size 1 and will fail otherwise.

Transports may override this method to implment their own batch processing.


108
109
110
111
112
113
114
115
116
117
# File 'lib/bolt/transport/base.rb', line 108

def batch_task_with(targets, task, target_mapping, options = {}, position = [], &callback)
  assert_batch_size_one("batch_task_with()", targets)
  target = targets.first
  arguments = target_mapping[target]

  with_events(target, callback, 'task', position) do
    @logger.debug { "Running task '#{task.name}' on #{target.safe_name} with '#{arguments.to_json}'" }
    run_task(target, task, arguments, options, position)
  end
end

#batch_upload(targets, source, destination, options = {}, position = [], &callback) ⇒ Object

Uploads the given source file to the destination location on a batch of targets.

The default implementation only supports batches of size 1 and will fail otherwise.

Transports may override this method to implement their own batch processing.


152
153
154
155
156
157
158
159
# File 'lib/bolt/transport/base.rb', line 152

def batch_upload(targets, source, destination, options = {}, position = [], &callback)
  assert_batch_size_one("batch_upload()", targets)
  target = targets.first
  with_events(target, callback, 'upload', position) do
    @logger.debug { "Uploading: '#{source}' to #{destination} on #{target.safe_name}" }
    upload(target, source, destination, options)
  end
end

#batches(targets) ⇒ Object

Split the given list of targets into a list of batches. The default implementation returns single-target batches.

Transports may override this method, and the corresponding batch_* methods, to implement their own batch processing.


190
191
192
# File 'lib/bolt/transport/base.rb', line 190

def batches(targets)
  targets.map { |target| [target] }
end

#connected?(_targets) ⇒ Boolean

Transports should override this method with their own implementation of a connection test.

Returns:

  • (Boolean)

Raises:

  • (NotImplementedError)

220
221
222
# File 'lib/bolt/transport/base.rb', line 220

def connected?(_targets)
  raise NotImplementedError, "connected?() must be implemented by the transport class"
end

#default_input_method(_executable) ⇒ Object


63
64
65
# File 'lib/bolt/transport/base.rb', line 63

def default_input_method(_executable)
  'both'
end

#download(*_args) ⇒ Object

Transports should override this method with their own implementation of file download.

Raises:

  • (NotImplementedError)

215
216
217
# File 'lib/bolt/transport/base.rb', line 215

def download(*_args)
  raise NotImplementedError, "download() must be implemented by the transport class"
end

#provided_featuresObject


59
60
61
# File 'lib/bolt/transport/base.rb', line 59

def provided_features
  []
end

#run_command(*_args) ⇒ Object

Transports should override this method with their own implementation of running a command.

Raises:

  • (NotImplementedError)

195
196
197
# File 'lib/bolt/transport/base.rb', line 195

def run_command(*_args)
  raise NotImplementedError, "run_command() must be implemented by the transport class"
end

#run_script(*_args) ⇒ Object

Transports should override this method with their own implementation of running a script.

Raises:

  • (NotImplementedError)

200
201
202
# File 'lib/bolt/transport/base.rb', line 200

def run_script(*_args)
  raise NotImplementedError, "run_script() must be implemented by the transport class"
end

#run_task(*_args) ⇒ Object

Transports should override this method with their own implementation of running a task.

Raises:

  • (NotImplementedError)

205
206
207
# File 'lib/bolt/transport/base.rb', line 205

def run_task(*_args)
  raise NotImplementedError, "run_task() must be implemented by the transport class"
end

#select_implementation(target, task) ⇒ Object


67
68
69
70
71
# File 'lib/bolt/transport/base.rb', line 67

def select_implementation(target, task)
  impl = task.select_implementation(target, provided_features)
  impl['input_method'] ||= default_input_method(impl['path'])
  impl
end

#select_interpreter(executable, interpreters) ⇒ Object


73
74
75
# File 'lib/bolt/transport/base.rb', line 73

def select_interpreter(executable, interpreters)
  interpreters[Pathname(executable).extname] if interpreters
end

#unwrap_sensitive_args(arguments) ⇒ Object

Unwraps any Sensitive data in an arguments Hash, so the plain-text is passed to the Task/Script.

This works on deeply nested data structures composed of Hashes, Arrays, and and plain-old data types (int, string, etc).


229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/bolt/transport/base.rb', line 229

def unwrap_sensitive_args(arguments)
  # Skip this if Puppet isn't loaded
  return arguments unless defined?(Puppet::Pops::Types::PSensitiveType::Sensitive)

  case arguments
  when Array
    # iterate over the array, unwrapping all elements
    arguments.map { |x| unwrap_sensitive_args(x) }
  when Hash
    # iterate over the arguments hash and unwrap all keys and values
    arguments.each_with_object({}) { |(k, v), h|
      h[unwrap_sensitive_args(k)] = unwrap_sensitive_args(v)
    }
  when Puppet::Pops::Types::PSensitiveType::Sensitive
    # this value is Sensitive, unwrap it
    unwrap_sensitive_args(arguments.unwrap)
  else
    # unknown data type, just return it
    arguments
  end
end

#upload(*_args) ⇒ Object

Transports should override this method with their own implementation of file upload.

Raises:

  • (NotImplementedError)

210
211
212
# File 'lib/bolt/transport/base.rb', line 210

def upload(*_args)
  raise NotImplementedError, "upload() must be implemented by the transport class"
end

#with_events(target, callback, action, position) ⇒ Object


46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/bolt/transport/base.rb', line 46

def with_events(target, callback, action, position)
  callback&.call(type: :node_start, target: target)

  result = begin
    yield
  rescue StandardError, NotImplementedError => e
    Bolt::Result.from_exception(target, e, action: action, position: position)
  end

  callback&.call(type: :node_result, result: result)
  result
end