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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -2
  3. data/lib/train.rb +1 -0
  4. data/lib/train/errors.rb +6 -0
  5. data/lib/train/extras.rb +0 -1
  6. data/lib/train/platforms.rb +82 -0
  7. data/lib/train/platforms/common.rb +34 -0
  8. data/lib/train/platforms/detect.rb +12 -0
  9. data/lib/train/platforms/detect/helpers/os_common.rb +56 -0
  10. data/lib/train/platforms/detect/helpers/os_linux.rb +75 -0
  11. data/lib/train/{extras/os_detect_windows.rb → platforms/detect/helpers/os_windows.rb} +3 -10
  12. data/lib/train/platforms/detect/scanner.rb +84 -0
  13. data/lib/train/platforms/detect/specifications/os.rb +480 -0
  14. data/lib/train/platforms/family.rb +26 -0
  15. data/lib/train/platforms/platform.rb +80 -0
  16. data/lib/train/plugins/base_connection.rb +75 -27
  17. data/lib/train/transports/docker.rb +17 -28
  18. data/lib/train/transports/local.rb +21 -22
  19. data/lib/train/transports/mock.rb +44 -30
  20. data/lib/train/transports/ssh_connection.rb +55 -67
  21. data/lib/train/transports/winrm_connection.rb +16 -26
  22. data/lib/train/version.rb +1 -1
  23. data/test/unit/file/remote/linux_test.rb +2 -2
  24. data/test/unit/platforms/detect/os_common_test.rb +85 -0
  25. data/test/unit/platforms/detect/os_linux_test.rb +124 -0
  26. data/test/unit/{extras/os_detect_windows_test.rb → platforms/detect/os_windows_test.rb} +5 -2
  27. data/test/unit/platforms/detect/scanner_test.rb +61 -0
  28. data/test/unit/platforms/family_test.rb +32 -0
  29. data/test/unit/platforms/os_detect_test.rb +175 -0
  30. data/test/unit/{extras/os_common_test.rb → platforms/platform_test.rb} +103 -18
  31. data/test/unit/platforms/platforms_test.rb +42 -0
  32. data/test/unit/plugins/connection_test.rb +106 -8
  33. data/test/unit/transports/local_test.rb +20 -15
  34. data/test/unit/transports/mock_test.rb +16 -6
  35. data/test/unit/transports/ssh_test.rb +17 -15
  36. metadata +28 -19
  37. data/lib/train/extras/linux_lsb.rb +0 -60
  38. data/lib/train/extras/os_common.rb +0 -151
  39. data/lib/train/extras/os_detect_arista_eos.rb +0 -34
  40. data/lib/train/extras/os_detect_darwin.rb +0 -40
  41. data/lib/train/extras/os_detect_esx.rb +0 -22
  42. data/lib/train/extras/os_detect_linux.rb +0 -164
  43. data/lib/train/extras/os_detect_openvms.rb +0 -29
  44. data/lib/train/extras/os_detect_unix.rb +0 -106
  45. data/lib/train/extras/uname.rb +0 -28
  46. data/lib/train/transports/local_os.rb +0 -51
  47. 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
@@ -3,5 +3,5 @@
3
3
  # Author:: Dominik Richter (<dominik.richter@gmail.com>)
4
4
 
5
5
  module Train
6
- VERSION = '0.29.2'.freeze
6
+ VERSION = '0.30.0'.freeze
7
7
  end
@@ -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({ family: 'linux' })
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
- require 'train/extras'
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::Extras::DetectWindows
8
+ include Train::Platforms::Detect::Helpers::Windows
6
9
 
7
10
  def initialize
8
11
  @platform = {}