train 0.29.2 → 0.30.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 = {}
|