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.
- checksums.yaml +7 -0
- data/.rubocop.yml +71 -0
- data/CHANGELOG.md +308 -0
- data/Gemfile +30 -0
- data/LICENSE +201 -0
- data/README.md +156 -0
- data/Rakefile +148 -0
- data/lib/train.rb +117 -0
- data/lib/train/errors.rb +23 -0
- data/lib/train/extras.rb +17 -0
- data/lib/train/extras/command_wrapper.rb +148 -0
- data/lib/train/extras/file_aix.rb +20 -0
- data/lib/train/extras/file_common.rb +161 -0
- data/lib/train/extras/file_linux.rb +16 -0
- data/lib/train/extras/file_unix.rb +79 -0
- data/lib/train/extras/file_windows.rb +91 -0
- data/lib/train/extras/linux_lsb.rb +60 -0
- data/lib/train/extras/os_common.rb +136 -0
- data/lib/train/extras/os_detect_darwin.rb +32 -0
- data/lib/train/extras/os_detect_linux.rb +148 -0
- data/lib/train/extras/os_detect_unix.rb +99 -0
- data/lib/train/extras/os_detect_windows.rb +57 -0
- data/lib/train/extras/stat.rb +133 -0
- data/lib/train/options.rb +80 -0
- data/lib/train/plugins.rb +40 -0
- data/lib/train/plugins/base_connection.rb +86 -0
- data/lib/train/plugins/transport.rb +49 -0
- data/lib/train/transports/docker.rb +103 -0
- data/lib/train/transports/local.rb +52 -0
- data/lib/train/transports/local_file.rb +90 -0
- data/lib/train/transports/local_os.rb +51 -0
- data/lib/train/transports/mock.rb +147 -0
- data/lib/train/transports/ssh.rb +163 -0
- data/lib/train/transports/ssh_connection.rb +225 -0
- data/lib/train/transports/winrm.rb +184 -0
- data/lib/train/transports/winrm_connection.rb +194 -0
- data/lib/train/version.rb +7 -0
- data/test/integration/.kitchen.yml +43 -0
- data/test/integration/Berksfile +3 -0
- data/test/integration/bootstrap.sh +17 -0
- data/test/integration/chefignore +1 -0
- data/test/integration/cookbooks/test/metadata.rb +1 -0
- data/test/integration/cookbooks/test/recipes/default.rb +100 -0
- data/test/integration/cookbooks/test/recipes/prep_files.rb +47 -0
- data/test/integration/docker_run.rb +153 -0
- data/test/integration/docker_test.rb +24 -0
- data/test/integration/docker_test_container.rb +24 -0
- data/test/integration/helper.rb +61 -0
- data/test/integration/sudo/customcommand.rb +15 -0
- data/test/integration/sudo/nopasswd.rb +16 -0
- data/test/integration/sudo/passwd.rb +21 -0
- data/test/integration/sudo/reqtty.rb +17 -0
- data/test/integration/sudo/run_as.rb +12 -0
- data/test/integration/test-travis-1.yaml +13 -0
- data/test/integration/test-travis-2.yaml +13 -0
- data/test/integration/test_local.rb +19 -0
- data/test/integration/test_ssh.rb +39 -0
- data/test/integration/tests/path_block_device_test.rb +74 -0
- data/test/integration/tests/path_character_device_test.rb +74 -0
- data/test/integration/tests/path_file_test.rb +79 -0
- data/test/integration/tests/path_folder_test.rb +90 -0
- data/test/integration/tests/path_missing_test.rb +77 -0
- data/test/integration/tests/path_pipe_test.rb +78 -0
- data/test/integration/tests/path_symlink_test.rb +95 -0
- data/test/integration/tests/run_command_test.rb +28 -0
- data/test/unit/extras/command_wrapper_test.rb +78 -0
- data/test/unit/extras/file_common_test.rb +180 -0
- data/test/unit/extras/linux_file_test.rb +167 -0
- data/test/unit/extras/os_common_test.rb +269 -0
- data/test/unit/extras/os_detect_linux_test.rb +189 -0
- data/test/unit/extras/os_detect_windows_test.rb +99 -0
- data/test/unit/extras/stat_test.rb +148 -0
- data/test/unit/extras/windows_file_test.rb +44 -0
- data/test/unit/helper.rb +7 -0
- data/test/unit/plugins/connection_test.rb +44 -0
- data/test/unit/plugins/transport_test.rb +111 -0
- data/test/unit/plugins_test.rb +22 -0
- data/test/unit/train_test.rb +156 -0
- data/test/unit/transports/local_file_test.rb +184 -0
- data/test/unit/transports/local_test.rb +87 -0
- data/test/unit/transports/mock_test.rb +87 -0
- data/test/unit/transports/ssh_test.rb +109 -0
- data/test/unit/version_test.rb +8 -0
- data/test/windows/local_test.rb +46 -0
- data/test/windows/winrm_test.rb +52 -0
- data/train.gemspec +38 -0
- metadata +295 -0
@@ -0,0 +1,148 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
require 'base64'
|
6
|
+
require 'winrm'
|
7
|
+
require 'train/errors'
|
8
|
+
|
9
|
+
module Train::Extras
|
10
|
+
# Define the interface of all command wrappers.
|
11
|
+
class CommandWrapperBase
|
12
|
+
# Verify that the command wrapper is initialized properly and working.
|
13
|
+
#
|
14
|
+
# @return [Any] verification result, nil if all went well, otherwise a message
|
15
|
+
def verify
|
16
|
+
fail Train::ClientError, "#{self.class} does not implement #verify()"
|
17
|
+
end
|
18
|
+
|
19
|
+
# Wrap a command and return the augmented command which can be executed.
|
20
|
+
#
|
21
|
+
# @param [Strin] command that will be wrapper
|
22
|
+
# @return [String] result of wrapping the command
|
23
|
+
def run(_command)
|
24
|
+
fail Train::ClientError, "#{self.class} does not implement #run(command)"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Wrap linux commands and add functionality like sudo.
|
29
|
+
class LinuxCommand < CommandWrapperBase
|
30
|
+
Train::Options.attach(self)
|
31
|
+
|
32
|
+
option :sudo, default: false
|
33
|
+
option :sudo_options, default: nil
|
34
|
+
option :sudo_password, default: nil
|
35
|
+
option :sudo_command, default: nil
|
36
|
+
option :user
|
37
|
+
|
38
|
+
def initialize(backend, options)
|
39
|
+
@backend = backend
|
40
|
+
validate_options(options)
|
41
|
+
|
42
|
+
@sudo = options[:sudo]
|
43
|
+
@sudo_options = options[:sudo_options]
|
44
|
+
@sudo_password = options[:sudo_password]
|
45
|
+
@sudo_command = options[:sudo_command]
|
46
|
+
@user = options[:user]
|
47
|
+
@prefix = build_prefix
|
48
|
+
end
|
49
|
+
|
50
|
+
# (see CommandWrapperBase::verify)
|
51
|
+
def verify
|
52
|
+
res = @backend.run_command(run('echo'))
|
53
|
+
return nil if res.exit_status == 0
|
54
|
+
rawerr = res.stdout + ' ' + res.stderr
|
55
|
+
|
56
|
+
{
|
57
|
+
'Sorry, try again' => 'Wrong sudo password.',
|
58
|
+
'sudo: no tty present and no askpass program specified' =>
|
59
|
+
'Sudo requires a password, please configure it.',
|
60
|
+
'sudo: command not found' =>
|
61
|
+
"Can't find sudo command. Please either install and "\
|
62
|
+
'configure it on the target or deactivate sudo.',
|
63
|
+
'sudo: sorry, you must have a tty to run sudo' =>
|
64
|
+
'Sudo requires a TTY. Please see the README on how to configure '\
|
65
|
+
'sudo to allow for non-interactive usage.',
|
66
|
+
}.each do |sudo, human|
|
67
|
+
rawerr = human if rawerr.include? sudo
|
68
|
+
end
|
69
|
+
|
70
|
+
rawerr
|
71
|
+
end
|
72
|
+
|
73
|
+
# (see CommandWrapperBase::run)
|
74
|
+
def run(command)
|
75
|
+
@prefix + command
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.active?(options)
|
79
|
+
options.is_a?(Hash) && options[:sudo]
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def build_prefix
|
85
|
+
return '' unless @sudo
|
86
|
+
return '' if @user == 'root'
|
87
|
+
|
88
|
+
res = (@sudo_command || 'sudo') + ' '
|
89
|
+
|
90
|
+
unless @sudo_password.nil?
|
91
|
+
b64pw = Base64.strict_encode64(@sudo_password + "\n")
|
92
|
+
res = "echo #{b64pw} | base64 -d | #{res}-S "
|
93
|
+
end
|
94
|
+
|
95
|
+
res << @sudo_options.to_s + ' ' unless @sudo_options.nil?
|
96
|
+
|
97
|
+
res
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# this is required if you run locally on windows,
|
102
|
+
# winrm connections provide a PowerShell shell automatically
|
103
|
+
# TODO: only activate in local mode
|
104
|
+
class PowerShellCommand < CommandWrapperBase
|
105
|
+
Train::Options.attach(self)
|
106
|
+
|
107
|
+
def initialize(backend, options)
|
108
|
+
@backend = backend
|
109
|
+
validate_options(options)
|
110
|
+
end
|
111
|
+
|
112
|
+
def run(script)
|
113
|
+
# wrap the script to ensure we always run it via powershell
|
114
|
+
# especially in local mode, we cannot be sure that we get a Powershell
|
115
|
+
# we may just get a `cmd`.
|
116
|
+
# TODO: we may want to opt for powershell.exe -command instead of `encodeCommand`
|
117
|
+
"powershell -encodedCommand #{WinRM::PowershellScript.new(safe_script(script)).encoded}"
|
118
|
+
end
|
119
|
+
|
120
|
+
# reused from https://github.com/WinRb/WinRM/blob/master/lib/winrm/command_executor.rb
|
121
|
+
# suppress the progress stream from leaking to stderr
|
122
|
+
def safe_script(script)
|
123
|
+
"$ProgressPreference='SilentlyContinue';" + script
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_s
|
127
|
+
'PowerShell CommandWrapper'
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
class CommandWrapper
|
132
|
+
include_options LinuxCommand
|
133
|
+
|
134
|
+
def self.load(transport, options)
|
135
|
+
if transport.os.unix?
|
136
|
+
return nil unless LinuxCommand.active?(options)
|
137
|
+
res = LinuxCommand.new(transport, options)
|
138
|
+
msg = res.verify
|
139
|
+
fail Train::UserError, "Sudo failed: #{msg}" unless msg.nil?
|
140
|
+
res
|
141
|
+
# only use powershell command for local transport. winrm transport
|
142
|
+
# uses powershell as default
|
143
|
+
elsif transport.os.windows? && transport.class == Train::Transports::Local::Connection
|
144
|
+
PowerShellCommand.new(transport, options)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'shellwords'
|
4
|
+
require 'train/extras/stat'
|
5
|
+
|
6
|
+
module Train::Extras
|
7
|
+
class AixFile < UnixFile
|
8
|
+
def link_path
|
9
|
+
return nil unless symlink?
|
10
|
+
@link_path ||=
|
11
|
+
@backend.run_command("perl -e 'print readlink shift' #{@spath}")
|
12
|
+
.stdout.chomp
|
13
|
+
end
|
14
|
+
|
15
|
+
def mounted
|
16
|
+
@mounted ||=
|
17
|
+
@backend.run_command("lsfs -c #{@spath}")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
require 'digest/sha2'
|
6
|
+
require 'digest/md5'
|
7
|
+
|
8
|
+
module Train::Extras
|
9
|
+
class FileCommon # rubocop:disable Metrics/ClassLength
|
10
|
+
# interface methods: these fields should be implemented by every
|
11
|
+
# backend File
|
12
|
+
%w{
|
13
|
+
exist? mode owner group uid gid content mtime size selinux_label path
|
14
|
+
product_version file_version
|
15
|
+
}.each do |m|
|
16
|
+
define_method m.to_sym do
|
17
|
+
fail NotImplementedError, "File must implement the #{m}() method."
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(backend, path, follow_symlink = true)
|
22
|
+
@backend = backend
|
23
|
+
@path = path || ''
|
24
|
+
@follow_symlink = follow_symlink
|
25
|
+
end
|
26
|
+
|
27
|
+
def type
|
28
|
+
:unknown
|
29
|
+
end
|
30
|
+
|
31
|
+
# The following methods can be overwritten by a derived class
|
32
|
+
# if desired, to e.g. achieve optimizations.
|
33
|
+
|
34
|
+
def md5sum
|
35
|
+
res = Digest::MD5.new
|
36
|
+
res.update(content)
|
37
|
+
res.hexdigest
|
38
|
+
rescue TypeError => _
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def sha256sum
|
43
|
+
res = Digest::SHA256.new
|
44
|
+
res.update(content)
|
45
|
+
res.hexdigest
|
46
|
+
rescue TypeError => _
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
# Additional methods for convenience
|
51
|
+
|
52
|
+
def file?
|
53
|
+
type == :file
|
54
|
+
end
|
55
|
+
|
56
|
+
def block_device?
|
57
|
+
type == :block_device
|
58
|
+
end
|
59
|
+
|
60
|
+
def character_device?
|
61
|
+
type == :character_device
|
62
|
+
end
|
63
|
+
|
64
|
+
def socket?
|
65
|
+
type == :socket
|
66
|
+
end
|
67
|
+
|
68
|
+
def directory?
|
69
|
+
type == :directory
|
70
|
+
end
|
71
|
+
|
72
|
+
def symlink?
|
73
|
+
source.type == :symlink
|
74
|
+
end
|
75
|
+
|
76
|
+
def source_path
|
77
|
+
@path
|
78
|
+
end
|
79
|
+
|
80
|
+
def source
|
81
|
+
if @follow_symlink
|
82
|
+
self.class.new(@backend, @path, false)
|
83
|
+
else
|
84
|
+
self
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def pipe?
|
89
|
+
type == :pipe
|
90
|
+
end
|
91
|
+
|
92
|
+
def mode?(sth)
|
93
|
+
mode == sth
|
94
|
+
end
|
95
|
+
|
96
|
+
def owned_by?(sth)
|
97
|
+
owner == sth
|
98
|
+
end
|
99
|
+
|
100
|
+
def grouped_into?(sth)
|
101
|
+
group == sth
|
102
|
+
end
|
103
|
+
|
104
|
+
def linked_to?(dst)
|
105
|
+
link_path == dst
|
106
|
+
end
|
107
|
+
|
108
|
+
def link_path
|
109
|
+
symlink? ? path : nil
|
110
|
+
end
|
111
|
+
|
112
|
+
def version?(version)
|
113
|
+
product_version == version or
|
114
|
+
file_version == version
|
115
|
+
end
|
116
|
+
|
117
|
+
def unix_mode_mask(owner, type)
|
118
|
+
o = UNIX_MODE_OWNERS[owner.to_sym]
|
119
|
+
return nil if o.nil?
|
120
|
+
|
121
|
+
t = UNIX_MODE_TYPES[type.to_sym]
|
122
|
+
return nil if t.nil?
|
123
|
+
|
124
|
+
t & o
|
125
|
+
end
|
126
|
+
|
127
|
+
def mounted?
|
128
|
+
!mounted.nil? && !mounted.stdout.nil? && !mounted.stdout.empty?
|
129
|
+
end
|
130
|
+
|
131
|
+
def basename(suffix = nil, sep = '/')
|
132
|
+
fail 'Not yet supported: Suffix in file.basename' unless suffix.nil?
|
133
|
+
@basename ||= detect_filename(path, sep || '/')
|
134
|
+
end
|
135
|
+
|
136
|
+
# helper methods provided to any implementing class
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def detect_filename(path, sep)
|
141
|
+
idx = path.rindex(sep)
|
142
|
+
return path if idx.nil?
|
143
|
+
idx += 1
|
144
|
+
return detect_filename(path[0..-2], sep) if idx == path.length
|
145
|
+
path[idx..-1]
|
146
|
+
end
|
147
|
+
|
148
|
+
UNIX_MODE_OWNERS = {
|
149
|
+
all: 00777,
|
150
|
+
owner: 00700,
|
151
|
+
group: 00070,
|
152
|
+
other: 00007,
|
153
|
+
}.freeze
|
154
|
+
|
155
|
+
UNIX_MODE_TYPES = {
|
156
|
+
r: 00444,
|
157
|
+
w: 00222,
|
158
|
+
x: 00111,
|
159
|
+
}.freeze
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
module Train::Extras
|
6
|
+
class LinuxFile < UnixFile
|
7
|
+
def content
|
8
|
+
return @content if defined?(@content)
|
9
|
+
@content = @backend.run_command(
|
10
|
+
"cat #{@spath} || echo -n").stdout
|
11
|
+
return @content unless @content.empty?
|
12
|
+
@content = nil if directory? or size.nil? or size > 0
|
13
|
+
@content
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
require 'shellwords'
|
6
|
+
require 'train/extras/stat'
|
7
|
+
|
8
|
+
module Train::Extras
|
9
|
+
class UnixFile < FileCommon
|
10
|
+
attr_reader :path
|
11
|
+
def initialize(backend, path, follow_symlink = true)
|
12
|
+
super(backend, path, follow_symlink)
|
13
|
+
@spath = Shellwords.escape(@path)
|
14
|
+
end
|
15
|
+
|
16
|
+
def content
|
17
|
+
@content ||= case
|
18
|
+
when !exist?, directory?
|
19
|
+
nil
|
20
|
+
when size.nil?, size == 0
|
21
|
+
''
|
22
|
+
else
|
23
|
+
@backend.run_command("cat #{@spath}").stdout || ''
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def exist?
|
28
|
+
@exist ||= (
|
29
|
+
f = @follow_symlink ? '' : " || test -L #{@spath}"
|
30
|
+
@backend.run_command("test -e #{@spath}"+f)
|
31
|
+
.exit_status == 0
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def path
|
36
|
+
return @path unless @follow_symlink && symlink?
|
37
|
+
@link_path ||= read_target_path
|
38
|
+
end
|
39
|
+
|
40
|
+
def mounted
|
41
|
+
@mounted ||=
|
42
|
+
@backend.run_command("mount | grep -- ' on #{@spath} '")
|
43
|
+
end
|
44
|
+
|
45
|
+
%w{
|
46
|
+
type mode owner group uid gid mtime size selinux_label
|
47
|
+
}.each do |field|
|
48
|
+
define_method field.to_sym do
|
49
|
+
stat[field.to_sym]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def product_version
|
54
|
+
nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def file_version
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
|
61
|
+
def stat
|
62
|
+
return @stat if defined?(@stat)
|
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
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# author: Dominik Richter
|
3
|
+
# author: Christoph Hartmann
|
4
|
+
|
5
|
+
require 'shellwords'
|
6
|
+
require 'train/extras/stat'
|
7
|
+
|
8
|
+
# PS C:\Users\Administrator> Get-Item -Path C:\test.txt | Select-Object -Property BaseName, FullName, IsReadOnly, Exists,
|
9
|
+
# LinkType, Mode, VersionInfo, Owner, Archive, Hidden, ReadOnly, System | ConvertTo-Json
|
10
|
+
|
11
|
+
module Train::Extras
|
12
|
+
class WindowsFile < FileCommon
|
13
|
+
attr_reader :path
|
14
|
+
def initialize(backend, path, follow_symlink = false)
|
15
|
+
super(backend, path, follow_symlink)
|
16
|
+
@spath = sanitize_filename(@path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def basename(suffix = nil, sep = '\\')
|
20
|
+
super(suffix, sep)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Ensures we do not use invalid characters for file names
|
24
|
+
# @see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#naming_conventions
|
25
|
+
def sanitize_filename(filename)
|
26
|
+
return if filename.nil?
|
27
|
+
# we do not filter :, backslash and forward slash, since they are part of the path
|
28
|
+
filename.gsub(/[<>"|?*]/, '')
|
29
|
+
end
|
30
|
+
|
31
|
+
def content
|
32
|
+
return @content if defined?(@content)
|
33
|
+
@content = @backend.run_command(
|
34
|
+
"Get-Content(\"#{@spath}\") | Out-String").stdout
|
35
|
+
return @content unless @content.empty?
|
36
|
+
@content = nil if directory? # or size.nil? or size > 0
|
37
|
+
@content
|
38
|
+
end
|
39
|
+
|
40
|
+
def exist?
|
41
|
+
return @exist if defined?(@exist)
|
42
|
+
@exist = @backend.run_command(
|
43
|
+
"(Test-Path -Path \"#{@spath}\").ToString()").stdout.chomp == 'True'
|
44
|
+
end
|
45
|
+
|
46
|
+
def link_path
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def mounted
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def type
|
55
|
+
if attributes.include?('Archive')
|
56
|
+
return :file
|
57
|
+
elsif attributes.include?('Directory')
|
58
|
+
return :directory
|
59
|
+
end
|
60
|
+
:unknown
|
61
|
+
end
|
62
|
+
|
63
|
+
%w{
|
64
|
+
mode owner group uid gid mtime size selinux_label
|
65
|
+
}.each do |field|
|
66
|
+
define_method field.to_sym do
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def product_version
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def file_version
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def stat
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def attributes
|
86
|
+
return @attributes if defined?(@attributes)
|
87
|
+
@attributes = @backend.run_command(
|
88
|
+
"(Get-ItemProperty -Path \"#{@spath}\").attributes.ToString()").stdout.chomp.split(/\s*,\s*/)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|