train-core 1.4.4
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 +7 -0
- data/CHANGELOG.md +780 -0
- data/Gemfile +36 -0
- data/LICENSE +201 -0
- data/README.md +197 -0
- data/lib/train.rb +158 -0
- data/lib/train/errors.rb +32 -0
- data/lib/train/extras.rb +11 -0
- data/lib/train/extras/command_wrapper.rb +137 -0
- data/lib/train/extras/stat.rb +132 -0
- data/lib/train/file.rb +151 -0
- data/lib/train/file/local.rb +75 -0
- data/lib/train/file/local/unix.rb +96 -0
- data/lib/train/file/local/windows.rb +63 -0
- data/lib/train/file/remote.rb +36 -0
- data/lib/train/file/remote/aix.rb +21 -0
- data/lib/train/file/remote/linux.rb +19 -0
- data/lib/train/file/remote/qnx.rb +41 -0
- data/lib/train/file/remote/unix.rb +106 -0
- data/lib/train/file/remote/windows.rb +94 -0
- data/lib/train/options.rb +80 -0
- data/lib/train/platforms.rb +84 -0
- data/lib/train/platforms/common.rb +34 -0
- data/lib/train/platforms/detect.rb +12 -0
- data/lib/train/platforms/detect/helpers/os_common.rb +145 -0
- data/lib/train/platforms/detect/helpers/os_linux.rb +75 -0
- data/lib/train/platforms/detect/helpers/os_windows.rb +120 -0
- data/lib/train/platforms/detect/scanner.rb +84 -0
- data/lib/train/platforms/detect/specifications/api.rb +15 -0
- data/lib/train/platforms/detect/specifications/os.rb +578 -0
- data/lib/train/platforms/detect/uuid.rb +34 -0
- data/lib/train/platforms/family.rb +26 -0
- data/lib/train/platforms/platform.rb +101 -0
- data/lib/train/plugins.rb +40 -0
- data/lib/train/plugins/base_connection.rb +169 -0
- data/lib/train/plugins/transport.rb +49 -0
- data/lib/train/transports/local.rb +232 -0
- data/lib/train/version.rb +7 -0
- data/train-core.gemspec +27 -0
- metadata +116 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Train
|
4
|
+
class File
|
5
|
+
class Local < Train::File
|
6
|
+
%w{
|
7
|
+
exist? file? socket? directory? symlink? pipe? size basename
|
8
|
+
}.each do |m|
|
9
|
+
define_method m.to_sym do
|
10
|
+
::File.method(m.to_sym).call(@path)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def content
|
15
|
+
@content ||= ::File.read(@path, encoding: 'UTF-8')
|
16
|
+
rescue StandardError => _
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def link_path
|
21
|
+
return nil unless symlink?
|
22
|
+
begin
|
23
|
+
@link_path ||= ::File.realpath(@path)
|
24
|
+
rescue Errno::ELOOP => _
|
25
|
+
# Leave it blank on symbolic loop, same as readlink
|
26
|
+
@link_path = ''
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def block_device?
|
31
|
+
::File.blockdev?(@path)
|
32
|
+
end
|
33
|
+
|
34
|
+
def character_device?
|
35
|
+
::File.chardev?(@path)
|
36
|
+
end
|
37
|
+
|
38
|
+
def type
|
39
|
+
case ::File.ftype(@path)
|
40
|
+
when 'blockSpecial'
|
41
|
+
:block_device
|
42
|
+
when 'characterSpecial'
|
43
|
+
:character_device
|
44
|
+
when 'link'
|
45
|
+
:symlink
|
46
|
+
when 'fifo'
|
47
|
+
:pipe
|
48
|
+
else
|
49
|
+
::File.ftype(@path).to_sym
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
%w{
|
54
|
+
mode owner group uid gid mtime selinux_label
|
55
|
+
}.each do |field|
|
56
|
+
define_method field.to_sym do
|
57
|
+
stat[field.to_sym]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def mode?(sth)
|
62
|
+
mode == sth
|
63
|
+
end
|
64
|
+
|
65
|
+
def linked_to?(dst)
|
66
|
+
link_path == dst
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# subclass requires are loaded after Train::File::Local is defined
|
73
|
+
# to avoid superclass mismatch errors
|
74
|
+
require 'train/file/local/unix'
|
75
|
+
require 'train/file/local/windows'
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'shellwords'
|
4
|
+
require 'train/extras/stat'
|
5
|
+
|
6
|
+
module Train
|
7
|
+
class File
|
8
|
+
class Local
|
9
|
+
class Unix < Train::File::Local
|
10
|
+
def sanitize_filename(path)
|
11
|
+
@spath = Shellwords.escape(path) || @path
|
12
|
+
end
|
13
|
+
|
14
|
+
def stat
|
15
|
+
return @stat if defined?(@stat)
|
16
|
+
|
17
|
+
begin
|
18
|
+
file_stat =
|
19
|
+
if @follow_symlink
|
20
|
+
::File.stat(@path)
|
21
|
+
else
|
22
|
+
::File.lstat(@path)
|
23
|
+
end
|
24
|
+
rescue StandardError => _err
|
25
|
+
return @stat = {}
|
26
|
+
end
|
27
|
+
|
28
|
+
@stat = {
|
29
|
+
type: Train::Extras::Stat.find_type(file_stat.mode),
|
30
|
+
mode: file_stat.mode & 07777,
|
31
|
+
mtime: file_stat.mtime.to_i,
|
32
|
+
size: file_stat.size,
|
33
|
+
owner: pw_username(file_stat.uid),
|
34
|
+
uid: file_stat.uid,
|
35
|
+
group: pw_groupname(file_stat.gid),
|
36
|
+
gid: file_stat.gid,
|
37
|
+
}
|
38
|
+
|
39
|
+
lstat = @follow_symlink ? ' -L' : ''
|
40
|
+
res = @backend.run_command("stat#{lstat} #{@spath} 2>/dev/null --printf '%C'")
|
41
|
+
if res.exit_status == 0 && !res.stdout.empty? && res.stdout != '?'
|
42
|
+
@stat[:selinux_label] = res.stdout.strip
|
43
|
+
end
|
44
|
+
|
45
|
+
@stat
|
46
|
+
end
|
47
|
+
|
48
|
+
def mounted
|
49
|
+
@mounted ||=
|
50
|
+
@backend.run_command("mount | grep -- ' on #{@spath} '")
|
51
|
+
end
|
52
|
+
|
53
|
+
def grouped_into?(sth)
|
54
|
+
group == sth
|
55
|
+
end
|
56
|
+
|
57
|
+
def unix_mode_mask(owner, type)
|
58
|
+
o = UNIX_MODE_OWNERS[owner.to_sym]
|
59
|
+
return nil if o.nil?
|
60
|
+
|
61
|
+
t = UNIX_MODE_TYPES[type.to_sym]
|
62
|
+
return nil if t.nil?
|
63
|
+
|
64
|
+
t & o
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def pw_username(uid)
|
70
|
+
Etc.getpwuid(uid).name
|
71
|
+
rescue ArgumentError => _
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def pw_groupname(gid)
|
76
|
+
Etc.getgrgid(gid).name
|
77
|
+
rescue ArgumentError => _
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
|
81
|
+
UNIX_MODE_OWNERS = {
|
82
|
+
all: 00777,
|
83
|
+
owner: 00700,
|
84
|
+
group: 00070,
|
85
|
+
other: 00007,
|
86
|
+
}.freeze
|
87
|
+
|
88
|
+
UNIX_MODE_TYPES = {
|
89
|
+
r: 00444,
|
90
|
+
w: 00222,
|
91
|
+
x: 00111,
|
92
|
+
}.freeze
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Train
|
4
|
+
class File
|
5
|
+
class Local
|
6
|
+
class Windows < Train::File::Local
|
7
|
+
# Ensures we do not use invalid characters for file names
|
8
|
+
# @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#naming_conventions
|
9
|
+
def sanitize_filename(path)
|
10
|
+
return if path.nil?
|
11
|
+
# we do not filter :, backslash and forward slash, since they are part of the path
|
12
|
+
@spath = path.gsub(/[<>"|?*]/, '')
|
13
|
+
end
|
14
|
+
|
15
|
+
def product_version
|
16
|
+
@product_version ||= @backend.run_command(
|
17
|
+
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"#{@spath}\").ProductVersion").stdout.chomp
|
18
|
+
end
|
19
|
+
|
20
|
+
def file_version
|
21
|
+
@file_version ||= @backend.run_command(
|
22
|
+
"[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"#{@spath}\").FileVersion").stdout.chomp
|
23
|
+
end
|
24
|
+
|
25
|
+
def owner
|
26
|
+
owner = @backend.run_command(
|
27
|
+
"Get-Acl \"#{@spath}\" | select -expand Owner").stdout.strip
|
28
|
+
return if owner.empty?
|
29
|
+
owner
|
30
|
+
end
|
31
|
+
|
32
|
+
def stat
|
33
|
+
return @stat if defined?(@stat)
|
34
|
+
|
35
|
+
begin
|
36
|
+
file_stat =
|
37
|
+
if @follow_symlink
|
38
|
+
::File.stat(@path)
|
39
|
+
else
|
40
|
+
::File.lstat(@path)
|
41
|
+
end
|
42
|
+
rescue StandardError => _err
|
43
|
+
return @stat = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
@stat = {
|
47
|
+
type: type,
|
48
|
+
mode: file_stat.mode,
|
49
|
+
mtime: file_stat.mtime.to_i,
|
50
|
+
size: file_stat.size,
|
51
|
+
owner: owner,
|
52
|
+
uid: file_stat.uid,
|
53
|
+
group: nil,
|
54
|
+
gid: file_stat.gid,
|
55
|
+
selinux_label: nil,
|
56
|
+
}
|
57
|
+
|
58
|
+
@stat
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Train
|
4
|
+
class File
|
5
|
+
class Remote < Train::File
|
6
|
+
def basename(suffix = nil, sep = '/')
|
7
|
+
fail 'Not yet supported: Suffix in file.basename' unless suffix.nil?
|
8
|
+
@basename ||= detect_filename(path, sep || '/')
|
9
|
+
end
|
10
|
+
|
11
|
+
def stat
|
12
|
+
return @stat if defined?(@stat)
|
13
|
+
@stat = Train::Extras::Stat.stat(@spath, @backend, @follow_symlink)
|
14
|
+
end
|
15
|
+
|
16
|
+
# helper methods provided to any implementing class
|
17
|
+
private
|
18
|
+
|
19
|
+
def detect_filename(path, sep)
|
20
|
+
idx = path.rindex(sep)
|
21
|
+
return path if idx.nil?
|
22
|
+
idx += 1
|
23
|
+
return detect_filename(path[0..-2], sep) if idx == path.length
|
24
|
+
path[idx..-1]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# subclass requires are loaded after Train::File::Remote is defined
|
31
|
+
# to avoid superclass mismatch errors
|
32
|
+
require 'train/file/remote/aix'
|
33
|
+
require 'train/file/remote/linux'
|
34
|
+
require 'train/file/remote/qnx'
|
35
|
+
require 'train/file/remote/unix'
|
36
|
+
require 'train/file/remote/windows'
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'train/file/remote/unix'
|
4
|
+
|
5
|
+
module Train
|
6
|
+
class File
|
7
|
+
class Remote
|
8
|
+
class Aix < Train::File::Remote::Unix
|
9
|
+
def link_path
|
10
|
+
return nil unless symlink?
|
11
|
+
@link_path ||=
|
12
|
+
@backend.run_command("perl -e 'print readlink shift' #{@spath}").stdout.chomp
|
13
|
+
end
|
14
|
+
|
15
|
+
def mounted
|
16
|
+
@mounted ||= @backend.run_command("lsfs -c #{@spath}")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'train/file/remote/unix'
|
4
|
+
|
5
|
+
module Train
|
6
|
+
class File
|
7
|
+
class Remote
|
8
|
+
class Linux < Train::File::Remote::Unix
|
9
|
+
def content
|
10
|
+
return @content if defined?(@content)
|
11
|
+
@content = @backend.run_command("cat #{@spath} || echo -n").stdout
|
12
|
+
return @content unless @content.empty?
|
13
|
+
@content = nil if directory? or size.nil? or size > 0
|
14
|
+
@content
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
# author: Dominik Richter
|
5
|
+
|
6
|
+
require 'train/file/remote/unix'
|
7
|
+
|
8
|
+
module Train
|
9
|
+
class File
|
10
|
+
class Remote
|
11
|
+
class Qnx < Train::File::Remote::Unix
|
12
|
+
def content
|
13
|
+
cat = 'cat'
|
14
|
+
cat = '/proc/boot/cat' if @backend.os[:release].to_i >= 7
|
15
|
+
@content ||= case
|
16
|
+
when !exist?
|
17
|
+
nil
|
18
|
+
else
|
19
|
+
@backend.run_command("#{cat} #{@spath}").stdout || ''
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def type
|
24
|
+
if @backend.run_command("file #{@spath}").stdout.include?('directory')
|
25
|
+
return :directory
|
26
|
+
else
|
27
|
+
return :file
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
%w{
|
32
|
+
mode owner group uid gid mtime size selinux_label link_path mounted stat
|
33
|
+
}.each do |field|
|
34
|
+
define_method field.to_sym do
|
35
|
+
fail NotImplementedError, "QNX does not implement the #{field}() method yet."
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
module Train
|
6
|
+
class File
|
7
|
+
class Remote
|
8
|
+
class Unix < Train::File::Remote
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
def sanitize_filename(path)
|
12
|
+
@spath = Shellwords.escape(path) || @path
|
13
|
+
end
|
14
|
+
|
15
|
+
def content
|
16
|
+
@content ||=
|
17
|
+
if !exist? || directory?
|
18
|
+
nil
|
19
|
+
elsif size.nil? || size.zero?
|
20
|
+
''
|
21
|
+
else
|
22
|
+
@backend.run_command("cat #{@spath}").stdout || ''
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exist?
|
27
|
+
@exist ||= (
|
28
|
+
f = @follow_symlink ? '' : " || test -L #{@spath}"
|
29
|
+
@backend.run_command("test -e #{@spath}"+f)
|
30
|
+
.exit_status == 0
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
def mounted
|
35
|
+
@mounted ||=
|
36
|
+
@backend.run_command("mount | grep -- ' on #{@spath} '")
|
37
|
+
end
|
38
|
+
|
39
|
+
%w{
|
40
|
+
type mode owner group uid gid mtime size selinux_label
|
41
|
+
}.each do |field|
|
42
|
+
define_method field.to_sym do
|
43
|
+
stat[field.to_sym]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def mode?(sth)
|
48
|
+
mode == sth
|
49
|
+
end
|
50
|
+
|
51
|
+
def grouped_into?(sth)
|
52
|
+
group == sth
|
53
|
+
end
|
54
|
+
|
55
|
+
def linked_to?(dst)
|
56
|
+
link_path == dst
|
57
|
+
end
|
58
|
+
|
59
|
+
def link_path
|
60
|
+
symlink? ? path : nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def unix_mode_mask(owner, type)
|
64
|
+
o = UNIX_MODE_OWNERS[owner.to_sym]
|
65
|
+
return nil if o.nil?
|
66
|
+
|
67
|
+
t = UNIX_MODE_TYPES[type.to_sym]
|
68
|
+
return nil if t.nil?
|
69
|
+
|
70
|
+
t & o
|
71
|
+
end
|
72
|
+
|
73
|
+
def path
|
74
|
+
return @path unless @follow_symlink && symlink?
|
75
|
+
@link_path ||= read_target_path
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# Returns full path of a symlink target(real dest) or '' on symlink loop
|
81
|
+
def read_target_path
|
82
|
+
full_path = @backend.run_command("readlink -n #{@spath} -f").stdout
|
83
|
+
# Needed for some OSes like OSX that returns relative path
|
84
|
+
# when the link and target are in the same directory
|
85
|
+
if !full_path.start_with?('/') && full_path != ''
|
86
|
+
full_path = ::File.expand_path("../#{full_path}", @spath)
|
87
|
+
end
|
88
|
+
full_path
|
89
|
+
end
|
90
|
+
|
91
|
+
UNIX_MODE_OWNERS = {
|
92
|
+
all: 00777,
|
93
|
+
owner: 00700,
|
94
|
+
group: 00070,
|
95
|
+
other: 00007,
|
96
|
+
}.freeze
|
97
|
+
|
98
|
+
UNIX_MODE_TYPES = {
|
99
|
+
r: 00444,
|
100
|
+
w: 00222,
|
101
|
+
x: 00111,
|
102
|
+
}.freeze
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|