Class: AngryShell::Shell
- Inherits:
-
Object
- Object
- AngryShell::Shell
- Defined in:
- lib/angry_shell.rb
Defined Under Namespace
Classes: IPCState, ShellResult
Instance Attribute Summary collapse
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Instance Method Summary collapse
- #debug(*msg) ⇒ Object
- #execute ⇒ Object
-
#initialize(*args, &block) ⇒ Shell
constructor
A new instance of Shell.
- #massaged_args(args) ⇒ Object
-
#ok? ⇒ Boolean
runs the command, returning true if it returns success.
-
#popen4(args = {}, &blk) ⇒ Object
This is taken from Chef and rewritten.
- #popen4_normalise_args(args) ⇒ Object
-
#popen4_parent_exhaust_io(cid, args, ipc, &blk) ⇒ Object
Use select to read the entire contents of the pipes into StringIOs.
- #popen4_proceed_as_child(args, ipc) ⇒ Object
- #popen4_proceed_as_parent(cid, args, ipc, &blk) ⇒ Object
-
#run ⇒ Object
runs the command, raising if it doesn’t return success.
-
#to_s ⇒ Object
runs the command, returning its ‘stdout`.
Constructor Details
#initialize(*args, &block) ⇒ Shell
Returns a new instance of Shell.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/angry_shell.rb', line 39 def initialize(*args,&block) @block = block @options = if Hash === args.last then args.pop else {} end case args.size when 0 # no op when 1 @options[:cmd] = args.first else @options[:cmd] = args end @options[:stream] = false unless @options.key?(:stream) end |
Instance Attribute Details
#options ⇒ Object (readonly)
Returns the value of attribute options.
37 38 39 |
# File 'lib/angry_shell.rb', line 37 def @options end |
Instance Method Details
#debug(*msg) ⇒ Object
33 34 35 |
# File 'lib/angry_shell.rb', line 33 def debug(*msg) puts "sh: #{msg * ' '}" end |
#execute ⇒ Object
55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/angry_shell.rb', line 55 def execute error,out = nil,nil rv = popen4() {|pid,ipc| out = ipc.stdout.read error = ipc.stderr.read } rv.stderr = error rv.stdout = out rv end |
#massaged_args(args) ⇒ Object
394 395 396 397 398 399 400 401 402 403 404 |
# File 'lib/angry_shell.rb', line 394 def massaged_args args args.dup.tap do |args_to_print| args_to_print[:environment] = e = args[:environment].dup %w{LC_ALL GEM_HOME GEM_PATH RUBYOPT BUNDLE_GEMFILE}.each {|env| e.delete(env)} args_to_print['cwd'] = args_to_print['cwd'].to_s if args_to_print['cwd'] args_to_print.delete_if{|k,v| v.blank?} end end |
#ok? ⇒ Boolean
runs the command, returning true if it returns success.
75 76 77 |
# File 'lib/angry_shell.rb', line 75 def ok? execute.ok? end |
#popen4(args = {}, &blk) ⇒ Object
This is taken from Chef and rewritten.
Chef’s preamble: This is taken directly from Ara T Howard’s Open4 library, and then modified to suit the needs of Chef. Any bugs here are most likely my own, and not Ara’s.
The original appears in external/open4.rb in its unmodified form.
Thanks Ara!
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/angry_shell.rb', line 164 def popen4(args={}, &blk) popen4_normalise_args(args) # We pass and manipulate all IPC pipes around inside this object. ipc = IPCState.new verbose = $VERBOSE cid = begin $VERBOSE = nil ipc.before_fork! fork { popen4_proceed_as_child(args,ipc) } ensure $VERBOSE = verbose end popen4_proceed_as_parent(cid,args,ipc,&blk) end |
#popen4_normalise_args(args) ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 |
# File 'lib/angry_shell.rb', line 351 def popen4_normalise_args(args) # Do we wait for the child process to die before we yield # to the block, or after? # # By default, we are waiting before we yield the block. args[:stream] ||= false args[:user] ||= nil unless args[:user].kind_of?(Integer) args[:user] = Etc.getpwnam(args[:user]).uid if args[:user] end args[:group] ||= nil unless args[:group].kind_of?(Integer) args[:group] = Etc.getgrnam(args[:group]).gid if args[:group] end args[:environment] ||= {} # Default on C locale so parsing commands output can be done # independently of the node's default locale. # "LC_ALL" could be set to nil, in which case we also must ignore it. unless args[:environment].has_key?("LC_ALL") args[:environment]["LC_ALL"] = "C" end unless TrueClass === args[:without_cleaning_bundler] args[:environment].update('RUBYOPT' => nil, 'BUNDLE_GEMFILE' => nil, 'GEM_HOME' => nil, 'GEM_PATH' => nil) end # `:as` - run the command as another user, via sudo, if user = args[:as] if (evars = args[:environment].reject {|k,v| v.nil?}.map {|k,v| "#{k}=#{v}"}) && !evars.empty? env = "env #{evars.join(' ')}" else env = '' end args[:cmd] = "sudo -H -u #{user} #{env} #{args[:cmd]}" end end |
#popen4_parent_exhaust_io(cid, args, ipc, &blk) ⇒ Object
Use select to read the entire contents of the pipes into StringIOs. This is the main change to come from Chef vs the original open4.
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/angry_shell.rb', line 222 def popen4_parent_exhaust_io(cid,args,ipc,&blk) output = StringIO.new error = StringIO.new if args[:input] ipc.write.puts args[:input] end ipc.write.close stdout = ipc.read stderr = ipc.error stdout.sync = true stderr.sync = true stdout.fcntl(Fcntl::F_SETFL, stdout.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) stderr.fcntl(Fcntl::F_SETFL, stderr.fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK) stdout_finished = false stderr_finished = false results = nil while !stdout_finished && !stderr_finished begin channels_to_watch = [] channels_to_watch << stdout if !stdout_finished channels_to_watch << stderr if !stderr_finished ready = IO.select(channels_to_watch, nil, nil, 1.0) rescue Errno::EAGAIN results = Process.waitpid2(cid, Process::WNOHANG) if results stdout_finished = true stderr_finished = true end end if ready && ready.first.include?(stdout) line = results ? stdout.gets(nil) : stdout.gets if line output.write(line) else stdout_finished = true end end if ready && ready.first.include?(stderr) line = results ? stderr.gets(nil) : stderr.gets if line error.write(line) else stderr_finished = true end end end results = Process.waitpid2(cid) unless results output.rewind error.rewind ipc.read = output ipc.error = error blk[cid, ipc] ShellResult.new(results.last, args) end |
#popen4_proceed_as_child(args, ipc) ⇒ Object
295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 |
# File 'lib/angry_shell.rb', line 295 def popen4_proceed_as_child(args,ipc) ipc.child_after_fork! if args[:group] Process.egid = args[:group] Process.gid = args[:group] end if args[:user] Process.euid = args[:user] Process.uid = args[:user] end # Copy the specified environment across to the child's environment. # Keys with `nil` values are deleted from the environment. args[:environment].each do |key,value| if value.nil? ENV.delete(key.to_s) else ENV[key.to_s] = value end end if args[:umask] umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777) File.umask(umask) end if args[:cwd] Dir.chdir args[:cwd] end begin cmd = args[:cmd] case cmd when Proc exit cmd.call.to_i when Array exec(*cmd) else exec(cmd) end raise 'forty-two' rescue SystemExit exit $!.status rescue Object => e Marshal.dump(e, ipc.exception) ipc.exception.flush end ipc.exception.close unless (ipc.exception.closed?) exit! end |
#popen4_proceed_as_parent(cid, args, ipc, &blk) ⇒ Object
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/angry_shell.rb', line 187 def popen4_proceed_as_parent(cid,args,ipc,&blk) ipc.parent_after_fork! # The first thing a parent does after forking is look for an Marshalled exception on the exception pipe. begin e = Marshal.load ipc.exception raise(Exception === e ? e : "unknown failure!") rescue EOFError # If we get an EOF error, then the exec was successful 42 ensure ipc.exception.close end ipc.write.sync = true if block_given? begin if args[:stream] # hand the block the pipes inside ipc to manipulate manually yield(cid, ipc) ShellResult.new(Process.waitpid2(cid).last, args) else popen4_parent_exhaust_io(cid,args,ipc,&blk) end ensure ipc.close_all end else # Return the pipes. The User needs to clean up after themselves. [cid, ipc] end end |
#run ⇒ Object
runs the command, raising if it doesn’t return success.
70 71 72 |
# File 'lib/angry_shell.rb', line 70 def run execute.ensure_ok! end |
#to_s ⇒ Object
runs the command, returning its ‘stdout`. If the command doesn’t return success, return a blank string.
80 81 82 83 84 85 86 87 |
# File 'lib/angry_shell.rb', line 80 def to_s result = execute if result.ok? result.stdout.chomp else '' end end |