r-train 0.10.8 → 0.11.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 +13 -2
- data/lib/train.rb +19 -0
- data/lib/train/extras/file_common.rb +27 -18
- data/lib/train/extras/file_unix.rb +22 -16
- data/lib/train/extras/file_windows.rb +9 -18
- data/lib/train/extras/stat.rb +29 -20
- data/lib/train/transports/local_file.rb +16 -3
- data/lib/train/transports/mock.rb +4 -5
- data/lib/train/version.rb +1 -1
- data/test/integration/tests/path_symlink_test.rb +17 -5
- data/test/unit/extras/file_common_test.rb +7 -6
- data/test/unit/extras/linux_file_test.rb +66 -20
- data/test/unit/extras/stat_test.rb +13 -9
- data/test/unit/extras/windows_file_test.rb +5 -5
- data/test/unit/train_test.rb +24 -0
- data/test/unit/transports/local_file_test.rb +81 -9
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 32041a577502de9c77d37eb40ff8fc7110693b7f
|
4
|
+
data.tar.gz: 8202709105de66980ebd1901ec4106ab32d9899e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f19534cf0741a0c400eff0c151f9f056bdb1df3f6043ed9c61e968c7ff0a99c8f07b5a24376f95e52d321dc27619fe72476b66657ea3d6a29b7ec0e83fb03c9e
|
7
|
+
data.tar.gz: d758c0e2af5abbad797f94d93f7c0ada74dea5ab070d785fea79ac72ee53d0c67cd7013d98107e11625cf411c0572a13b1dfa01be45f681ffa29a254bb426c9e
|
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,18 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
-
## [0.
|
4
|
-
[Full Changelog](https://github.com/chef/train/compare/v0.10.
|
3
|
+
## [0.11.0](https://github.com/chef/train/tree/0.11.0) (2016-04-28)
|
4
|
+
[Full Changelog](https://github.com/chef/train/compare/v0.10.8...0.11.0)
|
5
|
+
|
6
|
+
**Implemented enhancements:**
|
7
|
+
|
8
|
+
- Overhault file\(...\) and stat\(...\); point to destination of symlinks [\#92](https://github.com/chef/train/pull/92) ([arlimus](https://github.com/arlimus))
|
9
|
+
|
10
|
+
**Fixed bugs:**
|
11
|
+
|
12
|
+
- validate the backend configuration [\#91](https://github.com/chef/train/pull/91) ([arlimus](https://github.com/arlimus))
|
13
|
+
|
14
|
+
## [v0.10.8](https://github.com/chef/train/tree/v0.10.8) (2016-04-25)
|
15
|
+
[Full Changelog](https://github.com/chef/train/compare/v0.10.7...v0.10.8)
|
5
16
|
|
6
17
|
**Implemented enhancements:**
|
7
18
|
|
data/lib/train.rb
CHANGED
@@ -62,6 +62,7 @@ module Train
|
|
62
62
|
|
63
63
|
return conf if conf[:target].to_s.empty?
|
64
64
|
|
65
|
+
# split up the target's host/scheme configuration
|
65
66
|
uri = URI.parse(conf[:target].to_s)
|
66
67
|
unless uri.host.nil? and uri.scheme.nil?
|
67
68
|
conf[:backend] ||= uri.scheme
|
@@ -79,6 +80,24 @@ module Train
|
|
79
80
|
conf
|
80
81
|
end
|
81
82
|
|
83
|
+
def self.validate_backend(conf, default = :local)
|
84
|
+
return default if conf.nil?
|
85
|
+
res = conf[:backend]
|
86
|
+
return res if !res.nil?
|
87
|
+
|
88
|
+
if !conf[:target].nil?
|
89
|
+
fail Train::UserError, 'Cannot determine backend from target '\
|
90
|
+
"configuration #{conf[:target].inspect}. Valid example: ssh://192.168.0.1."
|
91
|
+
end
|
92
|
+
|
93
|
+
if !conf[:host].nil?
|
94
|
+
fail Train::UserError, 'Host configured, but no backend was provided. Please '\
|
95
|
+
'specify how you want to connect. Valid example: ssh://192.168.0.1.'
|
96
|
+
end
|
97
|
+
|
98
|
+
conf[:backend] = default
|
99
|
+
end
|
100
|
+
|
82
101
|
def self.group_keys_and_keyfiles(conf)
|
83
102
|
# in case the user specified a key-file, register it that way
|
84
103
|
# we will clear the list of keys and put keys and key_files separately
|
@@ -10,14 +10,20 @@ module Train::Extras
|
|
10
10
|
# interface methods: these fields should be implemented by every
|
11
11
|
# backend File
|
12
12
|
%w{
|
13
|
-
exist? mode owner group
|
14
|
-
|
13
|
+
exist? mode owner group uid gid content mtime size selinux_label path
|
14
|
+
product_version file_version
|
15
15
|
}.each do |m|
|
16
16
|
define_method m.to_sym do
|
17
17
|
fail NotImplementedError, "File must implement the #{m}() method."
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
def initialize(backend, path, follow_symlink = true)
|
22
|
+
@backend = backend
|
23
|
+
@path = path
|
24
|
+
@follow_symlink = follow_symlink
|
25
|
+
end
|
26
|
+
|
21
27
|
def type
|
22
28
|
:unknown
|
23
29
|
end
|
@@ -44,31 +50,39 @@ module Train::Extras
|
|
44
50
|
# Additional methods for convenience
|
45
51
|
|
46
52
|
def file?
|
47
|
-
|
53
|
+
type == :file
|
48
54
|
end
|
49
55
|
|
50
56
|
def block_device?
|
51
|
-
|
57
|
+
type == :block_device
|
52
58
|
end
|
53
59
|
|
54
60
|
def character_device?
|
55
|
-
|
61
|
+
type == :character_device
|
56
62
|
end
|
57
63
|
|
58
64
|
def socket?
|
59
|
-
|
65
|
+
type == :socket
|
60
66
|
end
|
61
67
|
|
62
68
|
def directory?
|
63
|
-
|
69
|
+
type == :directory
|
64
70
|
end
|
65
71
|
|
66
72
|
def symlink?
|
67
|
-
type == :symlink
|
73
|
+
source.type == :symlink
|
74
|
+
end
|
75
|
+
|
76
|
+
def source
|
77
|
+
if @follow_symlink
|
78
|
+
self.class.new(@backend, @path, false)
|
79
|
+
else
|
80
|
+
self
|
81
|
+
end
|
68
82
|
end
|
69
83
|
|
70
84
|
def pipe?
|
71
|
-
|
85
|
+
type == :pipe
|
72
86
|
end
|
73
87
|
|
74
88
|
def mode?(sth)
|
@@ -87,6 +101,10 @@ module Train::Extras
|
|
87
101
|
link_path == dst
|
88
102
|
end
|
89
103
|
|
104
|
+
def link_path
|
105
|
+
symlink? ? path : nil
|
106
|
+
end
|
107
|
+
|
90
108
|
def version?(version)
|
91
109
|
product_version == version or
|
92
110
|
file_version == version
|
@@ -123,15 +141,6 @@ module Train::Extras
|
|
123
141
|
path[idx..-1]
|
124
142
|
end
|
125
143
|
|
126
|
-
def target_type
|
127
|
-
# Just return the type unless this is a symlink
|
128
|
-
return type unless type == :symlink
|
129
|
-
# Get the link's target type, i.e. the real destination's type
|
130
|
-
return link_target.type unless link_target.nil?
|
131
|
-
# Return unknown if we don't know where this is pointing to
|
132
|
-
:unknown
|
133
|
-
end
|
134
|
-
|
135
144
|
UNIX_MODE_OWNERS = {
|
136
145
|
all: 00777,
|
137
146
|
owner: 00700,
|
@@ -8,9 +8,8 @@ require 'train/extras/stat'
|
|
8
8
|
module Train::Extras
|
9
9
|
class UnixFile < FileCommon
|
10
10
|
attr_reader :path
|
11
|
-
def initialize(backend, path)
|
12
|
-
|
13
|
-
@path = path
|
11
|
+
def initialize(backend, path, follow_symlink = true)
|
12
|
+
super(backend, path, follow_symlink)
|
14
13
|
@spath = Shellwords.escape(@path)
|
15
14
|
end
|
16
15
|
|
@@ -27,21 +26,15 @@ module Train::Extras
|
|
27
26
|
|
28
27
|
def exist?
|
29
28
|
@exist ||= (
|
30
|
-
@
|
29
|
+
f = @follow_symlink ? '' : " || test -L #{@spath}"
|
30
|
+
@backend.run_command("test -e #{@spath}"+f)
|
31
31
|
.exit_status == 0
|
32
32
|
)
|
33
33
|
end
|
34
34
|
|
35
|
-
def
|
36
|
-
return @
|
37
|
-
|
38
|
-
@link_target = @backend.file(link_path)
|
39
|
-
end
|
40
|
-
|
41
|
-
def link_path
|
42
|
-
return nil unless symlink?
|
43
|
-
@link_path ||=
|
44
|
-
@backend.run_command("readlink #{@spath}").stdout.chomp
|
35
|
+
def path
|
36
|
+
return @path unless @follow_symlink && symlink?
|
37
|
+
@link_path ||= read_target_path
|
45
38
|
end
|
46
39
|
|
47
40
|
def mounted
|
@@ -50,7 +43,7 @@ module Train::Extras
|
|
50
43
|
end
|
51
44
|
|
52
45
|
%w{
|
53
|
-
type mode owner group mtime size selinux_label
|
46
|
+
type mode owner group uid gid mtime size selinux_label
|
54
47
|
}.each do |field|
|
55
48
|
define_method field.to_sym do
|
56
49
|
stat[field.to_sym]
|
@@ -67,7 +60,20 @@ module Train::Extras
|
|
67
60
|
|
68
61
|
def stat
|
69
62
|
return @stat if defined?(@stat)
|
70
|
-
@stat = Train::Extras::Stat.stat(@spath, @backend)
|
63
|
+
@stat = Train::Extras::Stat.stat(@spath, @backend, @follow_symlink)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Returns full path of a symlink target(real dest) or '' on symlink loop
|
69
|
+
def read_target_path
|
70
|
+
full_path = @backend.run_command("readlink -n #{@spath} -f").stdout
|
71
|
+
# Needed for some OSes like OSX that returns relative path
|
72
|
+
# when the link and target are in the same directory
|
73
|
+
if !full_path.start_with?('/') && full_path != ''
|
74
|
+
full_path = File.expand_path("../#{full_path}", @spath)
|
75
|
+
end
|
76
|
+
full_path
|
71
77
|
end
|
72
78
|
end
|
73
79
|
end
|
@@ -11,9 +11,8 @@ require 'train/extras/stat'
|
|
11
11
|
module Train::Extras
|
12
12
|
class WindowsFile < FileCommon
|
13
13
|
attr_reader :path
|
14
|
-
def initialize(backend, path)
|
15
|
-
|
16
|
-
@path = path
|
14
|
+
def initialize(backend, path, follow_symlink)
|
15
|
+
super(backend, path, follow_symlink)
|
17
16
|
@spath = sanitize_filename(@path)
|
18
17
|
end
|
19
18
|
|
@@ -44,10 +43,6 @@ module Train::Extras
|
|
44
43
|
"(Test-Path -Path \"#{@spath}\").ToString()").stdout.chomp == 'True'
|
45
44
|
end
|
46
45
|
|
47
|
-
def link_target
|
48
|
-
nil
|
49
|
-
end
|
50
|
-
|
51
46
|
def link_path
|
52
47
|
nil
|
53
48
|
end
|
@@ -57,11 +52,16 @@ module Train::Extras
|
|
57
52
|
end
|
58
53
|
|
59
54
|
def type
|
55
|
+
if attributes.include?('Archive')
|
56
|
+
return :file
|
57
|
+
elsif attributes.include?('Directory')
|
58
|
+
return :directory
|
59
|
+
end
|
60
60
|
:unknown
|
61
61
|
end
|
62
62
|
|
63
63
|
%w{
|
64
|
-
mode owner group mtime size selinux_label
|
64
|
+
mode owner group uid gid mtime size selinux_label
|
65
65
|
}.each do |field|
|
66
66
|
define_method field.to_sym do
|
67
67
|
nil
|
@@ -85,16 +85,7 @@ module Train::Extras
|
|
85
85
|
def attributes
|
86
86
|
return @attributes if defined?(@attributes)
|
87
87
|
@attributes = @backend.run_command(
|
88
|
-
"(Get-ItemProperty -Path \"#{@spath}\").attributes.ToString()").stdout.chomp.split(
|
89
|
-
end
|
90
|
-
|
91
|
-
def target_type
|
92
|
-
if attributes.include?('Archive')
|
93
|
-
return :file
|
94
|
-
elsif attributes.include?('Directory')
|
95
|
-
return :directory
|
96
|
-
end
|
97
|
-
:unknown
|
88
|
+
"(Get-ItemProperty -Path \"#{@spath}\").attributes.ToString()").stdout.chomp.split(/\s*,\s*/)
|
98
89
|
end
|
99
90
|
end
|
100
91
|
end
|
data/lib/train/extras/stat.rb
CHANGED
@@ -19,22 +19,23 @@ module Train::Extras
|
|
19
19
|
res.nil? ? :unknown : res[0]
|
20
20
|
end
|
21
21
|
|
22
|
-
def self.stat(shell_escaped_path, backend)
|
22
|
+
def self.stat(shell_escaped_path, backend, follow_symlink)
|
23
23
|
# use perl scripts for aix and solaris 10
|
24
24
|
if backend.os.aix? || (backend.os.solaris? && backend.os[:release].to_i < 11)
|
25
|
-
return aix_stat(shell_escaped_path, backend)
|
25
|
+
return aix_stat(shell_escaped_path, backend, follow_symlink)
|
26
26
|
end
|
27
|
-
return bsd_stat(shell_escaped_path, backend) if backend.os.bsd?
|
27
|
+
return bsd_stat(shell_escaped_path, backend, follow_symlink) if backend.os.bsd?
|
28
28
|
# linux and solaris 11 will use standard linux stats
|
29
|
-
return linux_stat(shell_escaped_path, backend) if backend.os.unix?
|
29
|
+
return linux_stat(shell_escaped_path, backend, follow_symlink) if backend.os.unix?
|
30
30
|
# all other cases we don't handle
|
31
31
|
# TODO: print an error if we get here, as it shouldn't be invoked
|
32
32
|
# on non-unix
|
33
33
|
{}
|
34
34
|
end
|
35
35
|
|
36
|
-
def self.linux_stat(shell_escaped_path, backend)
|
37
|
-
|
36
|
+
def self.linux_stat(shell_escaped_path, backend, follow_symlink)
|
37
|
+
lstat = follow_symlink ? ' -L' : ''
|
38
|
+
res = backend.run_command("stat#{lstat} #{shell_escaped_path} 2>/dev/null --printf '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'")
|
38
39
|
|
39
40
|
# ignore the exit_code: it is != 0 if selinux labels are not supported
|
40
41
|
# on the system.
|
@@ -47,17 +48,19 @@ module Train::Extras
|
|
47
48
|
selinux = nil if selinux == '?' or selinux == '(null)'
|
48
49
|
|
49
50
|
{
|
50
|
-
type:
|
51
|
-
mode:
|
51
|
+
type: find_type(tmask),
|
52
|
+
mode: tmask & 07777,
|
52
53
|
owner: fields[2],
|
54
|
+
uid: fields[3].to_i,
|
53
55
|
group: fields[4],
|
56
|
+
gid: fields[5].to_i,
|
54
57
|
mtime: fields[7].to_i,
|
55
|
-
size:
|
58
|
+
size: fields[0].to_i,
|
56
59
|
selinux_label: selinux,
|
57
60
|
}
|
58
61
|
end
|
59
62
|
|
60
|
-
def self.bsd_stat(shell_escaped_path, backend)
|
63
|
+
def self.bsd_stat(shell_escaped_path, backend, follow_symlink)
|
61
64
|
# From stat man page on FreeBSD:
|
62
65
|
# z The size of file in bytes (st_size).
|
63
66
|
# p File type and permissions (st_mode).
|
@@ -72,8 +75,9 @@ module Train::Extras
|
|
72
75
|
# in combination with:
|
73
76
|
# ...
|
74
77
|
# gu Display group or user name.
|
78
|
+
lstat = follow_symlink ? ' -L' : ''
|
75
79
|
res = backend.run_command(
|
76
|
-
"stat -f '%z\n%p\n%Su\n%u\n%Sg\n%g\n%a\n%m' "\
|
80
|
+
"stat#{lstat} -f '%z\n%p\n%Su\n%u\n%Sg\n%g\n%a\n%m' "\
|
77
81
|
"#{shell_escaped_path}")
|
78
82
|
|
79
83
|
return {} if res.exit_status != 0
|
@@ -84,24 +88,27 @@ module Train::Extras
|
|
84
88
|
tmask = fields[1].to_i(8)
|
85
89
|
|
86
90
|
{
|
87
|
-
type:
|
88
|
-
mode:
|
91
|
+
type: find_type(tmask),
|
92
|
+
mode: tmask & 07777,
|
89
93
|
owner: fields[2],
|
94
|
+
uid: fields[3].to_i,
|
90
95
|
group: fields[4],
|
96
|
+
gid: fields[5].to_i,
|
91
97
|
mtime: fields[7].to_i,
|
92
|
-
size:
|
98
|
+
size: fields[0].to_i,
|
93
99
|
selinux_label: fields[8],
|
94
100
|
}
|
95
101
|
end
|
96
102
|
|
97
|
-
def self.aix_stat(shell_escaped_path, backend)
|
103
|
+
def self.aix_stat(shell_escaped_path, backend, follow_symlink)
|
98
104
|
# Perl here b/c it is default on AIX
|
105
|
+
lstat = follow_symlink ? 'lstat' : 'stat'
|
99
106
|
stat_cmd = <<-EOP
|
100
107
|
perl -e '
|
101
|
-
@a = lstat(shift) or exit 2;
|
108
|
+
@a = #{lstat}(shift) or exit 2;
|
102
109
|
$u = getpwuid($a[4]);
|
103
110
|
$g = getgrgid($a[5]);
|
104
|
-
printf("0%o\\n%s\\n%s\\n%d\\n%d\\n", $a[2], $u, $
|
111
|
+
printf("0%o\\n%s\\n%d\\n%s\\n%d\\n%d\\n%d\\n", $a[2], $u, $a[4], $u, $a[5], $a[9], $a[7])
|
105
112
|
' #{shell_escaped_path}
|
106
113
|
EOP
|
107
114
|
|
@@ -115,9 +122,11 @@ module Train::Extras
|
|
115
122
|
type: find_type(tmask),
|
116
123
|
mode: tmask & 07777,
|
117
124
|
owner: fields[1],
|
118
|
-
|
119
|
-
|
120
|
-
|
125
|
+
uid: fields[2].to_i,
|
126
|
+
group: fields[3],
|
127
|
+
gid: fields[4].to_i,
|
128
|
+
mtime: fields[5].to_i,
|
129
|
+
size: fields[6].to_i,
|
121
130
|
selinux_label: nil,
|
122
131
|
}
|
123
132
|
end
|
@@ -23,7 +23,12 @@ class Train::Transports::Local::Connection
|
|
23
23
|
|
24
24
|
def link_path
|
25
25
|
return nil unless symlink?
|
26
|
-
|
26
|
+
begin
|
27
|
+
@link_path ||= ::File.realpath(@path)
|
28
|
+
rescue Errno::ELOOP => _
|
29
|
+
# Leave it blank on symbolic loop, same as readlink
|
30
|
+
@link_path = ''
|
31
|
+
end
|
27
32
|
end
|
28
33
|
|
29
34
|
def block_device?
|
@@ -52,7 +57,12 @@ class Train::Transports::Local::Connection
|
|
52
57
|
return @stat if defined? @stat
|
53
58
|
|
54
59
|
begin
|
55
|
-
file_stat =
|
60
|
+
file_stat =
|
61
|
+
if @follow_symlink
|
62
|
+
::File.stat(@path)
|
63
|
+
else
|
64
|
+
::File.lstat(@path)
|
65
|
+
end
|
56
66
|
rescue StandardError => _err
|
57
67
|
return @stat = {}
|
58
68
|
end
|
@@ -63,10 +73,13 @@ class Train::Transports::Local::Connection
|
|
63
73
|
mtime: file_stat.mtime.to_i,
|
64
74
|
size: file_stat.size,
|
65
75
|
owner: pw_username(file_stat.uid),
|
76
|
+
uid: file_stat.uid,
|
66
77
|
group: pw_groupname(file_stat.gid),
|
78
|
+
gid: file_stat.gid,
|
67
79
|
}
|
68
80
|
|
69
|
-
|
81
|
+
lstat = @follow_symlink ? ' -L' : ''
|
82
|
+
res = @backend.run_command("stat#{lstat} #{@spath} 2>/dev/null --printf '%C'")
|
70
83
|
if res.exit_status == 0 && !res.stdout.empty? && res.stdout != '?'
|
71
84
|
@stat[:selinux_label] = res.stdout.strip
|
72
85
|
end
|
@@ -127,22 +127,21 @@ end
|
|
127
127
|
class Train::Transports::Mock::Connection
|
128
128
|
class File < FileCommon
|
129
129
|
%w{
|
130
|
-
exist? mode owner group
|
130
|
+
exist? mode owner group link_path content mtime size
|
131
131
|
selinux_label product_version file_version path type
|
132
132
|
}.each do |m|
|
133
133
|
attr_accessor m.tr('?', '').to_sym
|
134
134
|
end
|
135
135
|
|
136
|
-
def initialize(
|
137
|
-
|
136
|
+
def initialize(backend, path, follow_symlink = true)
|
137
|
+
super(backend, path, follow_symlink)
|
138
138
|
@type = :unknown
|
139
139
|
@exist = false
|
140
|
-
@runtime = runtime
|
141
140
|
end
|
142
141
|
|
143
142
|
def mounted
|
144
143
|
@mounted ||=
|
145
|
-
@
|
144
|
+
@backend.run_command("mount | grep -- ' on #{@path}'")
|
146
145
|
end
|
147
146
|
end
|
148
147
|
end
|
data/lib/train/version.rb
CHANGED
@@ -22,8 +22,8 @@ describe 'file interface' do
|
|
22
22
|
file.directory?.must_equal(false)
|
23
23
|
end
|
24
24
|
|
25
|
-
it 'has type :
|
26
|
-
file.type.must_equal(:
|
25
|
+
it 'has type :file' do
|
26
|
+
file.type.must_equal(:file)
|
27
27
|
end
|
28
28
|
|
29
29
|
it 'has content' do
|
@@ -34,16 +34,28 @@ describe 'file interface' do
|
|
34
34
|
file.owner.must_equal('root')
|
35
35
|
end
|
36
36
|
|
37
|
+
it 'has uid 0' do
|
38
|
+
file.uid.must_equal(0)
|
39
|
+
end
|
40
|
+
|
37
41
|
it 'has group name' do
|
38
42
|
file.group.must_equal(Test.root_group(backend.os))
|
39
43
|
end
|
40
44
|
|
45
|
+
it 'has gid 0' do
|
46
|
+
file.gid.must_equal(0)
|
47
|
+
end
|
48
|
+
|
41
49
|
it 'has mode 0777' do
|
42
|
-
file.mode.must_equal(00777)
|
50
|
+
file.source.mode.must_equal(00777)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'has mode 0765' do
|
54
|
+
file.mode.must_equal(00765)
|
43
55
|
end
|
44
56
|
|
45
|
-
it 'checks mode?
|
46
|
-
file.mode?(
|
57
|
+
it 'checks mode? 0765' do
|
58
|
+
file.mode?(00765).must_equal(true)
|
47
59
|
end
|
48
60
|
|
49
61
|
it 'has link_path' do
|
@@ -5,6 +5,7 @@ require 'train/extras/file_common'
|
|
5
5
|
|
6
6
|
describe 'file common' do
|
7
7
|
let(:cls) { Train::Extras::FileCommon }
|
8
|
+
let(:new_cls) { cls.new(nil, nil, false) }
|
8
9
|
|
9
10
|
def mockup(stubs)
|
10
11
|
Class.new(cls) do
|
@@ -13,35 +14,35 @@ describe 'file common' do
|
|
13
14
|
v
|
14
15
|
end
|
15
16
|
end
|
16
|
-
end.new
|
17
|
+
end.new(nil, nil, false)
|
17
18
|
end
|
18
19
|
|
19
20
|
it 'has the default type of unknown' do
|
20
|
-
|
21
|
+
new_cls.type.must_equal :unknown
|
21
22
|
end
|
22
23
|
|
23
24
|
it 'calculates md5sum from content' do
|
24
25
|
content = 'hello world'
|
25
|
-
|
26
|
+
new_cls.stub :content, content do |i|
|
26
27
|
i.md5sum.must_equal '5eb63bbbe01eeed093cb22bb8f5acdc3'
|
27
28
|
end
|
28
29
|
end
|
29
30
|
|
30
31
|
it 'sets md5sum of nil content to nil' do
|
31
|
-
|
32
|
+
new_cls.stub :content, nil do |i|
|
32
33
|
i.md5sum.must_be_nil
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
36
37
|
it 'calculates md5sum from content' do
|
37
38
|
content = 'hello world'
|
38
|
-
|
39
|
+
new_cls.stub :content, content do |i|
|
39
40
|
i.sha256sum.must_equal 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9'
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
43
44
|
it 'sets sha256sum of nil content to nil' do
|
44
|
-
|
45
|
+
new_cls.stub :content, nil do |i|
|
45
46
|
i.sha256sum.must_be_nil
|
46
47
|
end
|
47
48
|
end
|
@@ -11,9 +11,9 @@ describe 'file common' do
|
|
11
11
|
backend
|
12
12
|
}
|
13
13
|
|
14
|
-
def mock_stat(
|
14
|
+
def mock_stat(args, out, err = '', code = 0)
|
15
15
|
backend.mock_command(
|
16
|
-
"stat
|
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
17
|
out, err, code,
|
18
18
|
)
|
19
19
|
end
|
@@ -34,7 +34,7 @@ describe 'file common' do
|
|
34
34
|
|
35
35
|
it 'reads file contents' do
|
36
36
|
backend.mock_command('cat path || echo -n', '')
|
37
|
-
mock_stat('path', '', 'some error...', 1)
|
37
|
+
mock_stat('-L path', '', 'some error...', 1)
|
38
38
|
cls.new(backend, 'path').content.must_equal nil
|
39
39
|
end
|
40
40
|
|
@@ -51,16 +51,8 @@ describe 'file common' do
|
|
51
51
|
it 'retrieves the link path' do
|
52
52
|
out = rand.to_s
|
53
53
|
mock_stat('path', "13\na1ff\nz\n1001\nz\n1001\n1444573475\n1444573475\n?")
|
54
|
-
backend.mock_command('readlink path', out)
|
55
|
-
cls.new(backend, 'path').link_path.must_equal out
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'retrieves the linked file' do
|
59
|
-
out = rand.to_s
|
60
|
-
mock_stat('path', "13\na1ff\nz\n1001\nz\n1001\n1444573475\n1444573475\n?")
|
61
|
-
backend.mock_command('readlink path', out)
|
62
|
-
f = backend.file(out)
|
63
|
-
cls.new(backend, 'path').link_target.must_equal f
|
54
|
+
backend.mock_command('readlink -n path -f', out)
|
55
|
+
cls.new(backend, 'path').link_path.must_equal File.join(Dir.pwd, out)
|
64
56
|
end
|
65
57
|
|
66
58
|
it 'checks a mounted path' do
|
@@ -77,30 +69,84 @@ describe 'file common' do
|
|
77
69
|
end
|
78
70
|
|
79
71
|
describe 'stat on a file' do
|
80
|
-
before { mock_stat('path', "13\na1ff\nz\n1001\
|
72
|
+
before { mock_stat('-L path', "13\na1ff\nz\n1001\nz2\n1002\n1444573475\n1444573475\nlabels") }
|
73
|
+
let(:f) { cls.new(backend, 'path') }
|
74
|
+
|
75
|
+
it 'retrieves the file type' do
|
76
|
+
f.type.must_equal :symlink
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'retrieves the file mode' do
|
80
|
+
f.mode.must_equal 00777
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'retrieves the file owner' do
|
84
|
+
f.owner.must_equal 'z'
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'retrieves the file uid' do
|
88
|
+
f.uid.must_equal 1001
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'retrieves the file group' do
|
92
|
+
f.group.must_equal 'z2'
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'retrieves the file gid' do
|
96
|
+
f.gid.must_equal 1002
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'retrieves the file mtime' do
|
100
|
+
f.mtime.must_equal 1444573475
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'retrieves the file size' do
|
104
|
+
f.size.must_equal 13
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'retrieves the file selinux_label' do
|
108
|
+
f.selinux_label.must_equal 'labels'
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe 'stat on the source file' do
|
113
|
+
before { mock_stat('path', "13\na1ff\nz\n1001\nz2\n1002\n1444573475\n1444573475\nlabels") }
|
114
|
+
let(:f) { cls.new(backend, 'path').source }
|
81
115
|
|
82
116
|
it 'retrieves the file type' do
|
83
|
-
|
117
|
+
f.type.must_equal :symlink
|
84
118
|
end
|
85
119
|
|
86
120
|
it 'retrieves the file mode' do
|
87
|
-
|
121
|
+
f.mode.must_equal 00777
|
88
122
|
end
|
89
123
|
|
90
124
|
it 'retrieves the file owner' do
|
91
|
-
|
125
|
+
f.owner.must_equal 'z'
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'retrieves the file uid' do
|
129
|
+
f.uid.must_equal 1001
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'retrieves the file group' do
|
133
|
+
f.group.must_equal 'z2'
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'retrieves the file gid' do
|
137
|
+
f.gid.must_equal 1002
|
92
138
|
end
|
93
139
|
|
94
140
|
it 'retrieves the file mtime' do
|
95
|
-
|
141
|
+
f.mtime.must_equal 1444573475
|
96
142
|
end
|
97
143
|
|
98
144
|
it 'retrieves the file size' do
|
99
|
-
|
145
|
+
f.size.must_equal 13
|
100
146
|
end
|
101
147
|
|
102
148
|
it 'retrieves the file selinux_label' do
|
103
|
-
|
149
|
+
f.selinux_label.must_equal 'labels'
|
104
150
|
end
|
105
151
|
end
|
106
152
|
end
|
@@ -48,19 +48,21 @@ describe 'stat' do
|
|
48
48
|
res = Minitest::Mock.new
|
49
49
|
res.expect :stdout, ''
|
50
50
|
backend.expect :run_command, res, [String]
|
51
|
-
cls.linux_stat('/path', backend).must_equal({})
|
51
|
+
cls.linux_stat('/path', backend, false).must_equal({})
|
52
52
|
end
|
53
53
|
|
54
54
|
it 'reads correct stat results' do
|
55
55
|
res = Minitest::Mock.new
|
56
56
|
# 43ff is 41777; linux_stat strips the 4
|
57
|
-
res.expect :stdout, "360\n43ff\nroot\n0\
|
57
|
+
res.expect :stdout, "360\n43ff\nroot\n0\nrootz\n1\n1444520846\n1444522445\n?"
|
58
58
|
backend.expect :run_command, res, [String]
|
59
|
-
cls.linux_stat('/path', backend).must_equal({
|
59
|
+
cls.linux_stat('/path', backend, false).must_equal({
|
60
60
|
type: :directory,
|
61
61
|
mode: 01777,
|
62
62
|
owner: 'root',
|
63
|
-
|
63
|
+
uid: 0,
|
64
|
+
group: 'rootz',
|
65
|
+
gid: 1,
|
64
66
|
mtime: 1444522445,
|
65
67
|
size: 360,
|
66
68
|
selinux_label: nil,
|
@@ -76,7 +78,7 @@ describe 'stat' do
|
|
76
78
|
res.expect :stdout, '.....'
|
77
79
|
res.expect :exit_status, 1
|
78
80
|
backend.expect :run_command, res, [String]
|
79
|
-
cls.bsd_stat('/path', backend).must_equal({})
|
81
|
+
cls.bsd_stat('/path', backend, false).must_equal({})
|
80
82
|
end
|
81
83
|
|
82
84
|
it 'ignores wrong stat results' do
|
@@ -84,19 +86,21 @@ describe 'stat' do
|
|
84
86
|
res.expect :stdout, ''
|
85
87
|
res.expect :exit_status, 0
|
86
88
|
backend.expect :run_command, res, [String]
|
87
|
-
cls.bsd_stat('/path', backend).must_equal({})
|
89
|
+
cls.bsd_stat('/path', backend, false).must_equal({})
|
88
90
|
end
|
89
91
|
|
90
92
|
it 'reads correct stat results' do
|
91
93
|
res = Minitest::Mock.new
|
92
|
-
res.expect :stdout, "360\n41777\nroot\n0\
|
94
|
+
res.expect :stdout, "360\n41777\nroot\n0\nrootz\n1\n1444520846\n1444522445"
|
93
95
|
res.expect :exit_status, 0
|
94
96
|
backend.expect :run_command, res, [String]
|
95
|
-
cls.bsd_stat('/path', backend).must_equal({
|
97
|
+
cls.bsd_stat('/path', backend, false).must_equal({
|
96
98
|
type: :directory,
|
97
99
|
mode: 01777,
|
98
100
|
owner: 'root',
|
99
|
-
|
101
|
+
uid: 0,
|
102
|
+
group: 'rootz',
|
103
|
+
gid: 1,
|
100
104
|
mtime: 1444522445,
|
101
105
|
size: 360,
|
102
106
|
selinux_label: nil,
|
@@ -12,15 +12,15 @@ describe 'file common' do
|
|
12
12
|
}
|
13
13
|
|
14
14
|
it 'provides the full path' do
|
15
|
-
cls.new(backend, 'C:\dir\file').path.must_equal 'C:\dir\file'
|
15
|
+
cls.new(backend, 'C:\dir\file', false).path.must_equal 'C:\dir\file'
|
16
16
|
end
|
17
17
|
|
18
18
|
it 'provides the basename to a unix path' do
|
19
|
-
cls.new(backend, 'C:\dir\file').basename.must_equal 'file'
|
19
|
+
cls.new(backend, 'C:\dir\file', false).basename.must_equal 'file'
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'provides the full path with whitespace' do
|
23
|
-
wf = cls.new(backend, 'C:\Program Files\file name')
|
23
|
+
wf = cls.new(backend, 'C:\Program Files\file name', false)
|
24
24
|
wf.path.must_equal 'C:\Program Files\file name'
|
25
25
|
wf.basename.must_equal 'file name'
|
26
26
|
end
|
@@ -28,11 +28,11 @@ describe 'file common' do
|
|
28
28
|
it 'reads file contents' do
|
29
29
|
out = rand.to_s
|
30
30
|
backend.mock_command('Get-Content("path") | Out-String', out)
|
31
|
-
cls.new(backend, 'path').content.must_equal out
|
31
|
+
cls.new(backend, 'path', false).content.must_equal out
|
32
32
|
end
|
33
33
|
|
34
34
|
it 'check escaping of invalid chars in path' do
|
35
|
-
wf = cls.new(nil, nil)
|
35
|
+
wf = cls.new(nil, nil, false)
|
36
36
|
wf.sanitize_filename('c:/test') .must_equal 'c:/test'
|
37
37
|
wf.sanitize_filename('c:/test directory') .must_equal 'c:/test directory'
|
38
38
|
%w{ < > " * ?}.each do |char|
|
data/test/unit/train_test.rb
CHANGED
@@ -129,4 +129,28 @@ describe Train do
|
|
129
129
|
res.must_equal nu
|
130
130
|
end
|
131
131
|
end
|
132
|
+
|
133
|
+
describe '#validate_backend' do
|
134
|
+
it 'just returns the backend if it is provided' do
|
135
|
+
x = rand
|
136
|
+
Train.validate_backend({ backend: x }).must_equal x
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'returns the local backend if nothing was provided' do
|
140
|
+
Train.validate_backend({}).must_equal :local
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'returns the default backend if nothing was provided' do
|
144
|
+
x = rand
|
145
|
+
Train.validate_backend({}, x).must_equal x
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'fails if no backend was given but a target is provided' do
|
149
|
+
proc { Train.validate_backend({ target: rand }) }.must_raise Train::UserError
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'fails if no backend was given but a host is provided' do
|
153
|
+
proc { Train.validate_backend({ host: rand }) }.must_raise Train::UserError
|
154
|
+
end
|
155
|
+
end
|
132
156
|
end
|
@@ -42,7 +42,7 @@ describe 'local file transport' do
|
|
42
42
|
|
43
43
|
it 'checks a file\'s link path' do
|
44
44
|
out = rand.to_s
|
45
|
-
File.stub :
|
45
|
+
File.stub :realpath, out do
|
46
46
|
File.stub :symlink?, true do
|
47
47
|
connection.file(rand.to_s).link_path.must_equal out
|
48
48
|
end
|
@@ -51,7 +51,9 @@ describe 'local file transport' do
|
|
51
51
|
|
52
52
|
describe 'file metadata' do
|
53
53
|
let(:stat) { Struct.new(:mode, :size, :mtime, :uid, :gid) }
|
54
|
-
let(:
|
54
|
+
let(:uid) { rand }
|
55
|
+
let(:gid) { rand }
|
56
|
+
let(:statres) { stat.new(00140755, rand, (rand*100).to_i, uid, gid) }
|
55
57
|
|
56
58
|
def meta_stub(method, param, &block)
|
57
59
|
pwres = Struct.new(:name)
|
@@ -63,43 +65,55 @@ describe 'local file transport' do
|
|
63
65
|
end
|
64
66
|
|
65
67
|
it 'recognizes type' do
|
66
|
-
meta_stub :
|
68
|
+
meta_stub :stat, statres do
|
67
69
|
connection.file(rand.to_s).type.must_equal :socket
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
71
73
|
it 'recognizes mode' do
|
72
|
-
meta_stub :
|
74
|
+
meta_stub :stat, statres do
|
73
75
|
connection.file(rand.to_s).mode.must_equal 00755
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
77
79
|
it 'recognizes mtime' do
|
78
|
-
meta_stub :
|
80
|
+
meta_stub :stat, statres do
|
79
81
|
connection.file(rand.to_s).mtime.must_equal statres.mtime
|
80
82
|
end
|
81
83
|
end
|
82
84
|
|
83
85
|
it 'recognizes size' do
|
84
|
-
meta_stub :
|
86
|
+
meta_stub :stat, statres do
|
85
87
|
connection.file(rand.to_s).size.must_equal statres.size
|
86
88
|
end
|
87
89
|
end
|
88
90
|
|
89
91
|
it 'recognizes owner' do
|
90
|
-
meta_stub :
|
92
|
+
meta_stub :stat, statres do
|
91
93
|
connection.file(rand.to_s).owner.must_equal 'owner'
|
92
94
|
end
|
93
95
|
end
|
94
96
|
|
97
|
+
it 'recognizes uid' do
|
98
|
+
meta_stub :stat, statres do
|
99
|
+
connection.file(rand.to_s).uid.must_equal uid
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
95
103
|
it 'recognizes group' do
|
96
|
-
meta_stub :
|
104
|
+
meta_stub :stat, statres do
|
97
105
|
connection.file(rand.to_s).group.must_equal 'group'
|
98
106
|
end
|
99
107
|
end
|
100
108
|
|
109
|
+
it 'recognizes gid' do
|
110
|
+
meta_stub :stat, statres do
|
111
|
+
connection.file(rand.to_s).gid.must_equal gid
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
101
115
|
it 'recognizes selinux label' do
|
102
|
-
meta_stub :
|
116
|
+
meta_stub :stat, statres do
|
103
117
|
label = rand.to_s
|
104
118
|
res = Train::Extras::CommandResult.new(label, nil, 0)
|
105
119
|
connection.stub :run_command, res do
|
@@ -107,6 +121,64 @@ describe 'local file transport' do
|
|
107
121
|
end
|
108
122
|
end
|
109
123
|
end
|
124
|
+
|
125
|
+
it 'recognizes source type' do
|
126
|
+
meta_stub :lstat, statres do
|
127
|
+
connection.file(rand.to_s).source.type.must_equal :socket
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'recognizes source mode' do
|
132
|
+
meta_stub :lstat, statres do
|
133
|
+
connection.file(rand.to_s).source.mode.must_equal 00755
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'recognizes source mtime' do
|
138
|
+
meta_stub :lstat, statres do
|
139
|
+
connection.file(rand.to_s).source.mtime.must_equal statres.mtime
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'recognizes source size' do
|
144
|
+
meta_stub :lstat, statres do
|
145
|
+
connection.file(rand.to_s).source.size.must_equal statres.size
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'recognizes source owner' do
|
150
|
+
meta_stub :lstat, statres do
|
151
|
+
connection.file(rand.to_s).source.owner.must_equal 'owner'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
it 'recognizes source uid' do
|
156
|
+
meta_stub :lstat, statres do
|
157
|
+
connection.file(rand.to_s).source.uid.must_equal uid
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'recognizes source owner' do
|
162
|
+
meta_stub :lstat, statres do
|
163
|
+
connection.file(rand.to_s).source.owner.must_equal 'owner'
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'recognizes source gid' do
|
168
|
+
meta_stub :lstat, statres do
|
169
|
+
connection.file(rand.to_s).source.gid.must_equal gid
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'recognizes source selinux label' do
|
174
|
+
meta_stub :lstat, statres do
|
175
|
+
label = rand.to_s
|
176
|
+
res = Train::Extras::CommandResult.new(label, nil, 0)
|
177
|
+
connection.stub :run_command, res do
|
178
|
+
connection.file(rand.to_s).source.selinux_label.must_equal label
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
110
182
|
end
|
111
183
|
|
112
184
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: r-train
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.11.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-04-
|
11
|
+
date: 2016-04-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -238,7 +238,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
238
238
|
version: '0'
|
239
239
|
requirements: []
|
240
240
|
rubyforge_project:
|
241
|
-
rubygems_version: 2.
|
241
|
+
rubygems_version: 2.5.1
|
242
242
|
signing_key:
|
243
243
|
specification_version: 4
|
244
244
|
summary: Transport interface to talk to different backends.
|