right_popen 1.0.21 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +10 -8
- data/lib/right_popen.rb +125 -30
- data/lib/right_popen/linux/accumulator.rb +14 -4
- data/lib/right_popen/linux/{right_popen.rb → popen3_async.rb} +73 -57
- data/lib/right_popen/linux/popen3_sync.rb +35 -0
- data/lib/right_popen/linux/process.rb +136 -44
- data/lib/right_popen/linux/utilities.rb +5 -2
- data/lib/right_popen/process_base.rb +339 -0
- data/lib/right_popen/process_status.rb +64 -0
- data/lib/right_popen/safe_output_buffer.rb +79 -0
- data/lib/right_popen/target_proxy.rb +67 -0
- data/lib/right_popen/version.rb +2 -2
- data/right_popen.gemspec +2 -2
- data/spec/produce_mixed_output.rb +3 -0
- data/spec/right_popen/linux/accumulator_spec.rb +5 -13
- data/spec/right_popen/safe_output_buffer_spec.rb +26 -0
- data/spec/right_popen_spec.rb +272 -227
- data/spec/runner.rb +171 -79
- data/spec/sleeper.rb +35 -0
- data/spec/stdout.rb +1 -1
- data/spec/writer.rb +34 -0
- metadata +32 -26
data/README.rdoc
CHANGED
@@ -15,6 +15,7 @@ documentation.
|
|
15
15
|
Also use the built-in issues tracker (https://github.com/rightscale/right_popen/issues)
|
16
16
|
to report issues.
|
17
17
|
|
18
|
+
Maintained by the RightScale Teal Team
|
18
19
|
|
19
20
|
== USAGE
|
20
21
|
|
@@ -47,13 +48,14 @@ to report issues.
|
|
47
48
|
EM.run do
|
48
49
|
EM.next_tick do
|
49
50
|
command = "ruby -e \"puts 'some stdout text'; $stderr.puts 'some stderr text'\; exit 99\""
|
50
|
-
RightScale.
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
51
|
+
RightScale::RightPopen.popen3_async(
|
52
|
+
command,
|
53
|
+
:target => self,
|
54
|
+
:environment => nil,
|
55
|
+
:pid_handler => :on_pid,
|
56
|
+
:stdout_handler => :on_read_stdout,
|
57
|
+
:stderr_handler => :on_read_stderr,
|
58
|
+
:exit_handler => :on_exit)
|
57
59
|
end
|
58
60
|
timer = EM::PeriodicTimer.new(0.1) do
|
59
61
|
if @exit_status
|
@@ -109,7 +111,7 @@ the RightPopen tests:
|
|
109
111
|
|
110
112
|
<b>RightPopen</b>
|
111
113
|
|
112
|
-
Copyright:: Copyright (c) 2010 RightScale, Inc.
|
114
|
+
Copyright:: Copyright (c) 2010-2013 RightScale, Inc.
|
113
115
|
|
114
116
|
Permission is hereby granted, free of charge, to any person obtaining
|
115
117
|
a copy of this software and associated documentation files (the
|
data/lib/right_popen.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2009 RightScale Inc
|
2
|
+
# Copyright (c) 2009-2013 RightScale Inc
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -21,46 +21,141 @@
|
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
24
|
-
|
25
|
-
# while still capturing their standard and error outputs.
|
26
|
-
# It relies on EventMachine for most of its internal mechanisms.
|
24
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'right_popen', 'target_proxy'))
|
27
25
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
# TEAL FIX: this seems like test harness code smell, not production code. it
|
27
|
+
# should be removed in next major revision. unfortunately we cannot remove these
|
28
|
+
# require statements without breaking any code that depends on them.
|
29
|
+
unless RUBY_PLATFORM =~ /mswin|mingw/
|
30
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'right_popen', 'linux', 'accumulator'))
|
31
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'right_popen', 'linux', 'utilities'))
|
32
32
|
end
|
33
33
|
|
34
34
|
module RightScale
|
35
35
|
|
36
|
-
#
|
37
|
-
# standard streams of the child process.
|
36
|
+
# see popen3_async for details.
|
38
37
|
#
|
39
|
-
#
|
40
|
-
# ordering of bytes sent to stdout and stderr is not preserved.
|
41
|
-
#
|
42
|
-
# Calls given exit handler upon command process termination, passing in the
|
43
|
-
# resulting Process::Status.
|
44
|
-
#
|
45
|
-
# All handlers must be methods exposed by the given target.
|
38
|
+
# @deprecated in favor of sync vs. async methods in RightPopen namespace.
|
46
39
|
#
|
47
40
|
# === Parameters
|
48
|
-
#
|
49
|
-
# options[:environment](Hash):: Hash of environment variables values keyed by name
|
50
|
-
# options[:input](String):: Input string that will get streamed into child's process stdin
|
51
|
-
# options[:target](Object):: object defining handler methods to be called, optional (no handlers can be defined if not specified)
|
52
|
-
# options[:pid_handler](String):: PID notification handler method name, optional
|
53
|
-
# options[:stdout_handler](String):: Stdout handler method name, optional
|
54
|
-
# options[:stderr_handler](String):: Stderr handler method name, optional
|
55
|
-
# options[:exit_handler](String):: Exit handler method name, optional
|
41
|
+
# @param [Hash] options for execution
|
56
42
|
#
|
57
43
|
# === Returns
|
58
|
-
#
|
44
|
+
# @return [TrueClass] always true
|
59
45
|
def self.popen3(options)
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
46
|
+
warn 'WARNING: RightScale.popen3 is deprecated in favor of RightScale::RightPopen.popen3_async'
|
47
|
+
options = options.dup
|
48
|
+
cmd = options.dup.delete(:command)
|
49
|
+
raise ::ArgumentError.new("Missing command") unless cmd
|
50
|
+
::RightScale::RightPopen.popen3_async(options[:command], options)
|
64
51
|
end
|
65
52
|
|
53
|
+
module RightPopen
|
54
|
+
|
55
|
+
# see popen3_async for details.
|
56
|
+
DEFAULT_POPEN3_OPTIONS = {
|
57
|
+
:environment => nil,
|
58
|
+
:exit_handler => nil,
|
59
|
+
:group => nil,
|
60
|
+
:inherit_io => false,
|
61
|
+
:input => nil,
|
62
|
+
:locale => true,
|
63
|
+
:pid_handler => nil,
|
64
|
+
:size_limit_bytes => nil,
|
65
|
+
:stderr_handler => nil,
|
66
|
+
:stdout_handler => nil,
|
67
|
+
:target => nil,
|
68
|
+
:timeout_seconds => nil,
|
69
|
+
:umask => nil,
|
70
|
+
:user => nil,
|
71
|
+
:watch_handler => nil,
|
72
|
+
:watch_directory => nil,
|
73
|
+
}
|
74
|
+
|
75
|
+
# Loads the specified implementation.
|
76
|
+
#
|
77
|
+
# === Parameters
|
78
|
+
# @param [Symbol|String] synchronicity to load
|
79
|
+
#
|
80
|
+
# === Return
|
81
|
+
# @return [TrueClass] always true
|
82
|
+
def self.require_popen3_impl(synchronicity)
|
83
|
+
case RUBY_PLATFORM
|
84
|
+
when /mswin/
|
85
|
+
platform = 'win32'
|
86
|
+
when /mingw/
|
87
|
+
platform = 'mingw'
|
88
|
+
else
|
89
|
+
platform = 'linux'
|
90
|
+
end
|
91
|
+
require ::File.expand_path(::File.join(::File.dirname(__FILE__), 'right_popen', platform, synchronicity.to_s))
|
92
|
+
end
|
93
|
+
|
94
|
+
# Spawns a process to run given command synchronously. This is similar to
|
95
|
+
# the Ruby backtick but also supports streaming I/O, process watching, etc.
|
96
|
+
# Does not require any evented library to use.
|
97
|
+
#
|
98
|
+
# Streams the command's stdout and stderr to the given handlers. Time-
|
99
|
+
# ordering of bytes sent to stdout and stderr is not preserved.
|
100
|
+
#
|
101
|
+
# Calls given exit handler upon command process termination, passing in the
|
102
|
+
# resulting Process::Status.
|
103
|
+
#
|
104
|
+
# All handlers must be methods exposed by the given target.
|
105
|
+
#
|
106
|
+
# === Parameters
|
107
|
+
# @param [Hash] options see popen3_async for details
|
108
|
+
#
|
109
|
+
# === Returns
|
110
|
+
# @return [TrueClass] always true
|
111
|
+
def self.popen3_sync(cmd, options)
|
112
|
+
options = DEFAULT_POPEN3_OPTIONS.dup.merge(options)
|
113
|
+
require_popen3_impl(:popen3_sync)
|
114
|
+
::RightScale::RightPopen.popen3_sync_impl(
|
115
|
+
cmd, ::RightScale::RightPopen::TargetProxy.new(options), options)
|
116
|
+
end
|
117
|
+
|
118
|
+
# Spawns a process to run given command asynchronously, hooking all three
|
119
|
+
# standard streams of the child process. Implementation requires the
|
120
|
+
# eventmachine gem.
|
121
|
+
#
|
122
|
+
# Streams the command's stdout and stderr to the given handlers. Time-
|
123
|
+
# ordering of bytes sent to stdout and stderr is not preserved.
|
124
|
+
#
|
125
|
+
# Calls given exit handler upon command process termination, passing in the
|
126
|
+
# resulting Process::Status.
|
127
|
+
#
|
128
|
+
# All handlers must be methods exposed by the given target.
|
129
|
+
#
|
130
|
+
# === Parameters
|
131
|
+
# @param [Hash] options for execution
|
132
|
+
# @option options [Hash] :environment variables values keyed by name
|
133
|
+
# @option options [Symbol] :exit_handler target method called on exit
|
134
|
+
# @option options [Integer|String] :group or gid for forked process (linux only)
|
135
|
+
# @option options [TrueClass|FalseClass] :inherit_io set to true to share all IO objects with forked process or false to close shared IO objects (default) (linux only)
|
136
|
+
# @option options [String] :input string that will get streamed into child's process stdin
|
137
|
+
# @option options [TrueClass|FalseClass] :locale set to true to export LC_ALL=C in the forked environment (default) or false to use default locale (linux only)
|
138
|
+
# @option options [Symbol] :pid_handler target method called with process ID (PID)
|
139
|
+
# @option options [Integer] :size_limit_bytes for total size of watched directory after which child process will be interrupted
|
140
|
+
# @option options [Symbol] :stderr_handler target method called as error text is received
|
141
|
+
# @option options [Symbol] :stdout_handler target method called as output text is received
|
142
|
+
# @option options [Object] :target object defining handler methods to be called (no handlers can be defined if not specified)
|
143
|
+
# @option options [Numeric] :timeout_seconds after which child process will be interrupted
|
144
|
+
# @option options [Integer|String] :umask for files created by process (linux only)
|
145
|
+
# @option options [Integer|String] :user or uid for forked process (linux only)
|
146
|
+
# @option options [Symbol] :watch_handler called periodically with process during watch; return true to continue, false to abandon (sync only)
|
147
|
+
# @option options [String] :watch_directory to monitor for child process writing files
|
148
|
+
#
|
149
|
+
# === Returns
|
150
|
+
# @return [TrueClass] always true
|
151
|
+
def self.popen3_async(cmd, options)
|
152
|
+
options = DEFAULT_POPEN3_OPTIONS.dup.merge(options)
|
153
|
+
require_popen3_impl(:popen3_async)
|
154
|
+
unless ::EM.reactor_running?
|
155
|
+
raise ::ArgumentError, "EventMachine reactor must be running."
|
156
|
+
end
|
157
|
+
::RightScale::RightPopen.popen3_async_impl(
|
158
|
+
cmd, ::RightScale::RightPopen::TargetProxy.new(options), options)
|
159
|
+
end
|
160
|
+
end
|
66
161
|
end
|
@@ -23,10 +23,13 @@
|
|
23
23
|
|
24
24
|
module RightScale
|
25
25
|
module RightPopen
|
26
|
+
|
27
|
+
# @deprecated this seems like test harness code smell, not production code
|
26
28
|
class Accumulator
|
27
29
|
READ_CHUNK_SIZE = 4096
|
28
30
|
|
29
31
|
def initialize(process, inputs, read_callbacks, outputs, write_callbacks)
|
32
|
+
warn 'WARNING: RightScale::RightPopen::Accumulator is deprecated and will be removed.'
|
30
33
|
@process = process
|
31
34
|
@inputs = inputs
|
32
35
|
@outputs = outputs
|
@@ -45,10 +48,17 @@ module RightScale
|
|
45
48
|
@status = nil
|
46
49
|
end
|
47
50
|
|
51
|
+
def status
|
52
|
+
unless @status
|
53
|
+
@status = ::Process.waitpid2(@process.pid, ::Process::WNOHANG)
|
54
|
+
end
|
55
|
+
@status
|
56
|
+
end
|
57
|
+
|
48
58
|
def tick(sleep_time = 0.1)
|
49
|
-
return true unless @
|
59
|
+
return true unless @status.nil?
|
50
60
|
|
51
|
-
|
61
|
+
status
|
52
62
|
|
53
63
|
inputs = @inputs.dup
|
54
64
|
outputs = @outputs.dup
|
@@ -93,7 +103,7 @@ module RightScale
|
|
93
103
|
end
|
94
104
|
end unless ready.nil? || ready[1].nil?
|
95
105
|
|
96
|
-
return !@
|
106
|
+
return !@status.nil?
|
97
107
|
end
|
98
108
|
|
99
109
|
def number_waiting_on
|
@@ -103,7 +113,7 @@ module RightScale
|
|
103
113
|
def cleanup
|
104
114
|
@inputs.each {|p| p.close unless p.closed? }
|
105
115
|
@outputs.each {|p| p.close unless p.closed? }
|
106
|
-
@
|
116
|
+
@status = ::Process.waitpid2(@process.pid) if @status.nil?
|
107
117
|
end
|
108
118
|
|
109
119
|
def run_to_completion(sleep_time=0.1)
|
@@ -1,5 +1,5 @@
|
|
1
1
|
#--
|
2
|
-
# Copyright (c) 2009 RightScale Inc
|
2
|
+
# Copyright (c) 2009-2013 RightScale Inc
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -21,17 +21,13 @@
|
|
21
21
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
22
|
#++
|
23
23
|
|
24
|
-
# RightScale.popen3 allows running external processes aynchronously
|
25
|
-
# while still capturing their standard and error outputs.
|
26
|
-
# It relies on EventMachine for most of its internal mechanisms.
|
27
|
-
|
28
24
|
require 'rubygems'
|
29
25
|
require 'eventmachine'
|
26
|
+
|
30
27
|
require File.expand_path(File.join(File.dirname(__FILE__), "process"))
|
31
|
-
require File.expand_path(File.join(File.dirname(__FILE__), "accumulator"))
|
32
|
-
require File.expand_path(File.join(File.dirname(__FILE__), "utilities"))
|
33
28
|
|
34
|
-
module RightScale
|
29
|
+
module RightScale::RightPopen
|
30
|
+
|
35
31
|
# ensure uniqueness of handler to avoid confusion.
|
36
32
|
raise "#{StatusHandler.name} is already defined" if defined?(StatusHandler)
|
37
33
|
|
@@ -63,8 +59,8 @@ module RightScale
|
|
63
59
|
|
64
60
|
def unbind
|
65
61
|
if @data.size > 0
|
66
|
-
e = Marshal.load
|
67
|
-
raise (Exception === e ? e : "unknown failure: saw #{e} on status channel")
|
62
|
+
e = ::Marshal.load(@data)
|
63
|
+
raise (::Exception === e ? e : "unknown failure: saw #{e} on status channel")
|
68
64
|
end
|
69
65
|
end
|
70
66
|
end
|
@@ -80,11 +76,11 @@ module RightScale
|
|
80
76
|
# itself.
|
81
77
|
@handle = file_handle
|
82
78
|
@target = target
|
83
|
-
@
|
79
|
+
@data_handler = @target.method(handler)
|
84
80
|
end
|
85
81
|
|
86
82
|
def receive_data(data)
|
87
|
-
@
|
83
|
+
@data_handler.call(data)
|
88
84
|
end
|
89
85
|
|
90
86
|
def drain_and_close
|
@@ -123,62 +119,82 @@ module RightScale
|
|
123
119
|
end
|
124
120
|
end
|
125
121
|
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
122
|
+
# See RightScale.popen3_async for details
|
123
|
+
def self.popen3_async_impl(cmd, target, options)
|
124
|
+
# always create eventables on the main EM thread by using next_tick. this
|
125
|
+
# prevents synchronization problems between EM threads.
|
126
|
+
::EM.next_tick do
|
127
|
+
process = nil
|
128
|
+
begin
|
129
|
+
# create process.
|
130
|
+
process = ::RightScale::RightPopen::Process.new(options)
|
131
|
+
process.spawn(cmd, target)
|
132
|
+
|
133
|
+
# connect EM eventables to open streams.
|
134
|
+
handlers = []
|
135
|
+
handlers << ::EM.attach(process.status_fd, ::RightScale::RightPopen::StatusHandler, process.status_fd)
|
136
|
+
handlers << ::EM.attach(process.stderr, ::RightScale::RightPopen::PipeHandler, process.stderr, target, :stderr_handler)
|
137
|
+
handlers << ::EM.attach(process.stdout, ::RightScale::RightPopen::PipeHandler, process.stdout, target, :stdout_handler)
|
138
|
+
handlers << ::EM.attach(process.stdin, ::RightScale::RightPopen::InputHandler, process.stdin, options[:input])
|
139
|
+
|
140
|
+
target.pid_handler(process.pid)
|
141
|
+
|
142
|
+
# initial watch callback.
|
143
|
+
#
|
144
|
+
# note that we cannot abandon async watch; callback needs to interrupt
|
145
|
+
# in this case
|
146
|
+
target.watch_handler(process)
|
147
|
+
|
148
|
+
# periodic watcher.
|
149
|
+
watch_process(process, 0.1, target, handlers)
|
150
|
+
rescue
|
151
|
+
# we can't raise from the main EM thread or it will stop EM.
|
152
|
+
# the spawn method will signal the exit handler but not the
|
153
|
+
# pid handler in this case since there is no pid. any action
|
154
|
+
# (logging, etc.) associated with the failure will have to be
|
155
|
+
# driven by the exit handler.
|
156
|
+
target.exit_handler(process.status) rescue nil if target && process
|
157
|
+
end
|
150
158
|
end
|
151
159
|
true
|
152
160
|
end
|
153
161
|
|
154
|
-
#
|
155
|
-
#
|
162
|
+
# watches process for exit or interrupt criteria. doubles the wait time up to
|
163
|
+
# a maximum of 1 second for next wait.
|
156
164
|
#
|
157
165
|
# === Parameters
|
158
|
-
#
|
159
|
-
# wait_time
|
160
|
-
#
|
161
|
-
#
|
162
|
-
# options[:target](Object):: Object initiating command execution
|
166
|
+
# @param [Process] process that was run
|
167
|
+
# @param [Numeric] wait_time as seconds to wait before checking status
|
168
|
+
# @param [Object] target for handler calls
|
169
|
+
# @param [Array] handlers used by eventmachine for status, stderr, stdout, and stdin
|
163
170
|
#
|
164
171
|
# === Return
|
165
172
|
# true:: Always return true
|
166
|
-
def self.
|
167
|
-
EM::Timer.new(wait_time) do
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
first_exception = e unless first_exception
|
173
|
+
def self.watch_process(process, wait_time, target, handlers)
|
174
|
+
::EM::Timer.new(wait_time) do
|
175
|
+
begin
|
176
|
+
if process.alive?
|
177
|
+
if process.timer_expired? || process.size_limit_exceeded?
|
178
|
+
process.interrupt
|
179
|
+
else
|
180
|
+
# cannot abandon async watch; callback needs to interrupt in this case
|
181
|
+
target.watch_handler(process)
|
176
182
|
end
|
183
|
+
watch_process(process, [wait_time * 2, 1].min, target, handlers)
|
184
|
+
else
|
185
|
+
handlers.each { |h| h.drain_and_close rescue nil }
|
186
|
+
process.wait_for_exit_status
|
187
|
+
target.timeout_handler rescue nil if process.timer_expired?
|
188
|
+
target.size_limit_handler rescue nil if process.size_limit_exceeded?
|
189
|
+
target.exit_handler(process.status) rescue nil
|
177
190
|
end
|
178
|
-
|
179
|
-
raise
|
180
|
-
|
181
|
-
|
191
|
+
rescue
|
192
|
+
# we can't raise from the main EM thread or it will stop EM.
|
193
|
+
# the spawn method will signal the exit handler but not the
|
194
|
+
# pid handler in this case since there is no pid. any action
|
195
|
+
# (logging, etc.) associated with the failure will have to be
|
196
|
+
# driven by the exit handler.
|
197
|
+
target.exit_handler(process.status) rescue nil if target && process
|
182
198
|
end
|
183
199
|
end
|
184
200
|
true
|
@@ -0,0 +1,35 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "process"))
|
25
|
+
|
26
|
+
module RightScale::RightPopen
|
27
|
+
|
28
|
+
# See RightScale.popen3_sync for details
|
29
|
+
def self.popen3_sync_impl(cmd, target, options)
|
30
|
+
process = ::RightScale::RightPopen::Process.new(options)
|
31
|
+
process.sync_all(cmd, target)
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|