train 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
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