train 0.13.1 → 0.14.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 +15 -2
- data/lib/train/extras/file_common.rb +18 -8
- data/lib/train/extras/os_common.rb +4 -1
- data/lib/train/extras/os_detect_esx.rb +6 -4
- data/lib/train/extras/stat.rb +7 -7
- data/lib/train/plugins/base_connection.rb +17 -0
- data/lib/train/transports/docker.rb +0 -1
- data/lib/train/transports/local.rb +0 -1
- data/lib/train/transports/mock.rb +19 -5
- data/lib/train/transports/ssh_connection.rb +1 -2
- data/lib/train/transports/winrm_connection.rb +1 -2
- data/lib/train/version.rb +1 -1
- data/test/integration/test-one.yaml +4 -0
- data/test/integration/test-two.yaml +4 -0
- data/test/integration/tests/path_file_test.rb +6 -0
- data/test/integration/tests/run_command_test.rb +7 -0
- data/test/unit/extras/os_common_test.rb +1 -0
- data/test/unit/extras/stat_test.rb +41 -0
- data/test/unit/transports/mock_test.rb +9 -2
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bef369edee3db008a24f018851fb38b4b8ee2690
|
4
|
+
data.tar.gz: 4a029b67d3631b3975ad928c1b7c7832ac7da2cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81d4f61c914a1b3cbdfc11037c90be5652d3a7a8264d8ba351c65e614036a71bb507339ea6bfec310098b65a261b831d0ca97e517bd86dde5d286fae4cc9d466
|
7
|
+
data.tar.gz: c8d5bddc212d56e7d7f604df95008339b676c8dcf59a7dc59c7422d997d23781adce45276db983184a7c4b659282b98bd1d3b6f50b7434444fca12dfb7d8e7c0
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,20 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## [0.
|
4
|
-
[Full Changelog](https://github.com/chef/train/compare/v0.
|
3
|
+
## [0.14.0](https://github.com/chef/train/tree/0.14.0) (2016-06-27)
|
4
|
+
[Full Changelog](https://github.com/chef/train/compare/v0.13.1...0.14.0)
|
5
|
+
|
6
|
+
**Implemented enhancements:**
|
7
|
+
|
8
|
+
- json in and out for base connection [\#118](https://github.com/chef/train/pull/118) ([arlimus](https://github.com/arlimus))
|
9
|
+
- ESX support [\#116](https://github.com/chef/train/pull/116) ([Anirudh-Gupta](https://github.com/Anirudh-Gupta))
|
10
|
+
|
11
|
+
**Fixed bugs:**
|
12
|
+
|
13
|
+
- sporadic appveyor failure on `winrm delete ...` [\#105](https://github.com/chef/train/issues/105)
|
14
|
+
- bugfix: run frozen string commands via ssh [\#117](https://github.com/chef/train/pull/117) ([arlimus](https://github.com/arlimus))
|
15
|
+
|
16
|
+
## [v0.13.1](https://github.com/chef/train/tree/v0.13.1) (2016-06-16)
|
17
|
+
[Full Changelog](https://github.com/chef/train/compare/v0.12.1...v0.13.1)
|
5
18
|
|
6
19
|
**Implemented enhancements:**
|
7
20
|
|
@@ -9,10 +9,12 @@ module Train::Extras
|
|
9
9
|
class FileCommon # rubocop:disable Metrics/ClassLength
|
10
10
|
# interface methods: these fields should be implemented by every
|
11
11
|
# backend File
|
12
|
-
%w{
|
12
|
+
DATA_FIELDS = %w{
|
13
13
|
exist? mode owner group uid gid content mtime size selinux_label path
|
14
14
|
product_version file_version
|
15
|
-
}.
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
DATA_FIELDS.each do |m|
|
16
18
|
define_method m.to_sym do
|
17
19
|
fail NotImplementedError, "File must implement the #{m}() method."
|
18
20
|
end
|
@@ -24,6 +26,14 @@ module Train::Extras
|
|
24
26
|
@follow_symlink = follow_symlink
|
25
27
|
end
|
26
28
|
|
29
|
+
def to_json
|
30
|
+
res = Hash[DATA_FIELDS.map { |x| [x, method(x).call] }]
|
31
|
+
# additional fields provided as input
|
32
|
+
res['type'] = type
|
33
|
+
res['follow_symlink'] = @follow_symlink
|
34
|
+
res
|
35
|
+
end
|
36
|
+
|
27
37
|
def type
|
28
38
|
:unknown
|
29
39
|
end
|
@@ -50,27 +60,27 @@ module Train::Extras
|
|
50
60
|
# Additional methods for convenience
|
51
61
|
|
52
62
|
def file?
|
53
|
-
type ==
|
63
|
+
type.to_s == 'file'
|
54
64
|
end
|
55
65
|
|
56
66
|
def block_device?
|
57
|
-
type ==
|
67
|
+
type.to_s == 'block_device'
|
58
68
|
end
|
59
69
|
|
60
70
|
def character_device?
|
61
|
-
type ==
|
71
|
+
type.to_s == 'character_device'
|
62
72
|
end
|
63
73
|
|
64
74
|
def socket?
|
65
|
-
type ==
|
75
|
+
type.to_s == 'socket'
|
66
76
|
end
|
67
77
|
|
68
78
|
def directory?
|
69
|
-
type ==
|
79
|
+
type.to_s == 'directory'
|
70
80
|
end
|
71
81
|
|
72
82
|
def symlink?
|
73
|
-
source.type ==
|
83
|
+
source.type.to_s == 'symlink'
|
74
84
|
end
|
75
85
|
|
76
86
|
def source_path
|
@@ -12,6 +12,7 @@ require 'train/extras/os_detect_darwin'
|
|
12
12
|
require 'train/extras/os_detect_linux'
|
13
13
|
require 'train/extras/os_detect_unix'
|
14
14
|
require 'train/extras/os_detect_windows'
|
15
|
+
require 'train/extras/os_detect_esx'
|
15
16
|
|
16
17
|
module Train::Extras
|
17
18
|
class OSCommon
|
@@ -19,6 +20,7 @@ module Train::Extras
|
|
19
20
|
include Train::Extras::DetectLinux
|
20
21
|
include Train::Extras::DetectUnix
|
21
22
|
include Train::Extras::DetectWindows
|
23
|
+
include Train::Extras::DetectEsx
|
22
24
|
|
23
25
|
def initialize(backend, platform = nil)
|
24
26
|
@backend = backend
|
@@ -107,7 +109,7 @@ module Train::Extras
|
|
107
109
|
detect_family_type
|
108
110
|
end
|
109
111
|
|
110
|
-
def detect_family_type
|
112
|
+
def detect_family_type # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
111
113
|
pf = @platform[:family]
|
112
114
|
|
113
115
|
return detect_windows if pf == 'windows'
|
@@ -120,6 +122,7 @@ module Train::Extras
|
|
120
122
|
|
121
123
|
# unix based systems combine the above
|
122
124
|
return true if pf == 'unix' and detect_darwin
|
125
|
+
return true if pf == 'unix' and detect_esx
|
123
126
|
return true if pf == 'unix' and detect_via_uname
|
124
127
|
|
125
128
|
# if we arrive here, we most likey have a regular linux
|
@@ -11,10 +11,12 @@
|
|
11
11
|
module Train::Extras
|
12
12
|
module DetectEsx
|
13
13
|
def detect_esx
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
if uname_s.downcase.chomp == 'vmkernel'
|
15
|
+
@platform[:family] = 'esx'
|
16
|
+
@platform[:name] = uname_s.lines[0].chomp
|
17
|
+
@platform[:release] = uname_r.lines[0].chomp
|
18
|
+
true
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
data/lib/train/extras/stat.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
# author: Dominik Richter
|
3
3
|
# author: Christoph Hartmann
|
4
|
-
|
5
4
|
module Train::Extras
|
6
5
|
class Stat
|
7
6
|
TYPES = {
|
@@ -20,13 +19,13 @@ module Train::Extras
|
|
20
19
|
end
|
21
20
|
|
22
21
|
def self.stat(shell_escaped_path, backend, follow_symlink)
|
23
|
-
# use perl scripts for aix
|
22
|
+
# use perl scripts for aix, solaris 10 and hpux
|
24
23
|
if backend.os.aix? || (backend.os.solaris? && backend.os[:release].to_i < 11) || backend.os.hpux?
|
25
24
|
return aix_stat(shell_escaped_path, backend, follow_symlink)
|
26
25
|
end
|
27
26
|
return bsd_stat(shell_escaped_path, backend, follow_symlink) if backend.os.bsd?
|
28
|
-
# linux and
|
29
|
-
return linux_stat(shell_escaped_path, backend, follow_symlink) if backend.os.unix?
|
27
|
+
# linux,solaris 11 and esx will use standard linux stats
|
28
|
+
return linux_stat(shell_escaped_path, backend, follow_symlink) if backend.os.unix? || backend.os.esx?
|
30
29
|
# all other cases we don't handle
|
31
30
|
# TODO: print an error if we get here, as it shouldn't be invoked
|
32
31
|
# on non-unix
|
@@ -35,7 +34,8 @@ module Train::Extras
|
|
35
34
|
|
36
35
|
def self.linux_stat(shell_escaped_path, backend, follow_symlink)
|
37
36
|
lstat = follow_symlink ? ' -L' : ''
|
38
|
-
|
37
|
+
format = backend.os.esx? ? '-c' : '--printf'
|
38
|
+
res = backend.run_command("stat#{lstat} #{shell_escaped_path} 2>/dev/null #{format} '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'")
|
39
39
|
|
40
40
|
# ignore the exit_code: it is != 0 if selinux labels are not supported
|
41
41
|
# on the system.
|
@@ -45,8 +45,8 @@ module Train::Extras
|
|
45
45
|
|
46
46
|
tmask = fields[1].to_i(16)
|
47
47
|
selinux = fields[8]
|
48
|
-
selinux
|
49
|
-
|
48
|
+
## selinux security context string not available on esxi
|
49
|
+
selinux = nil if selinux == '?' or selinux == '(null)' or selinux == 'C'
|
50
50
|
{
|
51
51
|
type: find_type(tmask),
|
52
52
|
mode: tmask & 07777,
|
@@ -18,6 +18,9 @@ class Train::Plugins::Transport
|
|
18
18
|
class BaseConnection
|
19
19
|
include Train::Extras
|
20
20
|
|
21
|
+
# Provide access to the files cache.
|
22
|
+
attr_reader :files
|
23
|
+
|
21
24
|
# Create a new Connection instance.
|
22
25
|
#
|
23
26
|
# @param options [Hash] connection options
|
@@ -25,6 +28,7 @@ class Train::Plugins::Transport
|
|
25
28
|
def initialize(options = nil)
|
26
29
|
@options = options || {}
|
27
30
|
@logger = @options.delete(:logger) || Logger.new(STDOUT)
|
31
|
+
@files = {}
|
28
32
|
end
|
29
33
|
|
30
34
|
# Closes the session connection, if it is still active.
|
@@ -32,6 +36,19 @@ class Train::Plugins::Transport
|
|
32
36
|
# this method may be left unimplemented if that is applicable
|
33
37
|
end
|
34
38
|
|
39
|
+
def to_json
|
40
|
+
{
|
41
|
+
'files' => Hash[@files.map { |x, y| [x, y.to_json] }],
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_json(j)
|
46
|
+
require 'train/transports/mock'
|
47
|
+
j['files'].each do |path, jf|
|
48
|
+
@files[path] = Train::Transports::Mock::Connection::File.from_json(jf)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
35
52
|
# Execute a command using this connection.
|
36
53
|
#
|
37
54
|
# @param command [String] command string to execute
|
@@ -65,7 +65,6 @@ class Train::Transports::Mock
|
|
65
65
|
|
66
66
|
def initialize(conf = nil)
|
67
67
|
@conf = conf || {}
|
68
|
-
@files = {}
|
69
68
|
@os = OS.new(self, family: 'unknown')
|
70
69
|
@commands = {}
|
71
70
|
end
|
@@ -130,12 +129,27 @@ end
|
|
130
129
|
|
131
130
|
class Train::Transports::Mock::Connection
|
132
131
|
class File < FileCommon
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
132
|
+
def self.from_json(json)
|
133
|
+
res = new(json['backend'],
|
134
|
+
json['path'],
|
135
|
+
json['follow_symlink'])
|
136
|
+
res.type = json['type']
|
137
|
+
Train::Extras::FileCommon::DATA_FIELDS.each do |f|
|
138
|
+
m = (f.tr('?', '') + '=').to_sym
|
139
|
+
res.method(m).call(json[f])
|
140
|
+
end
|
141
|
+
res
|
142
|
+
end
|
143
|
+
|
144
|
+
Train::Extras::FileCommon::DATA_FIELDS.each do |m|
|
137
145
|
attr_accessor m.tr('?', '').to_sym
|
146
|
+
next unless m.include?('?')
|
147
|
+
|
148
|
+
define_method m.to_sym do
|
149
|
+
method(m.tr('?', '').to_sym).call
|
150
|
+
end
|
138
151
|
end
|
152
|
+
attr_accessor :type
|
139
153
|
|
140
154
|
def initialize(backend, path, follow_symlink = true)
|
141
155
|
super(backend, path, follow_symlink)
|
@@ -38,7 +38,6 @@ class Train::Transports::SSH
|
|
38
38
|
@connection_retries = @options.delete(:connection_retries)
|
39
39
|
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
|
40
40
|
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
41
|
-
@files = {}
|
42
41
|
@session = nil
|
43
42
|
@transport_options = @options.delete(:transport_options)
|
44
43
|
@cmd_wrapper = nil
|
@@ -73,7 +72,7 @@ class Train::Transports::SSH
|
|
73
72
|
def run_command(cmd)
|
74
73
|
stdout = stderr = ''
|
75
74
|
exit_status = nil
|
76
|
-
cmd.force_encoding('binary') if cmd.respond_to?(:force_encoding)
|
75
|
+
cmd.dup.force_encoding('binary') if cmd.respond_to?(:force_encoding)
|
77
76
|
logger.debug("[SSH] #{self} (#{cmd})")
|
78
77
|
|
79
78
|
session.open_channel do |channel|
|
@@ -27,7 +27,7 @@ class Train::Transports::WinRM
|
|
27
27
|
# host such as executing commands, transferring files, etc.
|
28
28
|
#
|
29
29
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
30
|
-
class Connection < BaseConnection
|
30
|
+
class Connection < BaseConnection
|
31
31
|
def initialize(options)
|
32
32
|
super(options)
|
33
33
|
@endpoint = @options.delete(:endpoint)
|
@@ -36,7 +36,6 @@ class Train::Transports::WinRM
|
|
36
36
|
@connection_retries = @options.delete(:connection_retries)
|
37
37
|
@connection_retry_sleep = @options.delete(:connection_retry_sleep)
|
38
38
|
@max_wait_until_ready = @options.delete(:max_wait_until_ready)
|
39
|
-
@files = {}
|
40
39
|
end
|
41
40
|
|
42
41
|
# (see Base::Connection#close)
|
data/lib/train/version.rb
CHANGED
@@ -10,6 +10,13 @@ describe 'run_command' do
|
|
10
10
|
res.exit_status.must_equal(0)
|
11
11
|
end
|
12
12
|
|
13
|
+
it 'can run frozen commands' do
|
14
|
+
res = backend.run_command('echo hello world'.freeze)
|
15
|
+
res.stdout.must_equal("hello world\n")
|
16
|
+
res.stderr.must_equal('')
|
17
|
+
res.exit_status.must_equal(0)
|
18
|
+
end
|
19
|
+
|
13
20
|
it 'can echo commands to stderr' do
|
14
21
|
# TODO: Specinfra often fails on this test.
|
15
22
|
# Fix and re-enable it.
|
@@ -270,6 +270,7 @@ describe 'os common plugin' do
|
|
270
270
|
let(:os) { mock_platform('esx') }
|
271
271
|
it { os.solaris?.must_equal(false) }
|
272
272
|
it { os.linux?.must_equal(false) }
|
273
|
+
it {os[:family].must_equal('esx')}
|
273
274
|
it { os.unix?.must_equal(false) }
|
274
275
|
it { os.esx?.must_equal(true) }
|
275
276
|
end
|
@@ -46,15 +46,56 @@ describe 'stat' do
|
|
46
46
|
|
47
47
|
it 'ignores wrong stat results' do
|
48
48
|
res = Minitest::Mock.new
|
49
|
+
os = Minitest::Mock.new
|
49
50
|
res.expect :stdout, ''
|
51
|
+
os.expect :esx?, false
|
52
|
+
backend.expect :os, os
|
50
53
|
backend.expect :run_command, res, [String]
|
51
54
|
cls.linux_stat('/path', backend, false).must_equal({})
|
52
55
|
end
|
53
56
|
|
54
57
|
it 'reads correct stat results' do
|
55
58
|
res = Minitest::Mock.new
|
59
|
+
os = Minitest::Mock.new
|
56
60
|
# 43ff is 41777; linux_stat strips the 4
|
57
61
|
res.expect :stdout, "360\n43ff\nroot\n0\nrootz\n1\n1444520846\n1444522445\n?"
|
62
|
+
os.expect :esx?, false
|
63
|
+
backend.expect :os, os
|
64
|
+
backend.expect :run_command, res, [String]
|
65
|
+
cls.linux_stat('/path', backend, false).must_equal({
|
66
|
+
type: :directory,
|
67
|
+
mode: 01777,
|
68
|
+
owner: 'root',
|
69
|
+
uid: 0,
|
70
|
+
group: 'rootz',
|
71
|
+
gid: 1,
|
72
|
+
mtime: 1444522445,
|
73
|
+
size: 360,
|
74
|
+
selinux_label: nil,
|
75
|
+
})
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe 'esx stat' do
|
80
|
+
let(:backend) { Minitest::Mock.new }
|
81
|
+
|
82
|
+
it 'ignores wrong stat results' do
|
83
|
+
res = Minitest::Mock.new
|
84
|
+
os = Minitest::Mock.new
|
85
|
+
res.expect :stdout, ''
|
86
|
+
os.expect :esx?, true
|
87
|
+
backend.expect :os, os
|
88
|
+
backend.expect :run_command, res, [String]
|
89
|
+
cls.linux_stat('/path', backend, false).must_equal({})
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'reads correct stat results' do
|
93
|
+
res = Minitest::Mock.new
|
94
|
+
os = Minitest::Mock.new
|
95
|
+
# 43ff is 41777; linux_stat strips the 4
|
96
|
+
res.expect :stdout, "360\n43ff\nroot\n0\nrootz\n1\n1444520846\n1444522445\nC"
|
97
|
+
os.expect :esx?, true
|
98
|
+
backend.expect :os, os
|
58
99
|
backend.expect :run_command, res, [String]
|
59
100
|
cls.linux_stat('/path', backend, false).must_equal({
|
60
101
|
type: :directory,
|
@@ -84,8 +84,15 @@ describe 'mock transport' do
|
|
84
84
|
end
|
85
85
|
|
86
86
|
describe 'when accessing a mocked file' do
|
87
|
-
|
88
|
-
|
87
|
+
JSON = Train.create('local').connection.file(__FILE__).to_json
|
88
|
+
RES = Train::Transports::Mock::Connection::File.from_json(JSON)
|
89
|
+
|
90
|
+
# tests if all fields between the local json and resulting mock file
|
91
|
+
# are equal
|
92
|
+
%w{ content mode owner group }.each do |f|
|
93
|
+
it "can be initialized from json (field #{f})" do
|
94
|
+
RES.method(f).call.must_equal JSON[f]
|
95
|
+
end
|
89
96
|
end
|
90
97
|
end
|
91
98
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: train
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.14.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dominik Richter
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-06-
|
11
|
+
date: 2016-06-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -188,8 +188,10 @@ files:
|
|
188
188
|
- test/integration/sudo/passwd.rb
|
189
189
|
- test/integration/sudo/reqtty.rb
|
190
190
|
- test/integration/sudo/run_as.rb
|
191
|
+
- test/integration/test-one.yaml
|
191
192
|
- test/integration/test-travis-1.yaml
|
192
193
|
- test/integration/test-travis-2.yaml
|
194
|
+
- test/integration/test-two.yaml
|
193
195
|
- test/integration/test_local.rb
|
194
196
|
- test/integration/test_ssh.rb
|
195
197
|
- test/integration/tests/path_block_device_test.rb
|
@@ -241,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
241
243
|
version: '0'
|
242
244
|
requirements: []
|
243
245
|
rubyforge_project:
|
244
|
-
rubygems_version: 2.
|
246
|
+
rubygems_version: 2.5.1
|
245
247
|
signing_key:
|
246
248
|
specification_version: 4
|
247
249
|
summary: Transport interface to talk to different backends.
|
@@ -262,8 +264,10 @@ test_files:
|
|
262
264
|
- test/integration/sudo/passwd.rb
|
263
265
|
- test/integration/sudo/reqtty.rb
|
264
266
|
- test/integration/sudo/run_as.rb
|
267
|
+
- test/integration/test-one.yaml
|
265
268
|
- test/integration/test-travis-1.yaml
|
266
269
|
- test/integration/test-travis-2.yaml
|
270
|
+
- test/integration/test-two.yaml
|
267
271
|
- test/integration/test_local.rb
|
268
272
|
- test/integration/test_ssh.rb
|
269
273
|
- test/integration/tests/path_block_device_test.rb
|