train 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|