evrone-common-spawn 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.travis.yml +12 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +83 -0
- data/Rakefile +7 -0
- data/evrone-common-spawn.gemspec +28 -0
- data/lib/evrone/common/spawn/error.rb +34 -0
- data/lib/evrone/common/spawn/process.rb +69 -0
- data/lib/evrone/common/spawn/read_timeout.rb +29 -0
- data/lib/evrone/common/spawn/ssh.rb +103 -0
- data/lib/evrone/common/spawn/timeout.rb +26 -0
- data/lib/evrone/common/spawn/version.rb +7 -0
- data/lib/evrone/common/spawn.rb +36 -0
- data/spec/lib/spawn/process_spec.rb +96 -0
- data/spec/lib/spawn/read_timeout_spec.rb +30 -0
- data/spec/lib/spawn/ssh_spec.rb +106 -0
- data/spec/lib/spawn_spec.rb +24 -0
- data/spec/spec_helper.rb +6 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e1b7c8972a1a7909719acde920d17104b4e6d382
|
4
|
+
data.tar.gz: aed3543ae957e4b8ef85a8417237d1684e659086
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fe6a1c24b8657dfc8527c1f8d76558ee2f540675b433c7a483f0d3bfde6aa74bef00d6b0e50a1cc4e9aae61dce6b6fa41b42ab2cf425a182aa56b26fa6412c30
|
7
|
+
data.tar.gz: 661353e09d8e41de56e4ad0d88358ef80306be4f3dba615d9f3ebaa9881c5aff0a4bbf17344b00fae1c65bbaa35f05287e02c9ce2922cfe43ba78d9ce362f992
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,12 @@
|
|
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
|
+
script: bundle exec rake SPEC_OPTS='-fd --color --order=rand'
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Dmitry Galinsky
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# Evrone::Common::Spawn
|
2
|
+
|
3
|
+
This gem helps to spawn system, ssh processes, capturing output in realtime,
|
4
|
+
allow to set temeouts and read timeouts.
|
5
|
+
|
6
|
+
* [](https://travis-ci.org/evrone/evrone-common-spawn)
|
7
|
+
* [](https://codeclimate.com/github/evrone/evrone-common-spawn)
|
8
|
+
|
9
|
+
## Requirements
|
10
|
+
|
11
|
+
MRI 1.9.3 or 2.0.0.
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add this line to your application's Gemfile:
|
16
|
+
|
17
|
+
gem 'evrone-common-spawn'
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install evrone-common-spawn
|
26
|
+
|
27
|
+
## Quick Start
|
28
|
+
|
29
|
+
Below is a small snippet that demonstrates how to use
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# Spawn system processes example
|
33
|
+
|
34
|
+
include Evrone::Common::Spawn
|
35
|
+
|
36
|
+
spawn "ls -la" do |output|
|
37
|
+
print output
|
38
|
+
# prints directory listing
|
39
|
+
end
|
40
|
+
|
41
|
+
spawn({'ENV_VAR' => 'VALUE'}, "echo $VALUE", timeout: 10) do |output|
|
42
|
+
print output
|
43
|
+
# its print "VALUE\n"
|
44
|
+
end
|
45
|
+
```
|
46
|
+
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
# Spawn remote processes example
|
50
|
+
|
51
|
+
open_ssh('localhost', 'user') do |ssh|
|
52
|
+
ssh.spawn("ls -la") do |output|
|
53
|
+
print output
|
54
|
+
# prints directory listing
|
55
|
+
end
|
56
|
+
|
57
|
+
spawn({'ENV_VAR' => 'VALUE'}, "echo $VALUE", read_timeout: 10) do |output|
|
58
|
+
print output
|
59
|
+
# its print "VALUE\n"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
```
|
64
|
+
|
65
|
+
### Timeouts
|
66
|
+
|
67
|
+
When timeout happened, spawn raises ```Evrone::Common::Spawn::TimeoutError``` or
|
68
|
+
```Evrone::Common::Spawn::ReadTimeoutError```, both exception classes inherited
|
69
|
+
from Timeout::Error
|
70
|
+
|
71
|
+
### Return values
|
72
|
+
|
73
|
+
Both ```spawn``` methods return process exit code, if process was killed by signal, for example
|
74
|
+
KILL or INT, return negative signal number (for KILL was -9)
|
75
|
+
|
76
|
+
## Contributing
|
77
|
+
|
78
|
+
1. Fork it
|
79
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
80
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
81
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
82
|
+
5. Create new Pull Request
|
83
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'evrone/common/spawn/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "evrone-common-spawn"
|
8
|
+
spec.version = Evrone::Common::Spawn::VERSION
|
9
|
+
spec.authors = ["Dmitry Galinsky"]
|
10
|
+
spec.email = ["dima.exe@gmail.com"]
|
11
|
+
spec.description = %q{ Spawn system, ssh processes, capturing output in realtime,
|
12
|
+
allow to set temeouts and read timeouts }
|
13
|
+
spec.summary = %q{ This gem helps to spawn system, ssh processes, capturing output in realtime,
|
14
|
+
allow to set temeouts and read timeouts }
|
15
|
+
spec.homepage = "https://github.com/evrone/evrone-common-spawn"
|
16
|
+
spec.license = "MIT"
|
17
|
+
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.add_runtime_dependency "net-ssh", "~> 2.6"
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec"
|
27
|
+
spec.add_development_dependency "pry"
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module Evrone
|
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 of '#{@cmd}' expired"
|
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 of '#{@cmd}' 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 Evrone
|
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)
|
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 Evrone
|
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,103 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
module Evrone
|
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
|
+
forward_agent: true,
|
13
|
+
paranoid: false
|
14
|
+
}.merge(options)) do |ssh|
|
15
|
+
yield new(ssh)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :host, :user, :options
|
21
|
+
|
22
|
+
def initialize(ssh)
|
23
|
+
@ssh = ssh
|
24
|
+
end
|
25
|
+
|
26
|
+
def spawn(*args, &block)
|
27
|
+
env = args.first.is_a?(Hash) ? args.shift : {}
|
28
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
29
|
+
command = args.join(" ")
|
30
|
+
|
31
|
+
exit_code = nil
|
32
|
+
timeout = Spawn::Timeout.new options.delete(:timeout)
|
33
|
+
read_timeout = Spawn::ReadTimeout.new options.delete(:read_timeout)
|
34
|
+
|
35
|
+
channel = spawn_channel env, 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 pool(channel, timeout, read_timeout)
|
49
|
+
@ssh.loop Spawn.pool_interval do
|
50
|
+
if read_timeout.happened? || timeout.happened?
|
51
|
+
false
|
52
|
+
else
|
53
|
+
channel.active?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def compute_exit_code(command, exit_code, timeout, read_timeout)
|
59
|
+
case
|
60
|
+
when read_timeout.happened?
|
61
|
+
raise Spawn::ReadTimeoutError.new command, read_timeout.value
|
62
|
+
when timeout.happened?
|
63
|
+
raise Spawn::TimeoutError.new command, timeout.value
|
64
|
+
else
|
65
|
+
exit_code || -1 # nil exit_code means that the process is killed
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def spawn_channel(env, command, read_timeout, &block)
|
70
|
+
|
71
|
+
@ssh.open_channel do |channel|
|
72
|
+
read_timeout.reset
|
73
|
+
|
74
|
+
env.each do |k, v|
|
75
|
+
channel.env k, v do |_, success|
|
76
|
+
yield "FAILED: couldn't execute command (ssh.channel.env)\n" if block_given?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
channel.exec command do |_, success|
|
81
|
+
|
82
|
+
unless success
|
83
|
+
yield "FAILED: couldn't execute command (ssh.channel.exec)\n" if block_given?
|
84
|
+
end
|
85
|
+
|
86
|
+
channel.on_data do |_, data|
|
87
|
+
yield data if block_given?
|
88
|
+
read_timeout.reset
|
89
|
+
end
|
90
|
+
|
91
|
+
channel.on_extended_data do |_, _, data|
|
92
|
+
yield data if block_given?
|
93
|
+
read_timeout.reset
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Evrone
|
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,36 @@
|
|
1
|
+
require File.expand_path("../spawn/version", __FILE__)
|
2
|
+
|
3
|
+
module Evrone
|
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,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
describe Evrone::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(Evrone::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(Evrone::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(Evrone::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 Evrone::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,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'timeout'
|
3
|
+
|
4
|
+
describe Evrone::Common::Spawn::SSH, ssh: true do
|
5
|
+
|
6
|
+
let(:user) { ENV['SSH_USER'] }
|
7
|
+
let(:host) { ENV['SSH_HOST'] }
|
8
|
+
let(:pass) { ENV['SSH_PASS'] }
|
9
|
+
let(:collected) { '' }
|
10
|
+
|
11
|
+
it "run command successfuly" do
|
12
|
+
code = run_ssh 'echo $USER'
|
13
|
+
expect(collected).to eq "#{user}\n"
|
14
|
+
expect(code).to eq 0
|
15
|
+
end
|
16
|
+
|
17
|
+
it "run command with error" do
|
18
|
+
code = run_ssh 'false'
|
19
|
+
expect(collected).to eq ""
|
20
|
+
expect(code).to eq 1
|
21
|
+
end
|
22
|
+
|
23
|
+
it "run command with env successfuly" do
|
24
|
+
code = run_ssh({'FOO' => "BAR"}, 'echo $FOO')
|
25
|
+
expect(collected).to match(re "FAILED: couldn't execute command (ssh.channel.env)")
|
26
|
+
expect(code).to eq 0
|
27
|
+
end
|
28
|
+
|
29
|
+
context "timeout" do
|
30
|
+
it 'run command with timeout' do
|
31
|
+
expect{
|
32
|
+
run_ssh('echo $USER; sleep 0.5', timeout: 0.2)
|
33
|
+
}.to raise_error(Evrone::Common::Spawn::TimeoutError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'run command with timeout successfuly' do
|
37
|
+
code = run_ssh('echo $USER; sleep 0.2', timeout: 0.5)
|
38
|
+
expect(collected).to eq "#{user}\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_ssh('sleep 0.5', read_timeout: 0.2)
|
47
|
+
}.to raise_error(Evrone::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_ssh('sleep 0.1 ; echo $USER ; sleep 0.5', read_timeout: 0.3)
|
54
|
+
}.to raise_error(Evrone::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_ssh('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_ssh('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_ssh("echo $USER; kill -9 $$")
|
73
|
+
expect(collected).to eq "#{user}\n"
|
74
|
+
expect(code).to eq(-1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'run and interupt process' do
|
78
|
+
code = run_ssh("echo $USER; kill -9 $$")
|
79
|
+
expect(collected).to eq "#{user}\n"
|
80
|
+
expect(code).to eq(-1)
|
81
|
+
end
|
82
|
+
|
83
|
+
def open_ssh(&block)
|
84
|
+
described_class.open(host, user, password: pass, verbose: 2, &block)
|
85
|
+
end
|
86
|
+
|
87
|
+
def re(s)
|
88
|
+
Regexp.escape s
|
89
|
+
end
|
90
|
+
|
91
|
+
def run_ssh(*args)
|
92
|
+
timeout do
|
93
|
+
open_ssh do |ssh|
|
94
|
+
ssh.spawn(*args) do |s|
|
95
|
+
collected << s
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def timeout
|
102
|
+
Timeout.timeout(10) do
|
103
|
+
yield
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Evrone::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(:ssh) { nil }
|
17
|
+
it "should be" do
|
18
|
+
subject.open_ssh(ENV['SSH_HOST'], ENV['SSH_USER'], password: ENV['SSH_PASS']) do |ssh|
|
19
|
+
expect(ssh.spawn 'true').to eq 0
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: evrone-common-spawn
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dmitry Galinsky
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-08-04 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 system, ssh processes, capturing output in realtime,\nallow to
|
84
|
+
set temeouts and read timeouts "
|
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
|
+
- evrone-common-spawn.gemspec
|
99
|
+
- lib/evrone/common/spawn.rb
|
100
|
+
- lib/evrone/common/spawn/error.rb
|
101
|
+
- lib/evrone/common/spawn/process.rb
|
102
|
+
- lib/evrone/common/spawn/read_timeout.rb
|
103
|
+
- lib/evrone/common/spawn/ssh.rb
|
104
|
+
- lib/evrone/common/spawn/timeout.rb
|
105
|
+
- lib/evrone/common/spawn/version.rb
|
106
|
+
- spec/lib/spawn/process_spec.rb
|
107
|
+
- spec/lib/spawn/read_timeout_spec.rb
|
108
|
+
- spec/lib/spawn/ssh_spec.rb
|
109
|
+
- spec/lib/spawn_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
homepage: https://github.com/evrone/evrone-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.0.2
|
132
|
+
signing_key:
|
133
|
+
specification_version: 4
|
134
|
+
summary: This gem helps to spawn system, ssh processes, capturing output in realtime,
|
135
|
+
allow to set temeouts and read timeouts
|
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
|