Class: Train::Plugins::Transport::BaseConnection

Inherits:
Object
  • Object
show all
Includes:
Extras
Defined in:
lib/train/plugins/base_connection.rb

Overview

A Connection instance can be generated and re-generated, given new connection details such as connection port, hostname, credentials, etc. This object is responsible for carrying out the actions on the remote host such as executing commands, transferring files, etc.

Author:

Instance Method Summary collapse

Constructor Details

#initialize(options = nil) {|self| ... } ⇒ BaseConnection

Create a new Connection instance.

Parameters:

  • options (Hash) (defaults to: nil)

    connection options

Yields:

  • (self)

    yields itself for block-style invocation



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/train/plugins/base_connection.rb', line 22

def initialize(options = nil)
  @options = options || {}

  @logger = @options.delete(:logger) || Logger.new($stdout, level: :fatal)
  Train::Platforms::Detect::Specifications::OS.load
  Train::Platforms::Detect::Specifications::Api.load

  # In run_command all options are not accessible as some of them gets deleted in transit.
  # To make the data like hostname, username available to aduit logs dup the options
  @audit_log_data = options.dup || {}
  # For transport other than local all audit log options accessible inside transport_options key
  if !@options.empty? && @options[:transport_options] && @options[:transport_options][:enable_audit_log]
    @audit_log = Train::AuditLog.create(options[:transport_options])
  elsif !@options.empty? && @options[:enable_audit_log]
    @audit_log = Train::AuditLog.create(@options)
  end

  # default caching options
  @cache_enabled = {
    file: true,
    command: false,
    api_call: false,
  }

  @cache = {}
  @cache_enabled.each_key do |type|
    clear_cache(type)
  end
end

Instance Method Details

#backend_typeObject



121
122
123
# File 'lib/train/plugins/base_connection.rb', line 121

def backend_type
  @options[:backend] || "unknown"
end

#cache_enabled?(type) ⇒ Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/train/plugins/base_connection.rb', line 75

def cache_enabled?(type)
  @cache_enabled[type.to_sym]
end

#cached_client(type, key) ⇒ Object

Returns cached client if caching enabled. Otherwise returns whatever is given in the block.

Examples:


def demo_client
  cached_client(:api_call, :demo_connection) do
    DemoClient.new(args)
  end
end

Parameters:

  • type (symbol)

    one of [:api_call, :file, :command]

  • key (symbol)

    for your cached connection



69
70
71
72
73
# File 'lib/train/plugins/base_connection.rb', line 69

def cached_client(type, key)
  return yield unless cache_enabled?(type)

  @cache[type][key] ||= yield
end

#closeObject

Closes the session connection, if it is still active.



95
96
97
# File 'lib/train/plugins/base_connection.rb', line 95

def close
  # this method may be left unimplemented if that is applicable
end

#disable_cache(type) ⇒ Object



87
88
89
90
91
92
# File 'lib/train/plugins/base_connection.rb', line 87

def disable_cache(type)
  raise Train::UnknownCacheType, "#{type} is not a valid cache type" unless @cache_enabled.keys.include?(type.to_sym)

  @cache_enabled[type.to_sym] = false
  clear_cache(type.to_sym)
end

#download(remotes, local) ⇒ Object

Download remote files or directories to local host.

Parameters:

  • remotes (Array<String>)

    paths to remote files or directories

  • local (String)

    path to local destination. If ‘local` is an existing directory, `remote` will be downloaded into the directory using its original name

Raises:

  • (TransportFailed)

    if the files could not all be downloaded successfully, which may vary by implementation



211
212
213
214
215
216
217
218
219
220
221
# File 'lib/train/plugins/base_connection.rb', line 211

def download(remotes, local)
  FileUtils.mkdir_p(File.dirname(local))

  Array(remotes).each do |remote|
    new_content = file(remote).content
    local_file = File.join(local, File.basename(remote))
    logger.debug("Attempting to download '#{remote}' as file #{local_file}")

    File.open(local_file, "w") { |fp| fp.write(new_content) }
  end
end

#enable_cache(type) ⇒ Object

Enable caching types for Train. Currently we support :api_call, :file and :command types



81
82
83
84
85
# File 'lib/train/plugins/base_connection.rb', line 81

def enable_cache(type)
  raise Train::UnknownCacheType, "#{type} is not a valid cache type" unless @cache_enabled.keys.include?(type.to_sym)

  @cache_enabled[type.to_sym] = true
end

#file(path, *args) ⇒ Object

This is the main file call for all connections. This will call the private file_via_connection on the connection with optional caching



174
175
176
177
178
179
# File 'lib/train/plugins/base_connection.rb', line 174

def file(path, *args)
  @audit_log.info({ type: "file", path: "#{path}", user: @audit_log_data[:username], hostname: @audit_log_data[:hostname] }) if @audit_log
  return file_via_connection(path, *args) unless cache_enabled?(:file)

  @cache[:file][path] ||= file_via_connection(path, *args)
end

#force_platform!(name, platform_details = nil) ⇒ Object Also known as: direct_platform



112
113
114
115
116
117
118
119
# File 'lib/train/plugins/base_connection.rb', line 112

def force_platform!(name, platform_details = nil)
  plat = Train::Platforms.name(name)
  plat.backend = self
  plat.platform = platform_details unless platform_details.nil?
  plat.find_family_hierarchy
  plat.add_platform_methods
  plat
end

#inspectObject



125
126
127
# File 'lib/train/plugins/base_connection.rb', line 125

def inspect
  "%s[%s]" % [self.class, backend_type]
end

#load_json(j) ⇒ Object



105
106
107
108
109
110
# File 'lib/train/plugins/base_connection.rb', line 105

def load_json(j)
  require_relative "../transports/mock"
  j["files"].each do |path, jf|
    @cache[:file][path] = Train::Transports::Mock::Connection::File.from_json(jf)
  end
end

#login_commandLoginCommand

Builds a LoginCommand which can be used to open an interactive session on the remote host.

Returns:

Raises:

  • (NotImplementedError)


227
228
229
# File 'lib/train/plugins/base_connection.rb', line 227

def 
  raise NotImplementedError, "#{self.class} does not implement #login_command()"
end

#platformPlatform Also known as: os

Get information on the operating system which this transport connects to.

Returns:

  • (Platform)

    system information



134
135
136
# File 'lib/train/plugins/base_connection.rb', line 134

def platform
  @platform ||= Train::Platforms::Detect.scan(self)
end

#run_command(cmd, opts = {}, &data_handler) ⇒ Object

This is the main command call for all connections. This will call the private run_command_via_connection on the connection with optional caching

This command accepts an optional data handler block. When provided, inbound data will be published vi ‘data_handler.call(data)`. This can allow callers to receive and render updates from remote command execution.

Parameters:

  • the (String)

    command to run

  • optional (Hash)

    hash of options for this command. The derived connection class’s implementation of run_command_via_connection should receive and apply these options.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/train/plugins/base_connection.rb', line 151

def run_command(cmd, opts = {}, &data_handler)
  # Some implementations do not accept an opts argument.
  # We cannot update all implementations to accept opts due to them being separate plugins.
  # Therefore here we check the implementation's arity to maintain compatibility.
  @audit_log.info({ type: "cmd", command: "#{cmd}", user: @audit_log_data[:username], hostname: @audit_log_data[:hostname] }) if @audit_log

  case method(:run_command_via_connection).arity.abs
  when 1
    return run_command_via_connection(cmd, &data_handler) unless cache_enabled?(:command)

    @cache[:command][cmd] ||= run_command_via_connection(cmd, &data_handler)

  when 2
    return run_command_via_connection(cmd, opts, &data_handler) unless cache_enabled?(:command)

    @cache[:command][cmd] ||= run_command_via_connection(cmd, opts, &data_handler)
  else
    raise NotImplementedError, "#{self.class} does not implement run_command_via_connection with arity #{method(:run_command_via_connection).arity}"
  end
end

#to_jsonObject



99
100
101
102
103
# File 'lib/train/plugins/base_connection.rb', line 99

def to_json
  {
    "files" => Hash[@cache[:file].map { |x, y| [x, y.to_json] }],
  }
end

#upload(locals, remote) ⇒ Object

Uploads local files to remote host.

Parameters:

  • locals (String, Array<String>)

    path to local files

  • remote (String)

    path to remote destination

Raises:

  • (TransportFailed)

    if the files could not all be uploaded successfully, which may vary by implementation



187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/train/plugins/base_connection.rb', line 187

def upload(locals, remote)
  remote_directory = file(remote).directory?

  if locals.is_a?(Array) && !remote_directory
    raise Train::TransportError, "#{self.class} expects remote directory as second upload parameter for multi-file uploads"
  end

  Array(locals).each do |local|
    remote_file = remote_directory ? File.join(remote, File.basename(local)) : remote
    @audit_log.info({ type: "file upload", source: local, destination: remote_file, user: @audit_log_data[:username], hostname: @audit_log_data[:hostname] }) if @audit_log
    logger.debug("Attempting to upload '#{local}' as file #{remote_file}")

    file(remote_file).content = File.read(local)
  end
end

#wait_until_readyObject

Block and return only when the remote host is prepared and ready to execute command and upload files. The semantics and details will vary by implementation, but a round trip through the hosted service is preferred to simply waiting on a socket to become available.



236
237
238
# File 'lib/train/plugins/base_connection.rb', line 236

def wait_until_ready
  # this method may be left unimplemented if that is applicable log
end

#with_sudo_ptyObject



52
53
54
# File 'lib/train/plugins/base_connection.rb', line 52

def with_sudo_pty
  yield
end