train 0.29.2 → 0.30.0
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 +4 -4
- data/CHANGELOG.md +26 -2
- data/lib/train.rb +1 -0
- data/lib/train/errors.rb +6 -0
- data/lib/train/extras.rb +0 -1
- data/lib/train/platforms.rb +82 -0
- data/lib/train/platforms/common.rb +34 -0
- data/lib/train/platforms/detect.rb +12 -0
- data/lib/train/platforms/detect/helpers/os_common.rb +56 -0
- data/lib/train/platforms/detect/helpers/os_linux.rb +75 -0
- data/lib/train/{extras/os_detect_windows.rb → platforms/detect/helpers/os_windows.rb} +3 -10
- data/lib/train/platforms/detect/scanner.rb +84 -0
- data/lib/train/platforms/detect/specifications/os.rb +480 -0
- data/lib/train/platforms/family.rb +26 -0
- data/lib/train/platforms/platform.rb +80 -0
- data/lib/train/plugins/base_connection.rb +75 -27
- data/lib/train/transports/docker.rb +17 -28
- data/lib/train/transports/local.rb +21 -22
- data/lib/train/transports/mock.rb +44 -30
- data/lib/train/transports/ssh_connection.rb +55 -67
- data/lib/train/transports/winrm_connection.rb +16 -26
- data/lib/train/version.rb +1 -1
- data/test/unit/file/remote/linux_test.rb +2 -2
- data/test/unit/platforms/detect/os_common_test.rb +85 -0
- data/test/unit/platforms/detect/os_linux_test.rb +124 -0
- data/test/unit/{extras/os_detect_windows_test.rb → platforms/detect/os_windows_test.rb} +5 -2
- data/test/unit/platforms/detect/scanner_test.rb +61 -0
- data/test/unit/platforms/family_test.rb +32 -0
- data/test/unit/platforms/os_detect_test.rb +175 -0
- data/test/unit/{extras/os_common_test.rb → platforms/platform_test.rb} +103 -18
- data/test/unit/platforms/platforms_test.rb +42 -0
- data/test/unit/plugins/connection_test.rb +106 -8
- data/test/unit/transports/local_test.rb +20 -15
- data/test/unit/transports/mock_test.rb +16 -6
- data/test/unit/transports/ssh_test.rb +17 -15
- metadata +28 -19
- data/lib/train/extras/linux_lsb.rb +0 -60
- data/lib/train/extras/os_common.rb +0 -151
- data/lib/train/extras/os_detect_arista_eos.rb +0 -34
- data/lib/train/extras/os_detect_darwin.rb +0 -40
- data/lib/train/extras/os_detect_esx.rb +0 -22
- data/lib/train/extras/os_detect_linux.rb +0 -164
- data/lib/train/extras/os_detect_openvms.rb +0 -29
- data/lib/train/extras/os_detect_unix.rb +0 -106
- data/lib/train/extras/uname.rb +0 -28
- data/lib/train/transports/local_os.rb +0 -51
- data/test/unit/extras/os_detect_linux_test.rb +0 -230
@@ -55,67 +55,6 @@ class Train::Transports::SSH
|
|
55
55
|
@session = nil
|
56
56
|
end
|
57
57
|
|
58
|
-
def os
|
59
|
-
@os ||= OS.new(self)
|
60
|
-
end
|
61
|
-
|
62
|
-
def file(path)
|
63
|
-
@files[path] ||= \
|
64
|
-
if os.aix?
|
65
|
-
Train::File::Remote::Aix.new(self, path)
|
66
|
-
elsif os.solaris?
|
67
|
-
Train::File::Remote::Unix.new(self, path)
|
68
|
-
elsif os[:name] == 'qnx'
|
69
|
-
Train::File::Remote::Qnx.new(self, path)
|
70
|
-
else
|
71
|
-
Train::File::Remote::Linux.new(self, path)
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
# (see Base::Connection#run_command)
|
76
|
-
def run_command(cmd)
|
77
|
-
stdout = stderr = ''
|
78
|
-
exit_status = nil
|
79
|
-
cmd.dup.force_encoding('binary') if cmd.respond_to?(:force_encoding)
|
80
|
-
logger.debug("[SSH] #{self} (#{cmd})")
|
81
|
-
|
82
|
-
session.open_channel do |channel|
|
83
|
-
# wrap commands if that is configured
|
84
|
-
cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil?
|
85
|
-
|
86
|
-
if @transport_options[:pty]
|
87
|
-
channel.request_pty do |_ch, success|
|
88
|
-
fail Train::Transports::SSHPTYFailed, 'Requesting PTY failed' unless success
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
channel.exec(cmd) do |_, success|
|
93
|
-
abort 'Couldn\'t execute command on SSH.' unless success
|
94
|
-
|
95
|
-
channel.on_data do |_, data|
|
96
|
-
stdout += data
|
97
|
-
end
|
98
|
-
|
99
|
-
channel.on_extended_data do |_, _type, data|
|
100
|
-
stderr += data
|
101
|
-
end
|
102
|
-
|
103
|
-
channel.on_request('exit-status') do |_, data|
|
104
|
-
exit_status = data.read_long
|
105
|
-
end
|
106
|
-
|
107
|
-
channel.on_request('exit-signal') do |_, data|
|
108
|
-
exit_status = data.read_long
|
109
|
-
end
|
110
|
-
end
|
111
|
-
end
|
112
|
-
@session.loop
|
113
|
-
|
114
|
-
CommandResult.new(stdout, stderr, exit_status)
|
115
|
-
rescue Net::SSH::Exception => ex
|
116
|
-
raise Train::Transports::SSHFailed, "SSH command failed (#{ex.message})"
|
117
|
-
end
|
118
|
-
|
119
58
|
# (see Base::Connection#login_command)
|
120
59
|
def login_command
|
121
60
|
level = logger.debug? ? 'VERBOSE' : 'ERROR'
|
@@ -225,6 +164,61 @@ class Train::Transports::SSH
|
|
225
164
|
retry
|
226
165
|
end
|
227
166
|
|
167
|
+
def file_via_connection(path)
|
168
|
+
if os.aix?
|
169
|
+
Train::File::Remote::Aix.new(self, path)
|
170
|
+
elsif os.solaris?
|
171
|
+
Train::File::Remote::Unix.new(self, path)
|
172
|
+
elsif os[:name] == 'qnx'
|
173
|
+
Train::File::Remote::Qnx.new(self, path)
|
174
|
+
else
|
175
|
+
Train::File::Remote::Linux.new(self, path)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def run_command_via_connection(cmd)
|
180
|
+
stdout = stderr = ''
|
181
|
+
exit_status = nil
|
182
|
+
cmd.dup.force_encoding('binary') if cmd.respond_to?(:force_encoding)
|
183
|
+
logger.debug("[SSH] #{self} (#{cmd})")
|
184
|
+
|
185
|
+
session.open_channel do |channel|
|
186
|
+
# wrap commands if that is configured
|
187
|
+
cmd = @cmd_wrapper.run(cmd) unless @cmd_wrapper.nil?
|
188
|
+
|
189
|
+
if @transport_options[:pty]
|
190
|
+
channel.request_pty do |_ch, success|
|
191
|
+
fail Train::Transports::SSHPTYFailed, 'Requesting PTY failed' unless success
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
channel.exec(cmd) do |_, success|
|
196
|
+
abort 'Couldn\'t execute command on SSH.' unless success
|
197
|
+
|
198
|
+
channel.on_data do |_, data|
|
199
|
+
stdout += data
|
200
|
+
end
|
201
|
+
|
202
|
+
channel.on_extended_data do |_, _type, data|
|
203
|
+
stderr += data
|
204
|
+
end
|
205
|
+
|
206
|
+
channel.on_request('exit-status') do |_, data|
|
207
|
+
exit_status = data.read_long
|
208
|
+
end
|
209
|
+
|
210
|
+
channel.on_request('exit-signal') do |_, data|
|
211
|
+
exit_status = data.read_long
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
@session.loop
|
216
|
+
|
217
|
+
CommandResult.new(stdout, stderr, exit_status)
|
218
|
+
rescue Net::SSH::Exception => ex
|
219
|
+
raise Train::Transports::SSHFailed, "SSH command failed (#{ex.message})"
|
220
|
+
end
|
221
|
+
|
228
222
|
# Returns a connection session, or establishes one when invoked the
|
229
223
|
# first time.
|
230
224
|
#
|
@@ -247,11 +241,5 @@ class Train::Transports::SSH
|
|
247
241
|
options_to_print[:password] = '<hidden>' if options_to_print.key?(:password)
|
248
242
|
"#{@username}@#{@hostname}<#{options_to_print.inspect}>"
|
249
243
|
end
|
250
|
-
|
251
|
-
class OS < OSCommon
|
252
|
-
def initialize(backend)
|
253
|
-
super(backend)
|
254
|
-
end
|
255
|
-
end
|
256
244
|
end
|
257
245
|
end
|
@@ -46,26 +46,6 @@ class Train::Transports::WinRM
|
|
46
46
|
@session = nil
|
47
47
|
end
|
48
48
|
|
49
|
-
def os
|
50
|
-
@os ||= OS.new(self)
|
51
|
-
end
|
52
|
-
|
53
|
-
def file(path)
|
54
|
-
@files[path] ||= Train::File::Remote::Windows.new(self, path)
|
55
|
-
end
|
56
|
-
|
57
|
-
def run_command(command)
|
58
|
-
return if command.nil?
|
59
|
-
logger.debug("[WinRM] #{self} (#{command})")
|
60
|
-
out = ''
|
61
|
-
|
62
|
-
response = session.run(command) do |stdout, _|
|
63
|
-
out << stdout if stdout
|
64
|
-
end
|
65
|
-
|
66
|
-
CommandResult.new(out, response.stderr, response.exitcode)
|
67
|
-
end
|
68
|
-
|
69
49
|
# (see Base::Connection#login_command)
|
70
50
|
def login_command
|
71
51
|
case RbConfig::CONFIG['host_os']
|
@@ -111,6 +91,22 @@ class Train::Transports::WinRM
|
|
111
91
|
|
112
92
|
PING_COMMAND = "Write-Host '[WinRM] Established\n'".freeze
|
113
93
|
|
94
|
+
def file_via_connection(path)
|
95
|
+
Train::File::Remote::Windows.new(self, path)
|
96
|
+
end
|
97
|
+
|
98
|
+
def run_command_via_connection(command)
|
99
|
+
return if command.nil?
|
100
|
+
logger.debug("[WinRM] #{self} (#{command})")
|
101
|
+
out = ''
|
102
|
+
|
103
|
+
response = session.run(command) do |stdout, _|
|
104
|
+
out << stdout if stdout
|
105
|
+
end
|
106
|
+
|
107
|
+
CommandResult.new(out, response.stderr, response.exitcode)
|
108
|
+
end
|
109
|
+
|
114
110
|
# Create a local RDP document and return it
|
115
111
|
#
|
116
112
|
# @param opts [Hash] configuration options
|
@@ -193,11 +189,5 @@ class Train::Transports::WinRM
|
|
193
189
|
options_to_print[:password] = '<hidden>' if options_to_print.key?(:password)
|
194
190
|
"#{@username}@#{@hostname}<#{options_to_print.inspect}>"
|
195
191
|
end
|
196
|
-
|
197
|
-
class OS < OSCommon
|
198
|
-
def initialize(backend)
|
199
|
-
super(backend, { family: 'windows' })
|
200
|
-
end
|
201
|
-
end
|
202
192
|
end
|
203
193
|
end
|
data/lib/train/version.rb
CHANGED
@@ -7,14 +7,14 @@ describe Train::File::Remote::Linux do
|
|
7
7
|
let(:cls) { Train::File::Remote::Linux }
|
8
8
|
let(:backend) {
|
9
9
|
backend = Train::Transports::Mock.new.connection
|
10
|
-
backend.mock_os({
|
10
|
+
backend.mock_os({ name: 'linux', family: 'unix' })
|
11
11
|
backend
|
12
12
|
}
|
13
13
|
|
14
14
|
def mock_stat(args, out, err = '', code = 0)
|
15
15
|
backend.mock_command(
|
16
16
|
"stat #{args} 2>/dev/null --printf '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'",
|
17
|
-
out, err, code
|
17
|
+
out, err, code
|
18
18
|
)
|
19
19
|
end
|
20
20
|
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'helper'
|
4
|
+
|
5
|
+
class OsDetectLinuxTester
|
6
|
+
attr_reader :platform
|
7
|
+
include Train::Platforms::Detect::Helpers::OSCommon
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@platform = {}
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'os_common' do
|
15
|
+
let(:detector) { OsDetectLinuxTester.new }
|
16
|
+
|
17
|
+
describe 'winrm? check' do
|
18
|
+
it 'return winrm? true' do
|
19
|
+
require 'train/transports/winrm'
|
20
|
+
be = Train::Transports::WinRM::Connection.new(nil)
|
21
|
+
detector.instance_variable_set(:@backend, be)
|
22
|
+
detector.winrm?.must_equal(true)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'return winrm? false' do
|
26
|
+
be = mock('Backend')
|
27
|
+
detector.instance_variable_set(:@backend, be)
|
28
|
+
detector.winrm?.must_equal(false)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe 'unix file contents' do
|
33
|
+
it 'return new file contents' do
|
34
|
+
be = mock('Backend')
|
35
|
+
output = mock('Output', exit_status: 0)
|
36
|
+
output.expects(:stdout).returns('test')
|
37
|
+
be.stubs(:run_command).with('test -f /etc/fstab && cat /etc/fstab').returns(output)
|
38
|
+
detector.instance_variable_set(:@backend, be)
|
39
|
+
detector.instance_variable_set(:@files, {})
|
40
|
+
detector.unix_file_contents('/etc/fstab').must_equal('test')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'return new file contents cached' do
|
44
|
+
be = mock('Backend')
|
45
|
+
detector.instance_variable_set(:@backend, be)
|
46
|
+
detector.instance_variable_set(:@files, { '/etc/profile' => 'test' })
|
47
|
+
detector.unix_file_contents('/etc/profile').must_equal('test')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe 'unix file exist?' do
|
52
|
+
it 'file does exist' do
|
53
|
+
be = mock('Backend')
|
54
|
+
be.stubs(:run_command).with('test -f /etc/test').returns(mock('Output', exit_status: 0))
|
55
|
+
detector.instance_variable_set(:@backend, be)
|
56
|
+
detector.unix_file_exist?('/etc/test').must_equal(true)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe '#detect_linux_arch' do
|
61
|
+
it 'uname m call' do
|
62
|
+
be = mock('Backend')
|
63
|
+
be.stubs(:run_command).with('uname -m').returns(mock('Output', stdout: "x86_64\n"))
|
64
|
+
detector.instance_variable_set(:@backend, be)
|
65
|
+
detector.instance_variable_set(:@uname, {})
|
66
|
+
detector.unix_uname_m.must_equal('x86_64')
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'uname s call' do
|
70
|
+
be = mock('Backend')
|
71
|
+
be.stubs(:run_command).with('uname -s').returns(mock('Output', stdout: "linux"))
|
72
|
+
detector.instance_variable_set(:@backend, be)
|
73
|
+
detector.instance_variable_set(:@uname, {})
|
74
|
+
detector.unix_uname_s.must_equal('linux')
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'uname r call' do
|
78
|
+
be = mock('Backend')
|
79
|
+
be.stubs(:run_command).with('uname -r').returns(mock('Output', stdout: "17.0.0\n"))
|
80
|
+
detector.instance_variable_set(:@backend, be)
|
81
|
+
detector.instance_variable_set(:@uname, {})
|
82
|
+
detector.unix_uname_r.must_equal('17.0.0')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'helper'
|
4
|
+
require 'train/transports/mock'
|
5
|
+
|
6
|
+
class OsDetectLinuxTester
|
7
|
+
include Train::Platforms::Detect::Helpers::OSCommon
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'os_linux' do
|
11
|
+
let(:detector) { OsDetectLinuxTester.new }
|
12
|
+
|
13
|
+
describe 'redhatish_platform cleaner' do
|
14
|
+
it 'normal redhat' do
|
15
|
+
detector.redhatish_platform('Red Hattter').must_equal('redhat')
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'custom redhat' do
|
19
|
+
detector.redhatish_platform('Centos Pro 11').must_equal('centos')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'redhatish_version cleaner' do
|
24
|
+
it 'normal rawhide' do
|
25
|
+
detector.redhatish_version('18 (Rawhide) Pro').must_equal('18 (rawhide)')
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'normal linux' do
|
29
|
+
detector.redhatish_version('derived from Ubuntu Linux 11').must_equal('11')
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'lsb parse' do
|
34
|
+
it 'lsb config' do
|
35
|
+
lsb = "DISTRIB_ID=Ubuntu\nDISTRIB_RELEASE=14.06\nDISTRIB_CODENAME=xenial"
|
36
|
+
expect = { :id=>'Ubuntu', :release=>'14.06', :codename=>'xenial' }
|
37
|
+
detector.lsb_config(lsb).must_equal(expect)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'lsb releasel' do
|
41
|
+
lsb = "Distributor ID: Ubuntu\nRelease: 14.06\nCodename: xenial"
|
42
|
+
expect = { :id=>'Ubuntu', :release=>'14.06', :codename=>'xenial' }
|
43
|
+
detector.lsb_release(lsb).must_equal(expect)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe '#linux_os_release' do
|
48
|
+
describe 'when no os-release data is available' do
|
49
|
+
it 'returns nil' do
|
50
|
+
detector.expects(:unix_file_contents).with('/etc/os-release').returns(nil)
|
51
|
+
detector.linux_os_release.must_be_nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'when os-release data exists with no CISCO_RELEASE_INFO' do
|
57
|
+
let(:os_release) { { 'KEY1' => 'VALUE1' } }
|
58
|
+
|
59
|
+
it 'returns a correct hash' do
|
60
|
+
detector.expects(:unix_file_contents).with('/etc/os-release').returns('os-release data')
|
61
|
+
detector.expects(:parse_os_release_info).with('os-release data').returns(os_release)
|
62
|
+
detector.linux_os_release['KEY1'].must_equal('VALUE1')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'when os-release data exists with CISCO_RELEASE_INFO' do
|
67
|
+
let(:os_release) { { 'KEY1' => 'VALUE1', 'CISCO_RELEASE_INFO' => 'cisco_file' } }
|
68
|
+
let(:cisco_release) { { 'KEY1' => 'NEWVALUE1', 'KEY2' => 'VALUE2' } }
|
69
|
+
|
70
|
+
it 'returns a correct hash' do
|
71
|
+
detector.expects(:unix_file_contents).with('/etc/os-release').returns('os-release data')
|
72
|
+
detector.expects(:unix_file_contents).with('cisco_file').returns('cisco data')
|
73
|
+
detector.expects(:parse_os_release_info).with('os-release data').returns(os_release)
|
74
|
+
detector.expects(:parse_os_release_info).with('cisco data').returns(cisco_release)
|
75
|
+
|
76
|
+
os_info = detector.linux_os_release
|
77
|
+
os_info['KEY1'].must_equal('NEWVALUE1')
|
78
|
+
os_info['KEY2'].must_equal('VALUE2')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe '#parse_os_release_info' do
|
83
|
+
describe 'when nil is supplied' do
|
84
|
+
it 'returns an empty hash' do
|
85
|
+
detector.parse_os_release_info(nil).must_equal({})
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'when unexpectedly-formatted data is supplied' do
|
90
|
+
let(:data) do
|
91
|
+
<<-EOL
|
92
|
+
blah blah
|
93
|
+
no good data here
|
94
|
+
EOL
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns an empty hash' do
|
98
|
+
detector.parse_os_release_info(nil).must_equal({})
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'when properly-formatted data is supplied' do
|
103
|
+
let(:data) do
|
104
|
+
<<-EOL
|
105
|
+
KEY1=value1
|
106
|
+
KEY2=
|
107
|
+
KEY3=value3
|
108
|
+
KEY4="value4 with spaces"
|
109
|
+
KEY5="value5 with a = sign"
|
110
|
+
EOL
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'parses the data correctly' do
|
114
|
+
parsed_data = detector.parse_os_release_info(data)
|
115
|
+
|
116
|
+
parsed_data['KEY1'].must_equal('value1')
|
117
|
+
parsed_data.key?('KEY2').must_equal(false)
|
118
|
+
parsed_data['KEY3'].must_equal('value3')
|
119
|
+
parsed_data['KEY4'].must_equal('value4 with spaces')
|
120
|
+
parsed_data['KEY5'].must_equal('value5 with a = sign')
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'helper'
|
4
|
+
require 'train/transports/mock'
|
2
5
|
|
3
6
|
class OsDetectWindowsTester
|
4
7
|
attr_reader :platform, :backend
|
5
|
-
include Train::
|
8
|
+
include Train::Platforms::Detect::Helpers::Windows
|
6
9
|
|
7
10
|
def initialize
|
8
11
|
@platform = {}
|