train 0.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +71 -0
- data/CHANGELOG.md +308 -0
- data/Gemfile +30 -0
- data/LICENSE +201 -0
- data/README.md +156 -0
- data/Rakefile +148 -0
- data/lib/train.rb +117 -0
- data/lib/train/errors.rb +23 -0
- data/lib/train/extras.rb +17 -0
- data/lib/train/extras/command_wrapper.rb +148 -0
- data/lib/train/extras/file_aix.rb +20 -0
- data/lib/train/extras/file_common.rb +161 -0
- data/lib/train/extras/file_linux.rb +16 -0
- data/lib/train/extras/file_unix.rb +79 -0
- data/lib/train/extras/file_windows.rb +91 -0
- data/lib/train/extras/linux_lsb.rb +60 -0
- data/lib/train/extras/os_common.rb +136 -0
- data/lib/train/extras/os_detect_darwin.rb +32 -0
- data/lib/train/extras/os_detect_linux.rb +148 -0
- data/lib/train/extras/os_detect_unix.rb +99 -0
- data/lib/train/extras/os_detect_windows.rb +57 -0
- data/lib/train/extras/stat.rb +133 -0
- data/lib/train/options.rb +80 -0
- data/lib/train/plugins.rb +40 -0
- data/lib/train/plugins/base_connection.rb +86 -0
- data/lib/train/plugins/transport.rb +49 -0
- data/lib/train/transports/docker.rb +103 -0
- data/lib/train/transports/local.rb +52 -0
- data/lib/train/transports/local_file.rb +90 -0
- data/lib/train/transports/local_os.rb +51 -0
- data/lib/train/transports/mock.rb +147 -0
- data/lib/train/transports/ssh.rb +163 -0
- data/lib/train/transports/ssh_connection.rb +225 -0
- data/lib/train/transports/winrm.rb +184 -0
- data/lib/train/transports/winrm_connection.rb +194 -0
- data/lib/train/version.rb +7 -0
- data/test/integration/.kitchen.yml +43 -0
- data/test/integration/Berksfile +3 -0
- data/test/integration/bootstrap.sh +17 -0
- data/test/integration/chefignore +1 -0
- data/test/integration/cookbooks/test/metadata.rb +1 -0
- data/test/integration/cookbooks/test/recipes/default.rb +100 -0
- data/test/integration/cookbooks/test/recipes/prep_files.rb +47 -0
- data/test/integration/docker_run.rb +153 -0
- data/test/integration/docker_test.rb +24 -0
- data/test/integration/docker_test_container.rb +24 -0
- data/test/integration/helper.rb +61 -0
- data/test/integration/sudo/customcommand.rb +15 -0
- data/test/integration/sudo/nopasswd.rb +16 -0
- data/test/integration/sudo/passwd.rb +21 -0
- data/test/integration/sudo/reqtty.rb +17 -0
- data/test/integration/sudo/run_as.rb +12 -0
- data/test/integration/test-travis-1.yaml +13 -0
- data/test/integration/test-travis-2.yaml +13 -0
- data/test/integration/test_local.rb +19 -0
- data/test/integration/test_ssh.rb +39 -0
- data/test/integration/tests/path_block_device_test.rb +74 -0
- data/test/integration/tests/path_character_device_test.rb +74 -0
- data/test/integration/tests/path_file_test.rb +79 -0
- data/test/integration/tests/path_folder_test.rb +90 -0
- data/test/integration/tests/path_missing_test.rb +77 -0
- data/test/integration/tests/path_pipe_test.rb +78 -0
- data/test/integration/tests/path_symlink_test.rb +95 -0
- data/test/integration/tests/run_command_test.rb +28 -0
- data/test/unit/extras/command_wrapper_test.rb +78 -0
- data/test/unit/extras/file_common_test.rb +180 -0
- data/test/unit/extras/linux_file_test.rb +167 -0
- data/test/unit/extras/os_common_test.rb +269 -0
- data/test/unit/extras/os_detect_linux_test.rb +189 -0
- data/test/unit/extras/os_detect_windows_test.rb +99 -0
- data/test/unit/extras/stat_test.rb +148 -0
- data/test/unit/extras/windows_file_test.rb +44 -0
- data/test/unit/helper.rb +7 -0
- data/test/unit/plugins/connection_test.rb +44 -0
- data/test/unit/plugins/transport_test.rb +111 -0
- data/test/unit/plugins_test.rb +22 -0
- data/test/unit/train_test.rb +156 -0
- data/test/unit/transports/local_file_test.rb +184 -0
- data/test/unit/transports/local_test.rb +87 -0
- data/test/unit/transports/mock_test.rb +87 -0
- data/test/unit/transports/ssh_test.rb +109 -0
- data/test/unit/version_test.rb +8 -0
- data/test/windows/local_test.rb +46 -0
- data/test/windows/winrm_test.rb +52 -0
- data/train.gemspec +38 -0
- metadata +295 -0
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
#
|
5
|
+
# This is heavily based on:
|
6
|
+
#
|
7
|
+
# OHAI https://github.com/chef/ohai
|
8
|
+
# by Adam Jacob, Chef Software Inc
|
9
|
+
#
|
10
|
+
module Train::Extras
|
11
|
+
module DetectWindows
|
12
|
+
def detect_windows
|
13
|
+
res = @backend.run_command('cmd /c ver')
|
14
|
+
return false if res.exit_status != 0 or res.stdout.empty?
|
15
|
+
|
16
|
+
# if the ver contains `Windows`, we know its a Windows system
|
17
|
+
version = res.stdout.strip
|
18
|
+
return false unless version.downcase =~ /windows/
|
19
|
+
@platform[:family] = 'windows'
|
20
|
+
|
21
|
+
# try to extract release from eg. `Microsoft Windows [Version 6.3.9600]`
|
22
|
+
release = /\[(?<name>.*)\]/.match(version)
|
23
|
+
unless release[:name].nil?
|
24
|
+
# release is 6.3.9600 now
|
25
|
+
@platform[:release] = release[:name].downcase.gsub('version', '').strip
|
26
|
+
# fallback, if we are not able to extract the name from wmic later
|
27
|
+
@platform[:name] = "Windows #{@platform[:release]}"
|
28
|
+
end
|
29
|
+
|
30
|
+
# try to use wmic, but lets keep it optional
|
31
|
+
read_wmic
|
32
|
+
|
33
|
+
true
|
34
|
+
end
|
35
|
+
|
36
|
+
# reads os name and version from wmic
|
37
|
+
# @see https://msdn.microsoft.com/en-us/library/bb742610.aspx#EEAA
|
38
|
+
# Thanks to Matt Wrock (https://github.com/mwrock) for this hint
|
39
|
+
def read_wmic
|
40
|
+
res = @backend.run_command('wmic os get * /format:list')
|
41
|
+
if res.exit_status == 0
|
42
|
+
sys_info = {}
|
43
|
+
res.stdout.lines.each { |line|
|
44
|
+
m = /^\s*([^=]*?)\s*=\s*(.*?)\s*$/.match(line)
|
45
|
+
sys_info[m[1].to_sym] = m[2] unless m.nil? || m[1].nil?
|
46
|
+
}
|
47
|
+
|
48
|
+
@platform[:release] = sys_info[:Version]
|
49
|
+
# additional info on windows
|
50
|
+
@platform[:build] = sys_info[:BuildNumber]
|
51
|
+
@platform[:name] = sys_info[:Caption]
|
52
|
+
@platform[:name] = @platform[:name].gsub('Microsoft', '').strip unless @platform[:name].empty?
|
53
|
+
@platform[:arch] = sys_info[:OSArchitecture]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
module Train::Extras
|
6
|
+
class Stat
|
7
|
+
TYPES = {
|
8
|
+
socket: 00140000,
|
9
|
+
symlink: 00120000,
|
10
|
+
file: 00100000,
|
11
|
+
block_device: 00060000,
|
12
|
+
directory: 00040000,
|
13
|
+
character_device: 00020000,
|
14
|
+
pipe: 00010000,
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def self.find_type(mode)
|
18
|
+
res = TYPES.find { |_, mask| mask & mode == mask }
|
19
|
+
res.nil? ? :unknown : res[0]
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.stat(shell_escaped_path, backend, follow_symlink)
|
23
|
+
# use perl scripts for aix and solaris 10
|
24
|
+
if backend.os.aix? || (backend.os.solaris? && backend.os[:release].to_i < 11) || backend.os.hpux?
|
25
|
+
return aix_stat(shell_escaped_path, backend, follow_symlink)
|
26
|
+
end
|
27
|
+
return bsd_stat(shell_escaped_path, backend, follow_symlink) if backend.os.bsd?
|
28
|
+
# linux and solaris 11 will use standard linux stats
|
29
|
+
return linux_stat(shell_escaped_path, backend, follow_symlink) if backend.os.unix?
|
30
|
+
# all other cases we don't handle
|
31
|
+
# TODO: print an error if we get here, as it shouldn't be invoked
|
32
|
+
# on non-unix
|
33
|
+
{}
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.linux_stat(shell_escaped_path, backend, follow_symlink)
|
37
|
+
lstat = follow_symlink ? ' -L' : ''
|
38
|
+
res = backend.run_command("stat#{lstat} #{shell_escaped_path} 2>/dev/null --printf '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'")
|
39
|
+
|
40
|
+
# ignore the exit_code: it is != 0 if selinux labels are not supported
|
41
|
+
# on the system.
|
42
|
+
|
43
|
+
fields = res.stdout.split("\n")
|
44
|
+
return {} if fields.length != 9
|
45
|
+
|
46
|
+
tmask = fields[1].to_i(16)
|
47
|
+
selinux = fields[8]
|
48
|
+
selinux = nil if selinux == '?' or selinux == '(null)'
|
49
|
+
|
50
|
+
{
|
51
|
+
type: find_type(tmask),
|
52
|
+
mode: tmask & 07777,
|
53
|
+
owner: fields[2],
|
54
|
+
uid: fields[3].to_i,
|
55
|
+
group: fields[4],
|
56
|
+
gid: fields[5].to_i,
|
57
|
+
mtime: fields[7].to_i,
|
58
|
+
size: fields[0].to_i,
|
59
|
+
selinux_label: selinux,
|
60
|
+
}
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.bsd_stat(shell_escaped_path, backend, follow_symlink)
|
64
|
+
# From stat man page on FreeBSD:
|
65
|
+
# z The size of file in bytes (st_size).
|
66
|
+
# p File type and permissions (st_mode).
|
67
|
+
# u, g User ID and group ID of file's owner (st_uid, st_gid).
|
68
|
+
# a, m, c, B
|
69
|
+
# The time file was last accessed or modified, or when the
|
70
|
+
# inode was last changed, or the birth time of the inode
|
71
|
+
# (st_atime, st_mtime, st_ctime, st_birthtime).
|
72
|
+
#
|
73
|
+
# The special output specifier S may be used to indicate that the
|
74
|
+
# output, if applicable, should be in string format. May be used
|
75
|
+
# in combination with:
|
76
|
+
# ...
|
77
|
+
# gu Display group or user name.
|
78
|
+
lstat = follow_symlink ? ' -L' : ''
|
79
|
+
res = backend.run_command(
|
80
|
+
"stat#{lstat} -f '%z\n%p\n%Su\n%u\n%Sg\n%g\n%a\n%m' "\
|
81
|
+
"#{shell_escaped_path}")
|
82
|
+
|
83
|
+
return {} if res.exit_status != 0
|
84
|
+
|
85
|
+
fields = res.stdout.split("\n")
|
86
|
+
return {} if fields.length != 8
|
87
|
+
|
88
|
+
tmask = fields[1].to_i(8)
|
89
|
+
|
90
|
+
{
|
91
|
+
type: find_type(tmask),
|
92
|
+
mode: tmask & 07777,
|
93
|
+
owner: fields[2],
|
94
|
+
uid: fields[3].to_i,
|
95
|
+
group: fields[4],
|
96
|
+
gid: fields[5].to_i,
|
97
|
+
mtime: fields[7].to_i,
|
98
|
+
size: fields[0].to_i,
|
99
|
+
selinux_label: fields[8],
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.aix_stat(shell_escaped_path, backend, follow_symlink)
|
104
|
+
# Perl here b/c it is default on AIX
|
105
|
+
lstat = follow_symlink ? 'lstat' : 'stat'
|
106
|
+
stat_cmd = <<-EOP
|
107
|
+
perl -e '
|
108
|
+
@a = #{lstat}(shift) or exit 2;
|
109
|
+
$u = getpwuid($a[4]);
|
110
|
+
$g = getgrgid($a[5]);
|
111
|
+
printf("0%o\\n%s\\n%d\\n%s\\n%d\\n%d\\n%d\\n", $a[2], $u, $a[4], $u, $a[5], $a[9], $a[7])
|
112
|
+
' #{shell_escaped_path}
|
113
|
+
EOP
|
114
|
+
|
115
|
+
res = backend.run_command(stat_cmd)
|
116
|
+
return {} if res.exit_status != 0
|
117
|
+
fields = res.stdout.split("\n")
|
118
|
+
return {} if fields.length != 7
|
119
|
+
tmask = fields[0].to_i(8)
|
120
|
+
{
|
121
|
+
type: find_type(tmask),
|
122
|
+
mode: tmask & 07777,
|
123
|
+
owner: fields[1],
|
124
|
+
uid: fields[2].to_i,
|
125
|
+
group: fields[3],
|
126
|
+
gid: fields[4].to_i,
|
127
|
+
mtime: fields[5].to_i,
|
128
|
+
size: fields[6].to_i,
|
129
|
+
selinux_label: nil,
|
130
|
+
}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
|
+
# Author:: Christoph Hartmann (<chris@lollyrock.com>)
|
5
|
+
|
6
|
+
module Train
|
7
|
+
module Options
|
8
|
+
def self.attach(target)
|
9
|
+
target.class.method(:include).call(ClassOptions)
|
10
|
+
target.method(:include).call(InstanceOptions)
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassOptions
|
14
|
+
def option(name, conf = nil, &block)
|
15
|
+
d = conf || {}
|
16
|
+
unless d.is_a? Hash
|
17
|
+
fail Train::ClientError,
|
18
|
+
"The transport plugin #{self} declared an option #{name} "\
|
19
|
+
"and didn't provide a valid configuration hash."
|
20
|
+
end
|
21
|
+
|
22
|
+
if !conf.nil? and !conf[:default].nil? and block_given?
|
23
|
+
fail Train::ClientError,
|
24
|
+
"The transport plugin #{self} declared an option #{name} "\
|
25
|
+
'with both a default value and block. Only use one of these.'
|
26
|
+
end
|
27
|
+
|
28
|
+
d[:default] = block if block_given?
|
29
|
+
|
30
|
+
default_options[name] = d
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_options
|
34
|
+
@default_options = {} unless defined? @default_options
|
35
|
+
@default_options
|
36
|
+
end
|
37
|
+
|
38
|
+
def include_options(other)
|
39
|
+
unless other.respond_to?(:default_options)
|
40
|
+
fail "Trying to include options from module #{other.inspect}, "\
|
41
|
+
"which doesn't seem to support options."
|
42
|
+
end
|
43
|
+
default_options.merge!(other.default_options)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module InstanceOptions
|
48
|
+
# @return [Hash] options, which created this Transport
|
49
|
+
attr_reader :options
|
50
|
+
|
51
|
+
def default_options
|
52
|
+
self.class.default_options
|
53
|
+
end
|
54
|
+
|
55
|
+
def merge_options(base, opts)
|
56
|
+
res = base.merge(opts || {})
|
57
|
+
default_options.each do |field, hm|
|
58
|
+
next unless res[field].nil? and hm.key?(:default)
|
59
|
+
default = hm[:default]
|
60
|
+
if default.is_a? Proc
|
61
|
+
res[field] = default.call(res)
|
62
|
+
else
|
63
|
+
res[field] = default
|
64
|
+
end
|
65
|
+
end
|
66
|
+
res
|
67
|
+
end
|
68
|
+
|
69
|
+
def validate_options(opts)
|
70
|
+
default_options.each do |field, hm|
|
71
|
+
if opts[field].nil? and hm[:required]
|
72
|
+
fail Train::ClientError,
|
73
|
+
"You must provide a value for #{field.to_s.inspect}."
|
74
|
+
end
|
75
|
+
end
|
76
|
+
opts
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
|
+
# Author:: Christoph Hartmann (<chris@lollyrock.com>)
|
5
|
+
|
6
|
+
require 'train/errors'
|
7
|
+
|
8
|
+
module Train
|
9
|
+
class Plugins
|
10
|
+
autoload :Transport, 'train/plugins/transport'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# Retrieve the current plugin registry, containing all plugin names
|
14
|
+
# and their transport handlers.
|
15
|
+
#
|
16
|
+
# @return [Hash] map with plugin names and plugins
|
17
|
+
def registry
|
18
|
+
@registry ||= {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Create a new plugin by inheriting from the class returned by this method.
|
24
|
+
# Create a versioned plugin by providing the transport layer plugin version
|
25
|
+
# to this method. It will then select the correct class to inherit from.
|
26
|
+
#
|
27
|
+
# The plugin version determins what methods will be available to your plugin.
|
28
|
+
#
|
29
|
+
# @param [Int] version = 1 the plugin version to use
|
30
|
+
# @return [Transport] the versioned transport base class
|
31
|
+
def self.plugin(version = 1)
|
32
|
+
if version != 1
|
33
|
+
fail ClientError,
|
34
|
+
'Only understand train plugin version 1. You are trying to '\
|
35
|
+
"initialize a train plugin #{version}, which is not supported "\
|
36
|
+
'in the current release of train.'
|
37
|
+
end
|
38
|
+
::Train::Plugins::Transport
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Author:: Salim Afiune (<salim@afiunemaya.com.mx>)
|
4
|
+
# Author:: Fletcher Nichol (<fnichol@nichol.ca>)
|
5
|
+
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
6
|
+
|
7
|
+
require 'train/errors'
|
8
|
+
require 'train/extras'
|
9
|
+
require 'logger'
|
10
|
+
|
11
|
+
class Train::Plugins::Transport
|
12
|
+
# A Connection instance can be generated and re-generated, given new
|
13
|
+
# connection details such as connection port, hostname, credentials, etc.
|
14
|
+
# This object is responsible for carrying out the actions on the remote
|
15
|
+
# host such as executing commands, transferring files, etc.
|
16
|
+
#
|
17
|
+
# @author Fletcher Nichol <fnichol@nichol.ca>
|
18
|
+
class BaseConnection
|
19
|
+
include Train::Extras
|
20
|
+
|
21
|
+
# Create a new Connection instance.
|
22
|
+
#
|
23
|
+
# @param options [Hash] connection options
|
24
|
+
# @yield [self] yields itself for block-style invocation
|
25
|
+
def initialize(options = nil)
|
26
|
+
@options = options || {}
|
27
|
+
@logger = @options.delete(:logger) || Logger.new(STDOUT)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Closes the session connection, if it is still active.
|
31
|
+
def close
|
32
|
+
# this method may be left unimplemented if that is applicable
|
33
|
+
end
|
34
|
+
|
35
|
+
# Execute a command using this connection.
|
36
|
+
#
|
37
|
+
# @param command [String] command string to execute
|
38
|
+
# @return [CommandResult] contains the result of running the command
|
39
|
+
def run_command(_command)
|
40
|
+
fail Train::ClientError, "#{self.class} does not implement #run_command()"
|
41
|
+
end
|
42
|
+
|
43
|
+
# Get information on the operating system which this transport connects to.
|
44
|
+
#
|
45
|
+
# @return [OSCommon] operating system information
|
46
|
+
def os
|
47
|
+
fail Train::ClientError, "#{self.class} does not implement #os()"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Interact with files on the target. Read, write, and get metadata
|
51
|
+
# from files via the transport.
|
52
|
+
#
|
53
|
+
# @param [String] path which is being inspected
|
54
|
+
# @return [FileCommon] file object that allows for interaction
|
55
|
+
def file(_path, *_args)
|
56
|
+
fail Train::ClientError, "#{self.class} does not implement #file(...)"
|
57
|
+
end
|
58
|
+
|
59
|
+
# Builds a LoginCommand which can be used to open an interactive
|
60
|
+
# session on the remote host.
|
61
|
+
#
|
62
|
+
# @return [LoginCommand] array of command line tokens
|
63
|
+
def login_command
|
64
|
+
fail Train::ClientError, "#{self.class} does not implement #run_command()"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Block and return only when the remote host is prepared and ready to
|
68
|
+
# execute command and upload files. The semantics and details will
|
69
|
+
# vary by implementation, but a round trip through the hosted
|
70
|
+
# service is preferred to simply waiting on a socket to become
|
71
|
+
# available.
|
72
|
+
def wait_until_ready
|
73
|
+
# this method may be left unimplemented if that is applicablelog
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# @return [Logger] logger for reporting information
|
79
|
+
# @api private
|
80
|
+
attr_reader :logger
|
81
|
+
|
82
|
+
# @return [Hash] connection options
|
83
|
+
# @api private
|
84
|
+
attr_reader :options
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Author:: Dominik Richter (<dominik.richter@gmail.com>)
|
4
|
+
# Author:: Christoph Hartmann (<chris@lollyrock.com>)
|
5
|
+
|
6
|
+
require 'logger'
|
7
|
+
require 'train/errors'
|
8
|
+
require 'train/extras'
|
9
|
+
require 'train/options'
|
10
|
+
|
11
|
+
class Train::Plugins
|
12
|
+
class Transport
|
13
|
+
include Train::Extras
|
14
|
+
Train::Options.attach(self)
|
15
|
+
|
16
|
+
autoload :BaseConnection, 'train/plugins/base_connection'
|
17
|
+
|
18
|
+
# Initialize a new Transport object
|
19
|
+
#
|
20
|
+
# @param [Hash] config = nil the configuration for this transport
|
21
|
+
# @return [Transport] the transport object
|
22
|
+
def initialize(options = {})
|
23
|
+
@options = merge_options({}, options || {})
|
24
|
+
@logger = @options[:logger] || Logger.new(STDOUT)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Create a connection to the target. Options may be provided
|
28
|
+
# for additional configuration.
|
29
|
+
#
|
30
|
+
# @param [Hash] _options = nil provide optional configuration params
|
31
|
+
# @return [Connection] the connection for this configuration
|
32
|
+
def connection(_options = nil)
|
33
|
+
fail Train::ClientError, "#{self.class} does not implement #connect()"
|
34
|
+
end
|
35
|
+
|
36
|
+
# Register the inheriting class with as a train plugin using the
|
37
|
+
# provided name.
|
38
|
+
#
|
39
|
+
# @param [String] name of the plugin, by which it will be found
|
40
|
+
def self.name(name)
|
41
|
+
Train::Plugins.registry[name] = self
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# @return [Logger] logger for reporting information
|
47
|
+
attr_reader :logger
|
48
|
+
end
|
49
|
+
end
|