right_popen 1.0.21 → 1.1.3
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.
- 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
|