mixlib-shellout 2.2.6 → 2.2.7

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,24 +1,24 @@
1
- require 'rspec/core/rake_task'
2
- require 'rubygems/package_task'
3
- require 'mixlib/shellout/version'
4
-
5
- Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
6
- gemspec = eval(IO.read(gemspec_path))
7
- Gem::PackageTask.new(gemspec).define
8
- end
9
-
10
- desc "Run all specs in spec directory"
11
- RSpec::Core::RakeTask.new(:spec) do |t|
12
- t.pattern = FileList['spec/**/*_spec.rb']
13
- end
14
-
15
- desc "Build it and ship it"
16
- task ship: [:clobber_package, :gem] do
17
- sh("git tag #{Mixlib::ShellOut::VERSION}")
18
- sh("git push opscode --tags")
19
- Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
20
- sh("gem push #{built_gem}")
21
- end
22
- end
23
-
24
- task default: :spec
1
+ require 'rspec/core/rake_task'
2
+ require 'rubygems/package_task'
3
+ require 'mixlib/shellout/version'
4
+
5
+ Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
6
+ gemspec = eval(IO.read(gemspec_path))
7
+ Gem::PackageTask.new(gemspec).define
8
+ end
9
+
10
+ desc "Run all specs in spec directory"
11
+ RSpec::Core::RakeTask.new(:spec) do |t|
12
+ t.pattern = FileList['spec/**/*_spec.rb']
13
+ end
14
+
15
+ desc "Build it and ship it"
16
+ task ship: [:clobber_package, :gem] do
17
+ # sh("git tag #{Mixlib::ShellOut::VERSION}")
18
+ sh("git push opscode --tags")
19
+ Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
20
+ sh("gem push #{built_gem}")
21
+ end
22
+ end
23
+
24
+ task default: :spec
@@ -1,357 +1,357 @@
1
- #--
2
- # Author:: Daniel DeLeo (<dan@opscode.com>)
3
- # Copyright:: Copyright (c) 2010, 2011 Opscode, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- require 'etc'
20
- require 'tmpdir'
21
- require 'fcntl'
22
- require 'mixlib/shellout/exceptions'
23
-
24
- module Mixlib
25
-
26
- class ShellOut
27
- READ_WAIT_TIME = 0.01
28
- READ_SIZE = 4096
29
- DEFAULT_READ_TIMEOUT = 600
30
-
31
- if RUBY_PLATFORM =~ /mswin|mingw32|windows/
32
- require 'mixlib/shellout/windows'
33
- include ShellOut::Windows
34
- else
35
- require 'mixlib/shellout/unix'
36
- include ShellOut::Unix
37
- end
38
-
39
- # User the command will run as. Normally set via options passed to new
40
- attr_accessor :user
41
- attr_accessor :domain
42
- attr_accessor :password
43
- # TODO remove
44
- attr_accessor :with_logon
45
-
46
- # Whether to simulate logon as the user. Normally set via options passed to new
47
- # Always enabled on windows
48
- attr_accessor :login
49
-
50
- # Group the command will run as. Normally set via options passed to new
51
- attr_accessor :group
52
-
53
- # Working directory for the subprocess. Normally set via options to new
54
- attr_accessor :cwd
55
-
56
- # An Array of acceptable exit codes. #error? (and #error!) use this list
57
- # to determine if the command was successful. Normally set via options to new
58
- attr_accessor :valid_exit_codes
59
-
60
- # When live_stdout is set, the stdout of the subprocess will be copied to it
61
- # as the subprocess is running.
62
- attr_accessor :live_stdout
63
-
64
- # When live_stderr is set, the stderr of the subprocess will be copied to it
65
- # as the subprocess is running.
66
- attr_accessor :live_stderr
67
-
68
- # ShellOut will push data from :input down the stdin of the subprocss.
69
- # Normally set via options passed to new.
70
- # Default: nil
71
- attr_accessor :input
72
-
73
- # If a logger is set, ShellOut will log a message before it executes the
74
- # command.
75
- attr_accessor :logger
76
-
77
- # The log level at which ShellOut should log.
78
- attr_accessor :log_level
79
-
80
- # A string which will be prepended to the log message.
81
- attr_accessor :log_tag
82
-
83
- # The command to be executed.
84
- attr_reader :command
85
-
86
- # The umask that will be set for the subcommand.
87
- attr_reader :umask
88
-
89
- # Environment variables that will be set for the subcommand. Refer to the
90
- # documentation of new to understand how ShellOut interprets this.
91
- attr_accessor :environment
92
-
93
- # The maximum time this command is allowed to run. Usually set via options
94
- # to new
95
- attr_writer :timeout
96
-
97
- # The amount of time the subcommand took to execute
98
- attr_reader :execution_time
99
-
100
- # Data written to stdout by the subprocess
101
- attr_reader :stdout
102
-
103
- # Data written to stderr by the subprocess
104
- attr_reader :stderr
105
-
106
- # A Process::Status (or ducktype) object collected when the subprocess is
107
- # reaped.
108
- attr_reader :status
109
-
110
- attr_reader :stdin_pipe, :stdout_pipe, :stderr_pipe, :process_status_pipe
111
-
112
- # === Arguments:
113
- # Takes a single command, or a list of command fragments. These are used
114
- # as arguments to Kernel.exec. See the Kernel.exec documentation for more
115
- # explanation of how arguments are evaluated. The last argument can be an
116
- # options Hash.
117
- # === Options:
118
- # If the last argument is a Hash, it is removed from the list of args passed
119
- # to exec and used as an options hash. The following options are available:
120
- # * +user+: the user the commmand should run as. if an integer is given, it is
121
- # used as a uid. A string is treated as a username and resolved to a uid
122
- # with Etc.getpwnam
123
- # * +group+: the group the command should run as. works similarly to +user+
124
- # * +cwd+: the directory to chdir to before running the command
125
- # * +umask+: a umask to set before running the command. If given as an Integer,
126
- # be sure to use two leading zeros so it's parsed as Octal. A string will
127
- # be treated as an octal integer
128
- # * +returns+: one or more Integer values to use as valid exit codes for the
129
- # subprocess. This only has an effect if you call +error!+ after
130
- # +run_command+.
131
- # * +environment+: a Hash of environment variables to set before the command
132
- # is run.
133
- # * +timeout+: a Numeric value for the number of seconds to wait on the
134
- # child process before raising an Exception. This is calculated as the
135
- # total amount of time that ShellOut waited on the child process without
136
- # receiving any output (i.e., IO.select returned nil). Default is 60
137
- # seconds. Note: the stdlib Timeout library is not used.
138
- # * +input+: A String of data to be passed to the subcommand. This is
139
- # written to the child process' stdin stream before the process is
140
- # launched. The child's stdin stream will be a pipe, so the size of input
141
- # data should not exceed the system's default pipe capacity (4096 bytes
142
- # is a safe value, though on newer Linux systems the capacity is 64k by
143
- # default).
144
- # * +live_stream+: An IO or Logger-like object (must respond to the append
145
- # operator +<<+) that will receive data as ShellOut reads it from the
146
- # child process. Generally this is used to copy data from the child to
147
- # the parent's stdout so that users may observe the progress of
148
- # long-running commands.
149
- # * +login+: Whether to simulate a login (set secondary groups, primary group, environment
150
- # variables etc) as done by the OS in an actual login
151
- # === Examples:
152
- # Invoke find(1) to search for .rb files:
153
- # find = Mixlib::ShellOut.new("find . -name '*.rb'")
154
- # find.run_command
155
- # # If all went well, the results are on +stdout+
156
- # puts find.stdout
157
- # # find(1) prints diagnostic info to STDERR:
158
- # puts "error messages" + find.stderr
159
- # # Raise an exception if it didn't exit with 0
160
- # find.error!
161
- # Run a command as the +www+ user with no extra ENV settings from +/tmp+
162
- # cmd = Mixlib::ShellOut.new("apachectl", "start", :user => 'www', :env => nil, :cwd => '/tmp')
163
- # cmd.run_command # etc.
164
- def initialize(*command_args)
165
- @stdout, @stderr, @process_status = '', '', ''
166
- @live_stdout = @live_stderr = nil
167
- @input = nil
168
- @log_level = :debug
169
- @log_tag = nil
170
- @environment = {}
171
- @cwd = nil
172
- @valid_exit_codes = [0]
173
- @terminate_reason = nil
174
- @timeout = nil
175
-
176
- if command_args.last.is_a?(Hash)
177
- parse_options(command_args.pop)
178
- end
179
-
180
- @command = command_args.size == 1 ? command_args.first : command_args
181
- end
182
-
183
- # Returns the stream that both is being used by both live_stdout and live_stderr, or nil
184
- def live_stream
185
- live_stdout == live_stderr ? live_stdout : nil
186
- end
187
-
188
- # A shortcut for setting both live_stdout and live_stderr, so that both the
189
- # stdout and stderr from the subprocess will be copied to the same stream as
190
- # the subprocess is running.
191
- def live_stream=(stream)
192
- @live_stdout = @live_stderr = stream
193
- end
194
-
195
- # Set the umask that the subprocess will have. If given as a string, it
196
- # will be converted to an integer by String#oct.
197
- def umask=(new_umask)
198
- @umask = (new_umask.respond_to?(:oct) ? new_umask.oct : new_umask.to_i) & 007777
199
- end
200
-
201
- # The uid that the subprocess will switch to. If the user attribute was
202
- # given as a username, it is converted to a uid by Etc.getpwnam
203
- # TODO migrate to shellout/unix.rb
204
- def uid
205
- return nil unless user
206
- user.kind_of?(Integer) ? user : Etc.getpwnam(user.to_s).uid
207
- end
208
-
209
- # The gid that the subprocess will switch to. If the group attribute is
210
- # given as a group name, it is converted to a gid by Etc.getgrnam
211
- # TODO migrate to shellout/unix.rb
212
- def gid
213
- return group.kind_of?(Integer) ? group : Etc.getgrnam(group.to_s).gid if group
214
- return Etc.getpwuid(uid).gid if using_login?
215
- return nil
216
- end
217
-
218
- def timeout
219
- @timeout || DEFAULT_READ_TIMEOUT
220
- end
221
-
222
- # Creates a String showing the output of the command, including a banner
223
- # showing the exact command executed. Used by +invalid!+ to show command
224
- # results when the command exited with an unexpected status.
225
- def format_for_exception
226
- msg = ""
227
- msg << "#{@terminate_reason}\n" if @terminate_reason
228
- msg << "---- Begin output of #{command} ----\n"
229
- msg << "STDOUT: #{stdout.strip}\n"
230
- msg << "STDERR: #{stderr.strip}\n"
231
- msg << "---- End output of #{command} ----\n"
232
- msg << "Ran #{command} returned #{status.exitstatus}" if status
233
- msg
234
- end
235
-
236
- # The exit status of the subprocess. Will be nil if the command is still
237
- # running or died without setting an exit status (e.g., terminated by
238
- # `kill -9`).
239
- def exitstatus
240
- @status && @status.exitstatus
241
- end
242
-
243
- # Run the command, writing the command's standard out and standard error
244
- # to +stdout+ and +stderr+, and saving its exit status object to +status+
245
- # === Returns
246
- # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be
247
- # populated with results of the command
248
- # === Raises
249
- # * Errno::EACCES when you are not privileged to execute the command
250
- # * Errno::ENOENT when the command is not available on the system (or not
251
- # in the current $PATH)
252
- # * CommandTimeout when the command does not complete
253
- # within +timeout+ seconds (default: 600s)
254
- def run_command
255
- if logger
256
- log_message = (log_tag.nil? ? "" : "#@log_tag ") << "sh(#@command)"
257
- logger.send(log_level, log_message)
258
- end
259
- super
260
- end
261
-
262
- # Checks the +exitstatus+ against the set of +valid_exit_codes+.
263
- # === Returns
264
- # +true+ if +exitstatus+ is not in the list of +valid_exit_codes+, false
265
- # otherwise.
266
- def error?
267
- !Array(valid_exit_codes).include?(exitstatus)
268
- end
269
-
270
- # If #error? is true, calls +invalid!+, which raises an Exception.
271
- # === Returns
272
- # nil::: always returns nil when it does not raise
273
- # === Raises
274
- # ::ShellCommandFailed::: via +invalid!+
275
- def error!
276
- invalid!("Expected process to exit with #{valid_exit_codes.inspect}, but received '#{exitstatus}'") if error?
277
- end
278
-
279
- # Raises a ShellCommandFailed exception, appending the
280
- # command's stdout, stderr, and exitstatus to the exception message.
281
- # === Arguments
282
- # +msg+: A String to use as the basis of the exception message. The
283
- # default explanation is very generic, providing a more informative message
284
- # is highly encouraged.
285
- # === Raises
286
- # ShellCommandFailed always
287
- def invalid!(msg=nil)
288
- msg ||= "Command produced unexpected results"
289
- raise ShellCommandFailed, msg + "\n" + format_for_exception
290
- end
291
-
292
- def inspect
293
- "<#{self.class.name}##{object_id}: command: '#@command' process_status: #{@status.inspect} " +
294
- "stdout: '#{stdout.strip}' stderr: '#{stderr.strip}' child_pid: #{@child_pid.inspect} " +
295
- "environment: #{@environment.inspect} timeout: #{timeout} user: #@user group: #@group working_dir: #@cwd >"
296
- end
297
-
298
- private
299
-
300
- def parse_options(opts)
301
- opts.each do |option, setting|
302
- case option.to_s
303
- when 'cwd'
304
- self.cwd = setting
305
- when 'domain'
306
- self.domain = setting
307
- when 'password'
308
- self.password = setting
309
- when 'user'
310
- self.user = setting
311
- self.with_logon = setting
312
- when 'group'
313
- self.group = setting
314
- when 'umask'
315
- self.umask = setting
316
- when 'timeout'
317
- self.timeout = setting
318
- when 'returns'
319
- self.valid_exit_codes = Array(setting)
320
- when 'live_stream'
321
- self.live_stdout = self.live_stderr = setting
322
- when 'live_stdout'
323
- self.live_stdout = setting
324
- when 'live_stderr'
325
- self.live_stderr = setting
326
- when 'input'
327
- self.input = setting
328
- when 'logger'
329
- self.logger = setting
330
- when 'log_level'
331
- self.log_level = setting
332
- when 'log_tag'
333
- self.log_tag = setting
334
- when 'environment', 'env'
335
- if setting
336
- self.environment = Hash[setting.map{|(k,v)| [k.to_s,v]}]
337
- else
338
- self.environment = {}
339
- end
340
- when 'login'
341
- self.login = setting
342
- else
343
- raise InvalidCommandOption, "option '#{option.inspect}' is not a valid option for #{self.class.name}"
344
- end
345
- end
346
-
347
- validate_options(opts)
348
- end
349
-
350
- def validate_options(opts)
351
- if login && !user
352
- raise InvalidCommandOption, "cannot set login without specifying a user"
353
- end
354
- super
355
- end
356
- end
357
- end
1
+ #--
2
+ # Author:: Daniel DeLeo (<dan@chef.io>)
3
+ # Copyright:: Copyright (c) 2010-2016 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'etc'
20
+ require 'tmpdir'
21
+ require 'fcntl'
22
+ require 'mixlib/shellout/exceptions'
23
+
24
+ module Mixlib
25
+
26
+ class ShellOut
27
+ READ_WAIT_TIME = 0.01
28
+ READ_SIZE = 4096
29
+ DEFAULT_READ_TIMEOUT = 600
30
+
31
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
32
+ require 'mixlib/shellout/windows'
33
+ include ShellOut::Windows
34
+ else
35
+ require 'mixlib/shellout/unix'
36
+ include ShellOut::Unix
37
+ end
38
+
39
+ # User the command will run as. Normally set via options passed to new
40
+ attr_accessor :user
41
+ attr_accessor :domain
42
+ attr_accessor :password
43
+ # TODO remove
44
+ attr_accessor :with_logon
45
+
46
+ # Whether to simulate logon as the user. Normally set via options passed to new
47
+ # Always enabled on windows
48
+ attr_accessor :login
49
+
50
+ # Group the command will run as. Normally set via options passed to new
51
+ attr_accessor :group
52
+
53
+ # Working directory for the subprocess. Normally set via options to new
54
+ attr_accessor :cwd
55
+
56
+ # An Array of acceptable exit codes. #error? (and #error!) use this list
57
+ # to determine if the command was successful. Normally set via options to new
58
+ attr_accessor :valid_exit_codes
59
+
60
+ # When live_stdout is set, the stdout of the subprocess will be copied to it
61
+ # as the subprocess is running.
62
+ attr_accessor :live_stdout
63
+
64
+ # When live_stderr is set, the stderr of the subprocess will be copied to it
65
+ # as the subprocess is running.
66
+ attr_accessor :live_stderr
67
+
68
+ # ShellOut will push data from :input down the stdin of the subprocss.
69
+ # Normally set via options passed to new.
70
+ # Default: nil
71
+ attr_accessor :input
72
+
73
+ # If a logger is set, ShellOut will log a message before it executes the
74
+ # command.
75
+ attr_accessor :logger
76
+
77
+ # The log level at which ShellOut should log.
78
+ attr_accessor :log_level
79
+
80
+ # A string which will be prepended to the log message.
81
+ attr_accessor :log_tag
82
+
83
+ # The command to be executed.
84
+ attr_reader :command
85
+
86
+ # The umask that will be set for the subcommand.
87
+ attr_reader :umask
88
+
89
+ # Environment variables that will be set for the subcommand. Refer to the
90
+ # documentation of new to understand how ShellOut interprets this.
91
+ attr_accessor :environment
92
+
93
+ # The maximum time this command is allowed to run. Usually set via options
94
+ # to new
95
+ attr_writer :timeout
96
+
97
+ # The amount of time the subcommand took to execute
98
+ attr_reader :execution_time
99
+
100
+ # Data written to stdout by the subprocess
101
+ attr_reader :stdout
102
+
103
+ # Data written to stderr by the subprocess
104
+ attr_reader :stderr
105
+
106
+ # A Process::Status (or ducktype) object collected when the subprocess is
107
+ # reaped.
108
+ attr_reader :status
109
+
110
+ attr_reader :stdin_pipe, :stdout_pipe, :stderr_pipe, :process_status_pipe
111
+
112
+ # === Arguments:
113
+ # Takes a single command, or a list of command fragments. These are used
114
+ # as arguments to Kernel.exec. See the Kernel.exec documentation for more
115
+ # explanation of how arguments are evaluated. The last argument can be an
116
+ # options Hash.
117
+ # === Options:
118
+ # If the last argument is a Hash, it is removed from the list of args passed
119
+ # to exec and used as an options hash. The following options are available:
120
+ # * +user+: the user the commmand should run as. if an integer is given, it is
121
+ # used as a uid. A string is treated as a username and resolved to a uid
122
+ # with Etc.getpwnam
123
+ # * +group+: the group the command should run as. works similarly to +user+
124
+ # * +cwd+: the directory to chdir to before running the command
125
+ # * +umask+: a umask to set before running the command. If given as an Integer,
126
+ # be sure to use two leading zeros so it's parsed as Octal. A string will
127
+ # be treated as an octal integer
128
+ # * +returns+: one or more Integer values to use as valid exit codes for the
129
+ # subprocess. This only has an effect if you call +error!+ after
130
+ # +run_command+.
131
+ # * +environment+: a Hash of environment variables to set before the command
132
+ # is run.
133
+ # * +timeout+: a Numeric value for the number of seconds to wait on the
134
+ # child process before raising an Exception. This is calculated as the
135
+ # total amount of time that ShellOut waited on the child process without
136
+ # receiving any output (i.e., IO.select returned nil). Default is 60
137
+ # seconds. Note: the stdlib Timeout library is not used.
138
+ # * +input+: A String of data to be passed to the subcommand. This is
139
+ # written to the child process' stdin stream before the process is
140
+ # launched. The child's stdin stream will be a pipe, so the size of input
141
+ # data should not exceed the system's default pipe capacity (4096 bytes
142
+ # is a safe value, though on newer Linux systems the capacity is 64k by
143
+ # default).
144
+ # * +live_stream+: An IO or Logger-like object (must respond to the append
145
+ # operator +<<+) that will receive data as ShellOut reads it from the
146
+ # child process. Generally this is used to copy data from the child to
147
+ # the parent's stdout so that users may observe the progress of
148
+ # long-running commands.
149
+ # * +login+: Whether to simulate a login (set secondary groups, primary group, environment
150
+ # variables etc) as done by the OS in an actual login
151
+ # === Examples:
152
+ # Invoke find(1) to search for .rb files:
153
+ # find = Mixlib::ShellOut.new("find . -name '*.rb'")
154
+ # find.run_command
155
+ # # If all went well, the results are on +stdout+
156
+ # puts find.stdout
157
+ # # find(1) prints diagnostic info to STDERR:
158
+ # puts "error messages" + find.stderr
159
+ # # Raise an exception if it didn't exit with 0
160
+ # find.error!
161
+ # Run a command as the +www+ user with no extra ENV settings from +/tmp+
162
+ # cmd = Mixlib::ShellOut.new("apachectl", "start", :user => 'www', :env => nil, :cwd => '/tmp')
163
+ # cmd.run_command # etc.
164
+ def initialize(*command_args)
165
+ @stdout, @stderr, @process_status = '', '', ''
166
+ @live_stdout = @live_stderr = nil
167
+ @input = nil
168
+ @log_level = :debug
169
+ @log_tag = nil
170
+ @environment = {}
171
+ @cwd = nil
172
+ @valid_exit_codes = [0]
173
+ @terminate_reason = nil
174
+ @timeout = nil
175
+
176
+ if command_args.last.is_a?(Hash)
177
+ parse_options(command_args.pop)
178
+ end
179
+
180
+ @command = command_args.size == 1 ? command_args.first : command_args
181
+ end
182
+
183
+ # Returns the stream that both is being used by both live_stdout and live_stderr, or nil
184
+ def live_stream
185
+ live_stdout == live_stderr ? live_stdout : nil
186
+ end
187
+
188
+ # A shortcut for setting both live_stdout and live_stderr, so that both the
189
+ # stdout and stderr from the subprocess will be copied to the same stream as
190
+ # the subprocess is running.
191
+ def live_stream=(stream)
192
+ @live_stdout = @live_stderr = stream
193
+ end
194
+
195
+ # Set the umask that the subprocess will have. If given as a string, it
196
+ # will be converted to an integer by String#oct.
197
+ def umask=(new_umask)
198
+ @umask = (new_umask.respond_to?(:oct) ? new_umask.oct : new_umask.to_i) & 007777
199
+ end
200
+
201
+ # The uid that the subprocess will switch to. If the user attribute was
202
+ # given as a username, it is converted to a uid by Etc.getpwnam
203
+ # TODO migrate to shellout/unix.rb
204
+ def uid
205
+ return nil unless user
206
+ user.kind_of?(Integer) ? user : Etc.getpwnam(user.to_s).uid
207
+ end
208
+
209
+ # The gid that the subprocess will switch to. If the group attribute is
210
+ # given as a group name, it is converted to a gid by Etc.getgrnam
211
+ # TODO migrate to shellout/unix.rb
212
+ def gid
213
+ return group.kind_of?(Integer) ? group : Etc.getgrnam(group.to_s).gid if group
214
+ return Etc.getpwuid(uid).gid if using_login?
215
+ return nil
216
+ end
217
+
218
+ def timeout
219
+ @timeout || DEFAULT_READ_TIMEOUT
220
+ end
221
+
222
+ # Creates a String showing the output of the command, including a banner
223
+ # showing the exact command executed. Used by +invalid!+ to show command
224
+ # results when the command exited with an unexpected status.
225
+ def format_for_exception
226
+ msg = ""
227
+ msg << "#{@terminate_reason}\n" if @terminate_reason
228
+ msg << "---- Begin output of #{command} ----\n"
229
+ msg << "STDOUT: #{stdout.strip}\n"
230
+ msg << "STDERR: #{stderr.strip}\n"
231
+ msg << "---- End output of #{command} ----\n"
232
+ msg << "Ran #{command} returned #{status.exitstatus}" if status
233
+ msg
234
+ end
235
+
236
+ # The exit status of the subprocess. Will be nil if the command is still
237
+ # running or died without setting an exit status (e.g., terminated by
238
+ # `kill -9`).
239
+ def exitstatus
240
+ @status && @status.exitstatus
241
+ end
242
+
243
+ # Run the command, writing the command's standard out and standard error
244
+ # to +stdout+ and +stderr+, and saving its exit status object to +status+
245
+ # === Returns
246
+ # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be
247
+ # populated with results of the command
248
+ # === Raises
249
+ # * Errno::EACCES when you are not privileged to execute the command
250
+ # * Errno::ENOENT when the command is not available on the system (or not
251
+ # in the current $PATH)
252
+ # * CommandTimeout when the command does not complete
253
+ # within +timeout+ seconds (default: 600s)
254
+ def run_command
255
+ if logger
256
+ log_message = (log_tag.nil? ? "" : "#@log_tag ") << "sh(#@command)"
257
+ logger.send(log_level, log_message)
258
+ end
259
+ super
260
+ end
261
+
262
+ # Checks the +exitstatus+ against the set of +valid_exit_codes+.
263
+ # === Returns
264
+ # +true+ if +exitstatus+ is not in the list of +valid_exit_codes+, false
265
+ # otherwise.
266
+ def error?
267
+ !Array(valid_exit_codes).include?(exitstatus)
268
+ end
269
+
270
+ # If #error? is true, calls +invalid!+, which raises an Exception.
271
+ # === Returns
272
+ # nil::: always returns nil when it does not raise
273
+ # === Raises
274
+ # ::ShellCommandFailed::: via +invalid!+
275
+ def error!
276
+ invalid!("Expected process to exit with #{valid_exit_codes.inspect}, but received '#{exitstatus}'") if error?
277
+ end
278
+
279
+ # Raises a ShellCommandFailed exception, appending the
280
+ # command's stdout, stderr, and exitstatus to the exception message.
281
+ # === Arguments
282
+ # +msg+: A String to use as the basis of the exception message. The
283
+ # default explanation is very generic, providing a more informative message
284
+ # is highly encouraged.
285
+ # === Raises
286
+ # ShellCommandFailed always
287
+ def invalid!(msg=nil)
288
+ msg ||= "Command produced unexpected results"
289
+ raise ShellCommandFailed, msg + "\n" + format_for_exception
290
+ end
291
+
292
+ def inspect
293
+ "<#{self.class.name}##{object_id}: command: '#@command' process_status: #{@status.inspect} " +
294
+ "stdout: '#{stdout.strip}' stderr: '#{stderr.strip}' child_pid: #{@child_pid.inspect} " +
295
+ "environment: #{@environment.inspect} timeout: #{timeout} user: #@user group: #@group working_dir: #@cwd >"
296
+ end
297
+
298
+ private
299
+
300
+ def parse_options(opts)
301
+ opts.each do |option, setting|
302
+ case option.to_s
303
+ when 'cwd'
304
+ self.cwd = setting
305
+ when 'domain'
306
+ self.domain = setting
307
+ when 'password'
308
+ self.password = setting
309
+ when 'user'
310
+ self.user = setting
311
+ self.with_logon = setting
312
+ when 'group'
313
+ self.group = setting
314
+ when 'umask'
315
+ self.umask = setting
316
+ when 'timeout'
317
+ self.timeout = setting
318
+ when 'returns'
319
+ self.valid_exit_codes = Array(setting)
320
+ when 'live_stream'
321
+ self.live_stdout = self.live_stderr = setting
322
+ when 'live_stdout'
323
+ self.live_stdout = setting
324
+ when 'live_stderr'
325
+ self.live_stderr = setting
326
+ when 'input'
327
+ self.input = setting
328
+ when 'logger'
329
+ self.logger = setting
330
+ when 'log_level'
331
+ self.log_level = setting
332
+ when 'log_tag'
333
+ self.log_tag = setting
334
+ when 'environment', 'env'
335
+ if setting
336
+ self.environment = Hash[setting.map{|(k,v)| [k.to_s,v]}]
337
+ else
338
+ self.environment = {}
339
+ end
340
+ when 'login'
341
+ self.login = setting
342
+ else
343
+ raise InvalidCommandOption, "option '#{option.inspect}' is not a valid option for #{self.class.name}"
344
+ end
345
+ end
346
+
347
+ validate_options(opts)
348
+ end
349
+
350
+ def validate_options(opts)
351
+ if login && !user
352
+ raise InvalidCommandOption, "cannot set login without specifying a user"
353
+ end
354
+ super
355
+ end
356
+ end
357
+ end