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.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +71 -0
  3. data/CHANGELOG.md +308 -0
  4. data/Gemfile +30 -0
  5. data/LICENSE +201 -0
  6. data/README.md +156 -0
  7. data/Rakefile +148 -0
  8. data/lib/train.rb +117 -0
  9. data/lib/train/errors.rb +23 -0
  10. data/lib/train/extras.rb +17 -0
  11. data/lib/train/extras/command_wrapper.rb +148 -0
  12. data/lib/train/extras/file_aix.rb +20 -0
  13. data/lib/train/extras/file_common.rb +161 -0
  14. data/lib/train/extras/file_linux.rb +16 -0
  15. data/lib/train/extras/file_unix.rb +79 -0
  16. data/lib/train/extras/file_windows.rb +91 -0
  17. data/lib/train/extras/linux_lsb.rb +60 -0
  18. data/lib/train/extras/os_common.rb +136 -0
  19. data/lib/train/extras/os_detect_darwin.rb +32 -0
  20. data/lib/train/extras/os_detect_linux.rb +148 -0
  21. data/lib/train/extras/os_detect_unix.rb +99 -0
  22. data/lib/train/extras/os_detect_windows.rb +57 -0
  23. data/lib/train/extras/stat.rb +133 -0
  24. data/lib/train/options.rb +80 -0
  25. data/lib/train/plugins.rb +40 -0
  26. data/lib/train/plugins/base_connection.rb +86 -0
  27. data/lib/train/plugins/transport.rb +49 -0
  28. data/lib/train/transports/docker.rb +103 -0
  29. data/lib/train/transports/local.rb +52 -0
  30. data/lib/train/transports/local_file.rb +90 -0
  31. data/lib/train/transports/local_os.rb +51 -0
  32. data/lib/train/transports/mock.rb +147 -0
  33. data/lib/train/transports/ssh.rb +163 -0
  34. data/lib/train/transports/ssh_connection.rb +225 -0
  35. data/lib/train/transports/winrm.rb +184 -0
  36. data/lib/train/transports/winrm_connection.rb +194 -0
  37. data/lib/train/version.rb +7 -0
  38. data/test/integration/.kitchen.yml +43 -0
  39. data/test/integration/Berksfile +3 -0
  40. data/test/integration/bootstrap.sh +17 -0
  41. data/test/integration/chefignore +1 -0
  42. data/test/integration/cookbooks/test/metadata.rb +1 -0
  43. data/test/integration/cookbooks/test/recipes/default.rb +100 -0
  44. data/test/integration/cookbooks/test/recipes/prep_files.rb +47 -0
  45. data/test/integration/docker_run.rb +153 -0
  46. data/test/integration/docker_test.rb +24 -0
  47. data/test/integration/docker_test_container.rb +24 -0
  48. data/test/integration/helper.rb +61 -0
  49. data/test/integration/sudo/customcommand.rb +15 -0
  50. data/test/integration/sudo/nopasswd.rb +16 -0
  51. data/test/integration/sudo/passwd.rb +21 -0
  52. data/test/integration/sudo/reqtty.rb +17 -0
  53. data/test/integration/sudo/run_as.rb +12 -0
  54. data/test/integration/test-travis-1.yaml +13 -0
  55. data/test/integration/test-travis-2.yaml +13 -0
  56. data/test/integration/test_local.rb +19 -0
  57. data/test/integration/test_ssh.rb +39 -0
  58. data/test/integration/tests/path_block_device_test.rb +74 -0
  59. data/test/integration/tests/path_character_device_test.rb +74 -0
  60. data/test/integration/tests/path_file_test.rb +79 -0
  61. data/test/integration/tests/path_folder_test.rb +90 -0
  62. data/test/integration/tests/path_missing_test.rb +77 -0
  63. data/test/integration/tests/path_pipe_test.rb +78 -0
  64. data/test/integration/tests/path_symlink_test.rb +95 -0
  65. data/test/integration/tests/run_command_test.rb +28 -0
  66. data/test/unit/extras/command_wrapper_test.rb +78 -0
  67. data/test/unit/extras/file_common_test.rb +180 -0
  68. data/test/unit/extras/linux_file_test.rb +167 -0
  69. data/test/unit/extras/os_common_test.rb +269 -0
  70. data/test/unit/extras/os_detect_linux_test.rb +189 -0
  71. data/test/unit/extras/os_detect_windows_test.rb +99 -0
  72. data/test/unit/extras/stat_test.rb +148 -0
  73. data/test/unit/extras/windows_file_test.rb +44 -0
  74. data/test/unit/helper.rb +7 -0
  75. data/test/unit/plugins/connection_test.rb +44 -0
  76. data/test/unit/plugins/transport_test.rb +111 -0
  77. data/test/unit/plugins_test.rb +22 -0
  78. data/test/unit/train_test.rb +156 -0
  79. data/test/unit/transports/local_file_test.rb +184 -0
  80. data/test/unit/transports/local_test.rb +87 -0
  81. data/test/unit/transports/mock_test.rb +87 -0
  82. data/test/unit/transports/ssh_test.rb +109 -0
  83. data/test/unit/version_test.rb +8 -0
  84. data/test/windows/local_test.rb +46 -0
  85. data/test/windows/winrm_test.rb +52 -0
  86. data/train.gemspec +38 -0
  87. metadata +295 -0
@@ -0,0 +1,99 @@
1
+ require 'train/extras'
2
+
3
+ class OsDetectWindowsTester
4
+ attr_reader :platform, :backend
5
+ include Train::Extras::DetectWindows
6
+
7
+ def initialize
8
+ @platform = {}
9
+ @backend = Train::Transports::Mock.new.connection
10
+ @backend.mock_os({ family: 'windows' })
11
+ end
12
+ end
13
+
14
+ describe 'os_detect_windows' do
15
+ describe 'windows 2012' do
16
+ let(:detector) {
17
+ detector = OsDetectWindowsTester.new
18
+ detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 6.3.9600]\r\n", '', 0)
19
+ detector.backend.mock_command('wmic os get * /format:list',"\r\r\nBuildNumber=9600\r\r\nCaption=Microsoft Windows Server 2012 R2 Standard\r\r\nOSArchitecture=64-bit\r\r\nVersion=6.3.9600\r\r\n" , '', 0)
20
+ detector
21
+ }
22
+
23
+ it 'sets the correct family/release for windows' do
24
+ detector.detect_windows
25
+ detector.platform[:family].must_equal('windows')
26
+ detector.platform[:name].must_equal('Windows Server 2012 R2 Standard')
27
+ detector.platform[:arch].must_equal('64-bit')
28
+ detector.platform[:release].must_equal('6.3.9600')
29
+ end
30
+ end
31
+
32
+ describe 'windows 2008' do
33
+ let(:detector) {
34
+ detector = OsDetectWindowsTester.new
35
+ detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 6.1.7601]\r\n", '', 0)
36
+ detector.backend.mock_command('wmic os get * /format:list',"\r\r\nBuildNumber=7601\r\r\nCaption=Microsoft Windows Server 2008 R2 Standard \r\r\nOSArchitecture=64-bit\r\r\nVersion=6.1.7601\r\r\n" , '', 0)
37
+ detector
38
+ }
39
+
40
+ it 'sets the correct family/release for windows' do
41
+ detector.detect_windows
42
+ detector.platform[:family].must_equal('windows')
43
+ detector.platform[:name].must_equal('Windows Server 2008 R2 Standard')
44
+ detector.platform[:arch].must_equal('64-bit')
45
+ detector.platform[:release].must_equal('6.1.7601')
46
+ end
47
+ end
48
+
49
+ describe 'windows 7' do
50
+ let(:detector) {
51
+ detector = OsDetectWindowsTester.new
52
+ detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 6.1.7601]\r\n", '', 0)
53
+ detector.backend.mock_command('wmic os get * /format:list',"\r\r\nBuildNumber=7601\r\r\nCaption=Microsoft Windows 7 Enterprise \r\r\nOSArchitecture=32-bit\r\r\nVersion=6.1.7601\r\r\n\r\r\n" , '', 0)
54
+ detector
55
+ }
56
+
57
+ it 'sets the correct family/release for windows' do
58
+ detector.detect_windows
59
+ detector.platform[:family].must_equal('windows')
60
+ detector.platform[:name].must_equal('Windows 7 Enterprise')
61
+ detector.platform[:arch].must_equal('32-bit')
62
+ detector.platform[:release].must_equal('6.1.7601')
63
+ end
64
+ end
65
+
66
+ describe 'windows 10' do
67
+ let(:detector) {
68
+ detector = OsDetectWindowsTester.new
69
+ detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 10.0.10240]\r\n", '', 0)
70
+ detector.backend.mock_command('wmic os get * /format:list',"\r\r\nBuildNumber=10240\r\r\nCaption=Microsoft Windows 10 Pro\r\r\nOSArchitecture=64-bit\r\r\nVersion=10.0.10240\r\r\n\r\r\n" , '', 0)
71
+ detector
72
+ }
73
+
74
+ it 'sets the correct family/release for windows' do
75
+ detector.detect_windows
76
+ detector.platform[:family].must_equal('windows')
77
+ detector.platform[:name].must_equal('Windows 10 Pro')
78
+ detector.platform[:arch].must_equal('64-bit')
79
+ detector.platform[:release].must_equal('10.0.10240')
80
+ end
81
+ end
82
+
83
+ describe 'windows 98' do
84
+ let(:detector) {
85
+ detector = OsDetectWindowsTester.new
86
+ detector.backend.mock_command('cmd /c ver', "\r\nMicrosoft Windows [Version 4.10.1998]\r\n", '', 0)
87
+ detector.backend.mock_command('wmic os get * /format:list', nil , '', 1)
88
+ detector
89
+ }
90
+
91
+ it 'fallback to version number if wmic is not available' do
92
+ detector.detect_windows
93
+ detector.platform[:family].must_equal('windows')
94
+ detector.platform[:name].must_equal('Windows 4.10.1998')
95
+ detector.platform[:arch].must_equal(nil)
96
+ detector.platform[:release].must_equal('4.10.1998')
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,148 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+ require 'train/extras'
4
+
5
+ describe 'stat' do
6
+ let(:cls) { Train::Extras::Stat }
7
+
8
+ describe 'find_type' do
9
+ let (:random_mode) { (rand * 1000).to_i }
10
+
11
+ it 'detects :unknown types' do
12
+ cls.find_type(random_mode).must_equal :unknown
13
+ end
14
+
15
+ it 'detects sockets' do
16
+ cls.find_type(00140755).must_equal :socket
17
+ end
18
+
19
+ it 'detects symlinks' do
20
+ cls.find_type(00120755).must_equal :symlink
21
+ end
22
+
23
+ it 'detects files' do
24
+ cls.find_type(00100755).must_equal :file
25
+ end
26
+
27
+ it 'detects block devices' do
28
+ cls.find_type(00060755).must_equal :block_device
29
+ end
30
+
31
+ it 'detects directories' do
32
+ cls.find_type(00040755).must_equal :directory
33
+ end
34
+
35
+ it 'detects character devices' do
36
+ cls.find_type(00020755).must_equal :character_device
37
+ end
38
+
39
+ it 'detects pipes' do
40
+ cls.find_type(00010755).must_equal :pipe
41
+ end
42
+ end
43
+
44
+ describe 'linux stat' do
45
+ let(:backend) { Minitest::Mock.new }
46
+
47
+ it 'ignores wrong stat results' do
48
+ res = Minitest::Mock.new
49
+ res.expect :stdout, ''
50
+ backend.expect :run_command, res, [String]
51
+ cls.linux_stat('/path', backend, false).must_equal({})
52
+ end
53
+
54
+ it 'reads correct stat results' do
55
+ res = Minitest::Mock.new
56
+ # 43ff is 41777; linux_stat strips the 4
57
+ res.expect :stdout, "360\n43ff\nroot\n0\nrootz\n1\n1444520846\n1444522445\n?"
58
+ backend.expect :run_command, res, [String]
59
+ cls.linux_stat('/path', backend, false).must_equal({
60
+ type: :directory,
61
+ mode: 01777,
62
+ owner: 'root',
63
+ uid: 0,
64
+ group: 'rootz',
65
+ gid: 1,
66
+ mtime: 1444522445,
67
+ size: 360,
68
+ selinux_label: nil,
69
+ })
70
+ end
71
+ end
72
+
73
+ describe 'bsd stat' do
74
+ let(:backend) { Minitest::Mock.new }
75
+
76
+ it 'ignores failed stat results' do
77
+ res = Minitest::Mock.new
78
+ res.expect :stdout, '.....'
79
+ res.expect :exit_status, 1
80
+ backend.expect :run_command, res, [String]
81
+ cls.bsd_stat('/path', backend, false).must_equal({})
82
+ end
83
+
84
+ it 'ignores wrong stat results' do
85
+ res = Minitest::Mock.new
86
+ res.expect :stdout, ''
87
+ res.expect :exit_status, 0
88
+ backend.expect :run_command, res, [String]
89
+ cls.bsd_stat('/path', backend, false).must_equal({})
90
+ end
91
+
92
+ it 'reads correct stat results' do
93
+ res = Minitest::Mock.new
94
+ res.expect :stdout, "360\n41777\nroot\n0\nrootz\n1\n1444520846\n1444522445"
95
+ res.expect :exit_status, 0
96
+ backend.expect :run_command, res, [String]
97
+ cls.bsd_stat('/path', backend, false).must_equal({
98
+ type: :directory,
99
+ mode: 01777,
100
+ owner: 'root',
101
+ uid: 0,
102
+ group: 'rootz',
103
+ gid: 1,
104
+ mtime: 1444522445,
105
+ size: 360,
106
+ selinux_label: nil,
107
+ })
108
+ end
109
+ end
110
+
111
+ describe 'aix stat' do
112
+ let(:backend) { Minitest::Mock.new }
113
+
114
+ it 'ignores failed stat results' do
115
+ res = Minitest::Mock.new
116
+ res.expect :stdout, '.....'
117
+ res.expect :exit_status, 1
118
+ backend.expect :run_command, res, [String]
119
+ cls.aix_stat('/path', backend, false).must_equal({})
120
+ end
121
+
122
+ it 'ignores wrong stat results' do
123
+ res = Minitest::Mock.new
124
+ res.expect :stdout, ''
125
+ res.expect :exit_status, 0
126
+ backend.expect :run_command, res, [String]
127
+ cls.aix_stat('/path', backend, false).must_equal({})
128
+ end
129
+
130
+ it 'reads correct stat results' do
131
+ res = Minitest::Mock.new
132
+ res.expect :stdout, "41777\nroot\n0\nrootz\n1\n1444522445\n360\n"
133
+ res.expect :exit_status, 0
134
+ backend.expect :run_command, res, [String]
135
+ cls.aix_stat('/path', backend, false).must_equal({
136
+ type: :directory,
137
+ mode: 01777,
138
+ owner: 'root',
139
+ uid: 0,
140
+ group: 'rootz',
141
+ gid: 1,
142
+ mtime: 1444522445,
143
+ size: 360,
144
+ selinux_label: nil,
145
+ })
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+ require 'train/transports/mock'
4
+ require 'train/extras'
5
+
6
+ describe 'file common' do
7
+ let(:cls) { Train::Extras::WindowsFile }
8
+ let(:backend) {
9
+ backend = Train::Transports::Mock.new.connection
10
+ backend.mock_os({ family: 'windows' })
11
+ backend
12
+ }
13
+
14
+ it 'provides the full path' do
15
+ cls.new(backend, 'C:\dir\file').path.must_equal 'C:\dir\file'
16
+ end
17
+
18
+ it 'provides the basename to a unix path' do
19
+ cls.new(backend, 'C:\dir\file').basename.must_equal 'file'
20
+ end
21
+
22
+ it 'provides the full path with whitespace' do
23
+ wf = cls.new(backend, 'C:\Program Files\file name')
24
+ wf.path.must_equal 'C:\Program Files\file name'
25
+ wf.basename.must_equal 'file name'
26
+ end
27
+
28
+ it 'reads file contents' do
29
+ out = rand.to_s
30
+ backend.mock_command('Get-Content("path") | Out-String', out)
31
+ cls.new(backend, 'path').content.must_equal out
32
+ end
33
+
34
+ it 'check escaping of invalid chars in path' do
35
+ wf = cls.new(nil, nil)
36
+ wf.sanitize_filename('c:/test') .must_equal 'c:/test'
37
+ wf.sanitize_filename('c:/test directory') .must_equal 'c:/test directory'
38
+ %w{ < > " * ?}.each do |char|
39
+ wf.sanitize_filename("c:/test#{char}directory") .must_equal 'c:/testdirectory'
40
+ end
41
+ end
42
+
43
+ # TODO: add all missing file tests!!
44
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ require 'minitest/autorun'
4
+ require 'minitest/spec'
5
+ require 'mocha/setup'
6
+
7
+ require 'train'
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+
4
+ describe 'v1 Connection Plugin' do
5
+ describe 'empty v1 connection plugin' do
6
+ let(:cls) { Train::Plugins::Transport::BaseConnection }
7
+ let(:connection) { cls.new({}) }
8
+
9
+ it 'provides a close method' do
10
+ connection.close # wont raise
11
+ end
12
+
13
+ it 'provides a run_command method' do
14
+ proc { connection.run_command('') }.must_raise Train::ClientError
15
+ end
16
+
17
+ it 'provides an os method' do
18
+ proc { connection.os }.must_raise Train::ClientError
19
+ end
20
+
21
+ it 'provides a file method' do
22
+ proc { connection.file('') }.must_raise Train::ClientError
23
+ end
24
+
25
+ it 'provides a login command method' do
26
+ proc { connection.login_command }.must_raise Train::ClientError
27
+ end
28
+
29
+ it 'can wait until ready' do
30
+ connection.wait_until_ready # wont raise
31
+ end
32
+
33
+ it 'provides a default logger' do
34
+ connection.method(:logger).call
35
+ .must_be_instance_of(Logger)
36
+ end
37
+
38
+ it 'must use the user-provided logger' do
39
+ l = rand
40
+ cls.new({logger: l})
41
+ .method(:logger).call.must_equal(l)
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,111 @@
1
+ # encoding: utf-8
2
+ require 'helper'
3
+
4
+ describe 'v1 Transport Plugin' do
5
+ describe 'empty v1 transport plugin' do
6
+ let(:plugin) { Class.new(Train.plugin(1)) }
7
+
8
+ it 'initializes an empty configuration' do
9
+ plugin.new.options.must_equal({})
10
+ end
11
+
12
+ it 'saves the provided configuration' do
13
+ conf = { a: rand }
14
+ plugin.new(conf).options.must_equal(conf)
15
+ end
16
+
17
+ it 'saves the provided configuration' do
18
+ conf = { a: rand }
19
+ plugin.new(conf).options.must_equal(conf)
20
+ end
21
+
22
+ it 'provides a default logger' do
23
+ conf = { a: rand }
24
+ plugin.new(conf)
25
+ .method(:logger).call
26
+ .must_be_instance_of(Logger)
27
+ end
28
+
29
+ it 'can configure custom loggers' do
30
+ l = rand
31
+ plugin.new({ logger: l })
32
+ .method(:logger).call
33
+ .must_equal(l)
34
+ end
35
+
36
+ it 'provides a connection method' do
37
+ proc { plugin.new.connection }.must_raise Train::ClientError
38
+ end
39
+ end
40
+
41
+ describe 'registered with a name' do
42
+ before do
43
+ Train::Plugins.registry.clear
44
+ end
45
+
46
+ it 'doesnt have any plugins in the registry if none were configured' do
47
+ Train::Plugins.registry.empty?.must_equal true
48
+ end
49
+
50
+ it 'is is added to the plugins registry' do
51
+ plugin_name = rand
52
+ Train::Plugins.registry.wont_include(plugin_name)
53
+
54
+ plugin = Class.new(Train.plugin(1)) do
55
+ name plugin_name
56
+ end
57
+
58
+ Train::Plugins.registry[plugin_name].must_equal(plugin)
59
+ end
60
+ end
61
+
62
+ describe 'with options' do
63
+ def train_class(opts = {})
64
+ name = rand.to_s
65
+ plugin = Class.new(Train.plugin(1)) do
66
+ option name, opts
67
+ end
68
+ [name, plugin]
69
+ end
70
+
71
+ it 'exposes the parameters via api' do
72
+ name, plugin = train_class
73
+ plugin.default_options.keys.must_equal [name]
74
+ end
75
+
76
+ it 'exposes the parameters via api' do
77
+ default = rand.to_s
78
+ name, plugin = train_class({ default: default })
79
+ plugin.default_options[name][:default].must_equal default
80
+ end
81
+
82
+ it 'option must be required' do
83
+ name, plugin = train_class(required: true)
84
+ plugin.default_options[name][:required].must_equal true
85
+ end
86
+
87
+ it 'default option must not be required' do
88
+ name, plugin = train_class
89
+ plugin.default_options[name][:required].must_equal nil
90
+ end
91
+
92
+ it 'can include options from another module' do
93
+ nameA, pluginA = train_class
94
+ b = Class.new(Train.plugin(1)) do
95
+ include_options(pluginA)
96
+ end
97
+ b.default_options[nameA].wont_be_nil
98
+ end
99
+
100
+ it 'overwrites existing options when including' do
101
+ old = rand.to_s
102
+ nu = rand.to_s
103
+ nameA, pluginA = train_class({ default: nu })
104
+ b = Class.new(Train.plugin(1)) do
105
+ option nameA, default: old
106
+ include_options(pluginA)
107
+ end
108
+ b.default_options[nameA][:default].must_equal nu
109
+ end
110
+ end
111
+ end