vx-common-spawn 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a7482ed7e3691e29c5345ff6ae38a1934a5f3a2a
4
+ data.tar.gz: 37fb27fd0928e5d2751ea6d94b2648607fe0096d
5
+ SHA512:
6
+ metadata.gz: 5be3b914e33d60f4fd228ddcb95cfea572517fcb70e9becca55906e12a5f801793977bce1fb6835c28108765d0a84a38485499d33591307a98354e94647124cf
7
+ data.tar.gz: 8dbdfee09a0b4c137831ca296ec5984402c612fedd14e3b90d48c6068f3b48d1f5779b81187f0559aacf0f017a6c8df836c0a3dd1a5a8f4784dcd818db784a84
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --order=rand
3
+ -fd
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - 2.0.0
4
+ before_install:
5
+ - echo 'travis:travis' | sudo chpasswd
6
+ - echo 'PasswordAuthentication yes' | sudo tee -a /etc/ssh/sshd_config
7
+ - sudo service ssh restart
8
+ before_script:
9
+ - export SSH_USER=travis
10
+ - export SSH_PASS=travis
11
+ - export SSH_HOST=localhost
12
+ - export SSH_PORT=22
13
+ script: bundle exec rake SPEC_OPTS='-fd --color --order=rand'
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in vx-common-spawn.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,276 @@
1
+ Copyright (c) 2013 Dmitry Galinsky
2
+
3
+ The GNU General Public License, Version 2, June 1991 (GPLv2)
4
+ ============================================================
5
+
6
+ > Copyright (C) 1989, 1991 Free Software Foundation, Inc.
7
+ > 59 Temple Place, Suite 330
8
+ > Boston, MA 02111-1307 USA
9
+
10
+ Everyone is permitted to copy and distribute verbatim copies of this license
11
+ document, but changing it is not allowed.
12
+
13
+
14
+ Preamble
15
+ --------
16
+
17
+ The licenses for most software are designed to take away your freedom to share
18
+ and change it. By contrast, the GNU General Public License is intended to
19
+ guarantee your freedom to share and change free software--to make sure the
20
+ software is free for all its users. This General Public License applies to most
21
+ of the Free Software Foundation's software and to any other program whose
22
+ authors commit to using it. (Some other Free Software Foundation software is
23
+ covered by the GNU Library General Public License instead.) You can apply it to
24
+ your programs, too.
25
+
26
+ When we speak of free software, we are referring to freedom, not price. Our
27
+ General Public Licenses are designed to make sure that you have the freedom to
28
+ distribute copies of free software (and charge for this service if you wish),
29
+ that you receive source code or can get it if you want it, that you can change
30
+ the software or use pieces of it in new free programs; and that you know you can
31
+ do these things.
32
+
33
+ To protect your rights, we need to make restrictions that forbid anyone to deny
34
+ you these rights or to ask you to surrender the rights. These restrictions
35
+ translate to certain responsibilities for you if you distribute copies of the
36
+ software, or if you modify it.
37
+
38
+ For example, if you distribute copies of such a program, whether gratis or for a
39
+ fee, you must give the recipients all the rights that you have. You must make
40
+ sure that they, too, receive or can get the source code. And you must show them
41
+ these terms so they know their rights.
42
+
43
+ We protect your rights with two steps: (1) copyright the software, and (2) offer
44
+ you this license which gives you legal permission to copy, distribute and/or
45
+ modify the software.
46
+
47
+ Also, for each author's protection and ours, we want to make certain that
48
+ everyone understands that there is no warranty for this free software. If the
49
+ software is modified by someone else and passed on, we want its recipients to
50
+ know that what they have is not the original, so that any problems introduced by
51
+ others will not reflect on the original authors' reputations.
52
+
53
+ Finally, any free program is threatened constantly by software patents. We wish
54
+ to avoid the danger that redistributors of a free program will individually
55
+ obtain patent licenses, in effect making the program proprietary. To prevent
56
+ this, we have made it clear that any patent must be licensed for everyone's free
57
+ use or not licensed at all.
58
+
59
+ The precise terms and conditions for copying, distribution and modification
60
+ follow.
61
+
62
+
63
+ Terms And Conditions For Copying, Distribution And Modification
64
+ ---------------------------------------------------------------
65
+ 0. This License applies to any program or other work which contains a notice
66
+ placed by the copyright holder saying it may be distributed under the terms
67
+ of this General Public License. The "Program", below, refers to any such
68
+ program or work, and a "work based on the Program" means either the Program
69
+ or any derivative work under copyright law: that is to say, a work
70
+ containing the Program or a portion of it, either verbatim or with
71
+ modifications and/or translated into another language. (Hereinafter,
72
+ translation is included without limitation in the term "modification".)
73
+ Each licensee is addressed as "you".
74
+
75
+ Activities other than copying, distribution and modification are not
76
+ covered by this License; they are outside its scope. The act of running the
77
+ Program is not restricted, and the output from the Program is covered only
78
+ if its contents constitute a work based on the Program (independent of
79
+ having been made by running the Program). Whether that is true depends on
80
+ what the Program does.
81
+
82
+ 1. You may copy and distribute verbatim copies of the Program's source code as
83
+ you receive it, in any medium, provided that you conspicuously and
84
+ appropriately publish on each copy an appropriate copyright notice and
85
+ disclaimer of warranty; keep intact all the notices that refer to this
86
+ License and to the absence of any warranty; and give any other recipients
87
+ of the Program a copy of this License along with the Program.
88
+
89
+ You may charge a fee for the physical act of transferring a copy, and you
90
+ may at your option offer warranty protection in exchange for a fee.
91
+
92
+ 2. You may modify your copy or copies of the Program or any portion of it,
93
+ thus forming a work based on the Program, and copy and distribute such
94
+ modifications or work under the terms of Section 1 above, provided that you
95
+ also meet all of these conditions:
96
+
97
+ a) You must cause the modified files to carry prominent notices stating
98
+ that you changed the files and the date of any change.
99
+
100
+ b) You must cause any work that you distribute or publish, that in whole
101
+ or in part contains or is derived from the Program or any part thereof,
102
+ to be licensed as a whole at no charge to all third parties under the
103
+ terms of this License.
104
+
105
+ c) If the modified program normally reads commands interactively when run,
106
+ you must cause it, when started running for such interactive use in the
107
+ most ordinary way, to print or display an announcement including an
108
+ appropriate copyright notice and a notice that there is no warranty (or
109
+ else, saying that you provide a warranty) and that users may
110
+ redistribute the program under these conditions, and telling the user
111
+ how to view a copy of this License. (Exception: if the Program itself
112
+ is interactive but does not normally print such an announcement, your
113
+ work based on the Program is not required to print an announcement.)
114
+
115
+ These requirements apply to the modified work as a whole. If identifiable
116
+ sections of that work are not derived from the Program, and can be
117
+ reasonably considered independent and separate works in themselves, then
118
+ this License, and its terms, do not apply to those sections when you
119
+ distribute them as separate works. But when you distribute the same
120
+ sections as part of a whole which is a work based on the Program, the
121
+ distribution of the whole must be on the terms of this License, whose
122
+ permissions for other licensees extend to the entire whole, and thus to
123
+ each and every part regardless of who wrote it.
124
+
125
+ Thus, it is not the intent of this section to claim rights or contest your
126
+ rights to work written entirely by you; rather, the intent is to exercise
127
+ the right to control the distribution of derivative or collective works
128
+ based on the Program.
129
+
130
+ In addition, mere aggregation of another work not based on the Program with
131
+ the Program (or with a work based on the Program) on a volume of a storage
132
+ or distribution medium does not bring the other work under the scope of
133
+ this License.
134
+
135
+ 3. You may copy and distribute the Program (or a work based on it, under
136
+ Section 2) in object code or executable form under the terms of Sections 1
137
+ and 2 above provided that you also do one of the following:
138
+
139
+ a) Accompany it with the complete corresponding machine-readable source
140
+ code, which must be distributed under the terms of Sections 1 and 2
141
+ above on a medium customarily used for software interchange; or,
142
+
143
+ b) Accompany it with a written offer, valid for at least three years, to
144
+ give any third party, for a charge no more than your cost of physically
145
+ performing source distribution, a complete machine-readable copy of the
146
+ corresponding source code, to be distributed under the terms of
147
+ Sections 1 and 2 above on a medium customarily used for software
148
+ interchange; or,
149
+
150
+ c) Accompany it with the information you received as to the offer to
151
+ distribute corresponding source code. (This alternative is allowed only
152
+ for noncommercial distribution and only if you received the program in
153
+ object code or executable form with such an offer, in accord with
154
+ Subsection b above.)
155
+
156
+ The source code for a work means the preferred form of the work for making
157
+ modifications to it. For an executable work, complete source code means all
158
+ the source code for all modules it contains, plus any associated interface
159
+ definition files, plus the scripts used to control compilation and
160
+ installation of the executable. However, as a special exception, the source
161
+ code distributed need not include anything that is normally distributed (in
162
+ either source or binary form) with the major components (compiler, kernel,
163
+ and so on) of the operating system on which the executable runs, unless
164
+ that component itself accompanies the executable.
165
+
166
+ If distribution of executable or object code is made by offering access to
167
+ copy from a designated place, then offering equivalent access to copy the
168
+ source code from the same place counts as distribution of the source code,
169
+ even though third parties are not compelled to copy the source along with
170
+ the object code.
171
+
172
+ 4. You may not copy, modify, sublicense, or distribute the Program except as
173
+ expressly provided under this License. Any attempt otherwise to copy,
174
+ modify, sublicense or distribute the Program is void, and will
175
+ automatically terminate your rights under this License. However, parties
176
+ who have received copies, or rights, from you under this License will not
177
+ have their licenses terminated so long as such parties remain in full
178
+ compliance.
179
+
180
+ 5. You are not required to accept this License, since you have not signed it.
181
+ However, nothing else grants you permission to modify or distribute the
182
+ Program or its derivative works. These actions are prohibited by law if you
183
+ do not accept this License. Therefore, by modifying or distributing the
184
+ Program (or any work based on the Program), you indicate your acceptance of
185
+ this License to do so, and all its terms and conditions for copying,
186
+ distributing or modifying the Program or works based on it.
187
+
188
+ 6. Each time you redistribute the Program (or any work based on the Program),
189
+ the recipient automatically receives a license from the original licensor
190
+ to copy, distribute or modify the Program subject to these terms and
191
+ conditions. You may not impose any further restrictions on the recipients'
192
+ exercise of the rights granted herein. You are not responsible for
193
+ enforcing compliance by third parties to this License.
194
+
195
+ 7. If, as a consequence of a court judgment or allegation of patent
196
+ infringement or for any other reason (not limited to patent issues),
197
+ conditions are imposed on you (whether by court order, agreement or
198
+ otherwise) that contradict the conditions of this License, they do not
199
+ excuse you from the conditions of this License. If you cannot distribute so
200
+ as to satisfy simultaneously your obligations under this License and any
201
+ other pertinent obligations, then as a consequence you may not distribute
202
+ the Program at all. For example, if a patent license would not permit
203
+ royalty-free redistribution of the Program by all those who receive copies
204
+ directly or indirectly through you, then the only way you could satisfy
205
+ both it and this License would be to refrain entirely from distribution of
206
+ the Program.
207
+
208
+ If any portion of this section is held invalid or unenforceable under any
209
+ particular circumstance, the balance of the section is intended to apply
210
+ and the section as a whole is intended to apply in other circumstances.
211
+
212
+ It is not the purpose of this section to induce you to infringe any patents
213
+ or other property right claims or to contest validity of any such claims;
214
+ this section has the sole purpose of protecting the integrity of the free
215
+ software distribution system, which is implemented by public license
216
+ practices. Many people have made generous contributions to the wide range
217
+ of software distributed through that system in reliance on consistent
218
+ application of that system; it is up to the author/donor to decide if he or
219
+ she is willing to distribute software through any other system and a
220
+ licensee cannot impose that choice.
221
+
222
+ This section is intended to make thoroughly clear what is believed to be a
223
+ consequence of the rest of this License.
224
+
225
+ 8. If the distribution and/or use of the Program is restricted in certain
226
+ countries either by patents or by copyrighted interfaces, the original
227
+ copyright holder who places the Program under this License may add an
228
+ explicit geographical distribution limitation excluding those countries, so
229
+ that distribution is permitted only in or among countries not thus excluded.
230
+ In such case, this License incorporates the limitation as if written in
231
+ the body of this License.
232
+
233
+ 9. The Free Software Foundation may publish revised and/or new versions of the
234
+ General Public License from time to time. Such new versions will be similar
235
+ in spirit to the present version, but may differ in detail to address new
236
+ problems or concerns.
237
+
238
+ Each version is given a distinguishing version number. If the Program
239
+ specifies a version number of this License which applies to it and "any
240
+ later version", you have the option of following the terms and conditions
241
+ either of that version or of any later version published by the Free
242
+ Software Foundation. If the Program does not specify a version number of
243
+ this License, you may choose any version ever published by the Free
244
+ Software Foundation.
245
+
246
+ 10. If you wish to incorporate parts of the Program into other free programs
247
+ whose distribution conditions are different, write to the author to ask for
248
+ permission. For software which is copyrighted by the Free Software
249
+ Foundation, write to the Free Software Foundation; we sometimes make
250
+ exceptions for this. Our decision will be guided by the two goals of
251
+ preserving the free status of all derivatives of our free software and of
252
+ promoting the sharing and reuse of software generally.
253
+
254
+
255
+ No Warranty
256
+ -----------
257
+
258
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR
259
+ THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
260
+ OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
261
+ PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
262
+ OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
263
+ MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO
264
+ THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM
265
+ PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
266
+ CORRECTION.
267
+
268
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL
269
+ ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE
270
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
271
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
272
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
273
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
274
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
275
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
276
+ SUCH DAMAGES.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Vx::Common::Spawn
2
+
3
+ This gem helps to spawn processes in a shell capturing output in realtime.
4
+ It also allows to set the temeouts.
5
+
6
+ ## Requirements
7
+
8
+ MRI 1.9.3 or 2.0.0.
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'vx-common-spawn'
15
+
16
+ And then execute the bundler:
17
+
18
+ $ bundle
19
+
20
+ Or install it via `gem` command:
21
+
22
+ $ gem install vx-common-spawn
23
+
24
+ ## Quick Start
25
+
26
+ The following snippet demonstrates the usage:
27
+
28
+ ```ruby
29
+ # Spawn system processes example
30
+
31
+ include Vx::Common::Spawn
32
+
33
+ spawn "ls -la" do |output|
34
+ print output
35
+ # prints directory listing
36
+ end
37
+
38
+ spawn({'ENV_VAR' => 'VALUE'}, "echo $VALUE", timeout: 10) do |output|
39
+ print output
40
+ # its print "VALUE\n"
41
+ end
42
+ ```
43
+
44
+
45
+ ```ruby
46
+ # Spawn remote processes example
47
+
48
+ open_ssh('localhost', 'user') do |ssh|
49
+ ssh.spawn("ls -la") do |output|
50
+ print output
51
+ # prints directory listing
52
+ end
53
+
54
+ spawn({'ENV_VAR' => 'VALUE'}, "echo $VALUE", read_timeout: 10) do |output|
55
+ print output
56
+ # its print "VALUE\n"
57
+ end
58
+ end
59
+
60
+ ```
61
+
62
+ ### Timeouts
63
+
64
+ When a timeout is reached spawn raises ```Vx::Common::Spawn::TimeoutError``` or
65
+ ```Vx::Common::Spawn::ReadTimeoutError```. Both exceptions inherit
66
+ from Timeout::Error
67
+
68
+ ### Return values
69
+
70
+ Both ```spawn``` methods return process exit code. If a process was terminated by a signal, for example
71
+ KILL or INT, the methods return negative number identical to a signal number (-9 for KILL, etc.)
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create new Pull Request
80
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,36 @@
1
+ require File.expand_path("../spawn/version", __FILE__)
2
+
3
+ module Vx
4
+ module Common
5
+ module Spawn
6
+
7
+ autoload :Process, File.expand_path("../spawn/process", __FILE__)
8
+ autoload :SSH, File.expand_path("../spawn/ssh", __FILE__)
9
+ autoload :Timeout, File.expand_path("../spawn/timeout", __FILE__)
10
+ autoload :ReadTimeout, File.expand_path("../spawn/read_timeout", __FILE__)
11
+ autoload :TimeoutError, File.expand_path("../spawn/error", __FILE__)
12
+ autoload :ReadTimeoutError, File.expand_path("../spawn/error", __FILE__)
13
+
14
+ class << self
15
+ @@pool_interval = 0.1
16
+
17
+ def pool_interval
18
+ @@pool_interval
19
+ end
20
+
21
+ def pool_interval=(val)
22
+ @@pool_interval = val
23
+ end
24
+ end
25
+
26
+ def open_ssh(*args, &block)
27
+ Common::Spawn::SSH.open(*args, &block)
28
+ end
29
+
30
+ def spawn(*args, &block)
31
+ Common::Spawn::Process.spawn(*args, &block)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,34 @@
1
+ require 'timeout'
2
+
3
+ module Vx
4
+ module Common
5
+ module Spawn
6
+
7
+ class TimeoutError < ::Timeout::Error
8
+
9
+ def initialize(cmd, seconds)
10
+ @cmd = cmd
11
+ @seconds = seconds
12
+ end
13
+
14
+ def to_s
15
+ "Execution expired, command did not finish within #{@seconds} seconds"
16
+ end
17
+
18
+ end
19
+
20
+ class ReadTimeoutError < ::Timeout::Error
21
+
22
+ def initialize(cmd, seconds)
23
+ @cmd = cmd
24
+ @seconds = seconds
25
+ end
26
+
27
+ def to_s
28
+ "No output has been received in the last #{@seconds} seconds"
29
+ end
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,69 @@
1
+ require 'timeout'
2
+
3
+ module Vx
4
+ module Common
5
+ module Spawn
6
+ module Process
7
+
8
+ extend self
9
+
10
+ def spawn(*args, &block)
11
+ env = args.first.is_a?(Hash) ? args.shift : {}
12
+ options = args.last.is_a?(Hash) ? args.pop : {}
13
+ cmd = args.join(" ")
14
+
15
+ select_timeout = options.delete(:pool_interval) || Spawn.pool_interval
16
+ timeout = Spawn::Timeout.new options.delete(:timeout)
17
+ read_timeout = Spawn::ReadTimeout.new options.delete(:read_timeout)
18
+
19
+ r,w = IO.pipe
20
+ r.sync = true
21
+
22
+ pid = ::Process.spawn(env, cmd, options.merge(out: w, err: w))
23
+ w.close
24
+
25
+ read_loop r, timeout, read_timeout, select_timeout, &block
26
+
27
+ ::Process.kill 'KILL', pid
28
+ _, status = ::Process.wait2(pid) # protect from zombies
29
+
30
+ compute_exit_code cmd, status, timeout, read_timeout
31
+ end
32
+
33
+ private
34
+
35
+ def compute_exit_code(command, status, timeout, read_timeout)
36
+ case
37
+ when read_timeout.happened?
38
+ raise Spawn::ReadTimeoutError.new command, read_timeout.value
39
+ when timeout.happened?
40
+ raise Spawn::TimeoutError.new command, timeout.value
41
+ else
42
+ termsig = status.termsig
43
+ exit_code = status.exitstatus
44
+ exit_code || (termsig && termsig * -1) || -1
45
+ end
46
+ end
47
+
48
+ def read_loop(reader, timeout, read_timeout, interval, &block)
49
+ read_timeout.reset
50
+
51
+ loop do
52
+ break if timeout.happened?
53
+
54
+ rs, _, _ = IO.select([reader], nil, nil, interval)
55
+
56
+ if rs
57
+ break if rs[0].eof?
58
+ yield rs[0].readpartial(8192) if block_given?
59
+ read_timeout.reset
60
+ else
61
+ break if read_timeout.happened?
62
+ end
63
+ end
64
+ end
65
+
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ module Vx
2
+ module Common
3
+ module Spawn
4
+ class ReadTimeout
5
+
6
+ def initialize(val)
7
+ @value = val.to_f > 0 ? val.to_f : nil
8
+ @happened = false
9
+ end
10
+
11
+ def reset
12
+ @tm = Time.new if @value
13
+ end
14
+
15
+ def happened?
16
+ return true if @happened
17
+ return false unless @tm
18
+
19
+ @happened = Time.now > (@tm + @value)
20
+ end
21
+
22
+ def value
23
+ @value
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,118 @@
1
+ require 'net/ssh'
2
+ require 'timeout'
3
+
4
+ module Vx
5
+ module Common
6
+ module Spawn
7
+ class SSH
8
+
9
+ class << self
10
+ def open(host, user, options = {}, &block)
11
+ ::Net::SSH.start(host, user, {
12
+ paranoid: false
13
+ }.merge(options)) do |ssh|
14
+ yield new(ssh)
15
+ end
16
+ end
17
+ end
18
+
19
+ attr_reader :host, :user, :options, :connection
20
+
21
+ def initialize(ssh)
22
+ @connection = ssh
23
+ end
24
+
25
+ def spawn(*args, &block)
26
+ env = args.first.is_a?(Hash) ? args.shift : {}
27
+ options = args.last.is_a?(Hash) ? args.pop : {}
28
+ command = args.join(" ")
29
+
30
+ exit_code = nil
31
+ timeout = Spawn::Timeout.new options.delete(:timeout)
32
+ read_timeout = Spawn::ReadTimeout.new options.delete(:read_timeout)
33
+
34
+ command = build_command(env, command, options)
35
+ channel = spawn_channel command, read_timeout, &block
36
+
37
+ channel.on_request("exit-status") do |_,data|
38
+ exit_code = data.read_long
39
+ end
40
+
41
+ pool channel, timeout, read_timeout
42
+
43
+ compute_exit_code command, exit_code, timeout, read_timeout
44
+ end
45
+
46
+ private
47
+
48
+ def normalize_nl(str)
49
+ str.gsub(/\r\n?/, "\n")
50
+ end
51
+
52
+ def build_command(env, command, options)
53
+ cmd = command
54
+ unless env.empty?
55
+ e = env.map{|k,v| "#{k}=#{v}" }.join(" ")
56
+ cmd = "env #{e} #{cmd}"
57
+ end
58
+ if options.key?(:chdir)
59
+ e = "cd #{options[:chdir]}"
60
+ cmd = "#{e} ; #{cmd}"
61
+ end
62
+ cmd
63
+ end
64
+
65
+ def pool(channel, timeout, read_timeout)
66
+ @connection.loop Spawn.pool_interval do
67
+ if read_timeout.happened? || timeout.happened?
68
+ false
69
+ else
70
+ channel.active?
71
+ end
72
+ end
73
+ end
74
+
75
+ def compute_exit_code(command, exit_code, timeout, read_timeout)
76
+ case
77
+ when read_timeout.happened?
78
+ raise Spawn::ReadTimeoutError.new command, read_timeout.value
79
+ when timeout.happened?
80
+ raise Spawn::TimeoutError.new command, timeout.value
81
+ else
82
+ exit_code || -1 # nil exit_code means that the process is killed
83
+ end
84
+ end
85
+
86
+ def spawn_channel(command, read_timeout, &block)
87
+ @connection.open_channel do |channel|
88
+
89
+ #channel.request_pty do |_, pty_status|
90
+ # raise StandardError, "could not obtain pty" unless pty_status
91
+
92
+ read_timeout.reset
93
+
94
+ channel.exec command do |_, success|
95
+
96
+ unless success
97
+ yield "FAILED: couldn't execute command (ssh.channel.exec)\n" if block_given?
98
+ end
99
+
100
+ channel.on_data do |_, data|
101
+ yield normalize_nl(data) if block_given?
102
+ read_timeout.reset
103
+ end
104
+
105
+ channel.on_extended_data do |_, _, data|
106
+ yield normalize_nl(data) if block_given?
107
+ read_timeout.reset
108
+ end
109
+ end
110
+ #end
111
+ end
112
+
113
+ end
114
+
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,26 @@
1
+ module Vx
2
+ module Common
3
+ module Spawn
4
+ class Timeout
5
+ def initialize(value)
6
+ @value = (value.to_f > 0) ? value.to_f : nil
7
+ if @value
8
+ @time_end = Time.now + @value
9
+ end
10
+ @happened = false
11
+ end
12
+
13
+ def happened?
14
+ return false unless value
15
+ return true if @happened
16
+
17
+ @happened = Time.now > @time_end
18
+ end
19
+
20
+ def value
21
+ @value
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ module Vx
2
+ module Common
3
+ module Spawn
4
+ VERSION = "0.0.7"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+ require 'timeout'
3
+
4
+ describe Vx::Common::Spawn::Process do
5
+ let(:collected) { "" }
6
+ let(:user) { ENV['USER'] }
7
+
8
+ subject { collected }
9
+
10
+ it "run command successfuly" do
11
+ code = run "echo $USER"
12
+ expect(subject).to eq "#{user}\n"
13
+ expect(code).to eq 0
14
+ end
15
+
16
+ it "run command with error" do
17
+ code = run( "false")
18
+ expect(subject).to eq ""
19
+ expect(code).to eq 1
20
+ end
21
+
22
+ it "run command with env successfuly" do
23
+ code = run( {'FOO' => "BAR" }, "echo $FOO")
24
+ expect(subject).to eq "BAR\n"
25
+ expect(code).to eq 0
26
+ end
27
+
28
+ context "timeout" do
29
+ it 'run command with timeout' do
30
+ expect {
31
+ run("echo $USER && sleep 0.5", timeout: 0.2)
32
+ }.to raise_error(Vx::Common::Spawn::TimeoutError)
33
+ expect(subject).to eq "#{user}\n"
34
+ end
35
+
36
+ it 'run command with timeout successfuly' do
37
+ code = run( {'FOO' => "BAR" }, "echo $FOO && sleep 0.1", timeout: 0.5)
38
+ expect(subject).to eq "BAR\n"
39
+ expect(code).to eq 0
40
+ end
41
+ end
42
+
43
+ context "read_timeout" do
44
+ it 'run command with read timeout' do
45
+ expect{
46
+ run('sleep 0.5', read_timeout: 0.2)
47
+ }.to raise_error(Vx::Common::Spawn::ReadTimeoutError)
48
+ expect(collected).to eq ""
49
+ end
50
+
51
+ it 'run command with read timeout in loop' do
52
+ expect{
53
+ run('sleep 0.1 ; echo $USER ; sleep 0.5', read_timeout: 0.3)
54
+ }.to raise_error(Vx::Common::Spawn::ReadTimeoutError)
55
+ expect(collected).to eq "#{user}\n"
56
+ end
57
+
58
+ it 'run command with read timeout successfuly' do
59
+ code = run('echo $USER; sleep 0.1', read_timeout: 0.5)
60
+ expect(collected).to eq "#{user}\n"
61
+ expect(code).to eq 0
62
+ end
63
+
64
+ it 'run command with read timeout in loop successfuly' do
65
+ code = run('sleep 0.3 ; echo $USER; sleep 0.3 ; echo $USER', read_timeout: 0.5)
66
+ expect(collected).to eq "#{user}\n#{user}\n"
67
+ expect(code).to eq 0
68
+ end
69
+ end
70
+
71
+ it 'run and kill process' do
72
+ code = run( "echo $USER; kill -KILL $$")
73
+ expect(subject).to eq "#{user}\n"
74
+ expect(code).to eq(-9)
75
+ end
76
+
77
+ it 'run and interupt process' do
78
+ code = run( "echo $USER; kill -INT $$")
79
+ expect(subject).to eq "#{user}\n"
80
+ expect(code).to eq(-2)
81
+ end
82
+
83
+ def run(*args, &block)
84
+ timeout do
85
+ described_class.spawn(*args) do |out|
86
+ collected << out
87
+ end
88
+ end
89
+ end
90
+
91
+ def timeout
92
+ Timeout.timeout(10) do
93
+ yield
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vx::Common::Spawn::ReadTimeout do
4
+ subject { described_class.new 0.2 }
5
+
6
+ context "just created" do
7
+ its(:value) { should eq 0.2 }
8
+ its(:happened?) { should be_false }
9
+ end
10
+
11
+ it "should be work" do
12
+ subject.reset
13
+ sleep 0.1
14
+ expect(subject.happened?).to be_false
15
+
16
+ subject.reset
17
+ sleep 0.3
18
+ expect(subject.happened?).to be_true
19
+ end
20
+
21
+ it "do nothing unless value" do
22
+ expect(subject.happened?).to be_false
23
+ end
24
+
25
+ it "do nothing unless timeout" do
26
+ subject.reset
27
+ sleep 0.1
28
+ expect(subject.happened?).to be_false
29
+ end
30
+ end
@@ -0,0 +1,123 @@
1
+ require 'spec_helper'
2
+ require 'timeout'
3
+
4
+ describe Vx::Common::Spawn::SSH, ssh: true do
5
+
6
+ let(:user) { ENV['SSH_USER'] || 'vagrant' }
7
+ let(:host) { ENV['SSH_HOST'] || 'localhost' }
8
+ let(:pass) { ENV['SSH_PASS'] || 'vagrant' }
9
+ let(:port) { ENV['SSH_PORT'] || 2222 }
10
+ let(:collected) { '' }
11
+
12
+ it "run command successfuly" do
13
+ code = run_ssh 'echo $USER'
14
+ expect(collected).to eq "#{user}\n"
15
+ expect(code).to eq 0
16
+ end
17
+
18
+ it "run command with error" do
19
+ code = run_ssh 'false'
20
+ expect(collected).to eq ""
21
+ expect(code).to eq 1
22
+ end
23
+
24
+ it "run command with env successfuly" do
25
+ code = run_ssh({'FOO' => "BAR"}, "sh -c 'echo $FOO'")
26
+ expect(collected).to eq "BAR\n"
27
+ expect(code).to eq 0
28
+ end
29
+
30
+ it "run command with chdir successfuly" do
31
+ code = run_ssh("echo $(pwd)", chdir: "/tmp")
32
+ expect(collected).to eq "/tmp\n"
33
+ expect(code).to eq 0
34
+ end
35
+
36
+ it "run command with chdir and env successfuly" do
37
+ code = run_ssh(
38
+ {'FOO' => "BAR"},
39
+ "sh -c 'echo $FOO' ; echo $(pwd)",
40
+ chdir: '/tmp'
41
+ )
42
+ expect(collected).to eq "BAR\n/tmp\n"
43
+ expect(code).to eq 0
44
+ end
45
+
46
+ context "timeout" do
47
+ it 'run command with timeout' do
48
+ expect{
49
+ run_ssh('echo $USER; sleep 0.5', timeout: 0.2)
50
+ }.to raise_error(Vx::Common::Spawn::TimeoutError)
51
+ end
52
+
53
+ it 'run command with timeout successfuly' do
54
+ code = run_ssh('echo $USER; sleep 0.2', timeout: 1)
55
+ expect(collected).to eq "#{user}\n"
56
+ expect(code).to eq 0
57
+ end
58
+ end
59
+
60
+ context "read_timeout" do
61
+ it 'run command with read timeout' do
62
+ expect{
63
+ run_ssh('sleep 0.5', read_timeout: 0.2)
64
+ }.to raise_error(Vx::Common::Spawn::ReadTimeoutError)
65
+ expect(collected).to eq ""
66
+ end
67
+
68
+ it 'run command with read timeout in loop' do
69
+ expect{
70
+ run_ssh('sleep 0.1 ; echo $USER ; sleep 0.5', read_timeout: 0.3)
71
+ }.to raise_error(Vx::Common::Spawn::ReadTimeoutError)
72
+ expect(collected).to eq "#{user}\n"
73
+ end
74
+
75
+ it 'run command with read timeout successfuly' do
76
+ code = run_ssh('echo $USER; sleep 0.1', read_timeout: 0.5)
77
+ expect(collected).to eq "#{user}\n"
78
+ expect(code).to eq 0
79
+ end
80
+
81
+ it 'run command with read timeout in loop successfuly' do
82
+ code = run_ssh('sleep 0.3 ; echo $USER; sleep 0.3 ; echo $USER', read_timeout: 0.5)
83
+ expect(collected).to eq "#{user}\n#{user}\n"
84
+ expect(code).to eq 0
85
+ end
86
+ end
87
+
88
+ it 'run and kill process' do
89
+ code = run_ssh("echo $USER; kill -9 $$")
90
+ expect(collected).to eq "#{user}\n"
91
+ expect(code).to eq(-1)
92
+ end
93
+
94
+ it 'run and interupt process' do
95
+ code = run_ssh("echo $USER; kill -9 $$")
96
+ expect(collected).to eq "#{user}\n"
97
+ expect(code).to eq(-1)
98
+ end
99
+
100
+ def open_ssh(&block)
101
+ described_class.open(host, user, password: pass, paranoid: false, verbose: 2, port: port, &block)
102
+ end
103
+
104
+ def re(s)
105
+ Regexp.escape s
106
+ end
107
+
108
+ def run_ssh(*args)
109
+ timeout do
110
+ open_ssh do |ssh|
111
+ ssh.spawn(*args) do |s|
112
+ collected << s
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ def timeout
119
+ Timeout.timeout(10) do
120
+ yield
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Vx::Common::Spawn do
4
+
5
+ subject { Object.new }
6
+
7
+ before { subject.extend described_class }
8
+
9
+ context "spawn" do
10
+ it "should be" do
11
+ expect(subject.spawn 'true').to eq 0
12
+ end
13
+ end
14
+
15
+ context "open_ssh" do
16
+ let(:user) { ENV['SSH_USER'] || 'vagrant' }
17
+ let(:host) { ENV['SSH_HOST'] || 'localhost' }
18
+ let(:pass) { ENV['SSH_PASS'] || 'vagrant' }
19
+ let(:port) { ENV['SSH_PORT'] || 2222 }
20
+ let(:ssh) { nil }
21
+
22
+ it "should be" do
23
+ subject.open_ssh(host, user, password: pass, port: port) do |ssh|
24
+ expect(ssh.spawn 'true').to eq 0
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,6 @@
1
+ require 'rspec/autorun'
2
+ require File.expand_path('../../lib/vx/common/spawn', __FILE__)
3
+
4
+ RSpec.configure do |c|
5
+ end
6
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'vx/common/spawn/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "vx-common-spawn"
8
+ spec.version = Vx::Common::Spawn::VERSION
9
+ spec.authors = ["Dmitry Galinsky"]
10
+ spec.email = ["dima.exe@gmail.com"]
11
+ spec.description = %q{ Spawn processes in a shell capturing output in realtime. It also allows to set the temeouts. }
12
+ spec.summary = %q{ This gem helps to spawn processes in a shell capturing output in realtime. It also allows to set the temeouts. }
13
+ spec.homepage = "https://github.com/vextor/vx-common-spawn"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "net-ssh", "~> 2.6"
22
+ spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "rspec"
25
+ spec.add_development_dependency "pry"
26
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vx-common-spawn
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.7
5
+ platform: ruby
6
+ authors:
7
+ - Dmitry Galinsky
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: net-ssh
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '2.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '2.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: ' Spawn processes in a shell capturing output in realtime. It also allows
84
+ to set the temeouts. '
85
+ email:
86
+ - dima.exe@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - .gitignore
92
+ - .rspec
93
+ - .travis.yml
94
+ - Gemfile
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - lib/vx/common/spawn.rb
99
+ - lib/vx/common/spawn/error.rb
100
+ - lib/vx/common/spawn/process.rb
101
+ - lib/vx/common/spawn/read_timeout.rb
102
+ - lib/vx/common/spawn/ssh.rb
103
+ - lib/vx/common/spawn/timeout.rb
104
+ - lib/vx/common/spawn/version.rb
105
+ - spec/lib/spawn/process_spec.rb
106
+ - spec/lib/spawn/read_timeout_spec.rb
107
+ - spec/lib/spawn/ssh_spec.rb
108
+ - spec/lib/spawn_spec.rb
109
+ - spec/spec_helper.rb
110
+ - vx-common-spawn.gemspec
111
+ homepage: https://github.com/vextor/vx-common-spawn
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.1.11
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: This gem helps to spawn processes in a shell capturing output in realtime.
135
+ It also allows to set the temeouts.
136
+ test_files:
137
+ - spec/lib/spawn/process_spec.rb
138
+ - spec/lib/spawn/read_timeout_spec.rb
139
+ - spec/lib/spawn/ssh_spec.rb
140
+ - spec/lib/spawn_spec.rb
141
+ - spec/spec_helper.rb
142
+ has_rdoc: