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