outoftime-shell_elf 0.9.0
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 +116 -0
- data/Rakefile +7 -0
- data/VERSION.yml +4 -0
- data/bin/shell-elf +10 -0
- data/lib/shell_elf/batch.rb +41 -0
- data/lib/shell_elf/job.rb +32 -0
- data/lib/shell_elf/runner.rb +61 -0
- data/lib/shell_elf.rb +18 -0
- data/script/shell-elf-listener +51 -0
- data/spec/services/http_service.rb +9 -0
- data/spec/shell_elf_spec.rb +139 -0
- data/spec/spec_helper.rb +77 -0
- data/tasks/gemspec.rake +22 -0
- data/tasks/spec.rake +36 -0
- metadata +138 -0
data/README.rdoc
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
= Shell Elf
|
2
|
+
|
3
|
+
http://github.com/outoftime/shell_elf
|
4
|
+
|
5
|
+
Shell Elf is a small, standalone daemon whose sole purpose is to read shell
|
6
|
+
commands out of a Starling queue, execute them, and optionally POST back to an
|
7
|
+
HTTP service on success or failure. Shell Elf is distributed as a RubyGem, but
|
8
|
+
is not meant to be included as a library; rather, it contains the standalone
|
9
|
+
`shell-elf` executable, which starts and stops the daemon.
|
10
|
+
|
11
|
+
Features
|
12
|
+
|
13
|
+
* Send a single shell command or a batch of commands to be executed serially
|
14
|
+
* Specify postback URLs for success and failure, so that your application can
|
15
|
+
be notified when batch has completed.
|
16
|
+
* Gracefully catch TERM and INT signals, either finishing the current batch or
|
17
|
+
requeueing it for later processing before exiting.
|
18
|
+
|
19
|
+
== Usage
|
20
|
+
|
21
|
+
=== Installation
|
22
|
+
|
23
|
+
sudo gem install outoftime-shell_elf --source=gems.github.com
|
24
|
+
|
25
|
+
=== Starting the daemon
|
26
|
+
|
27
|
+
shell-elf start -- [options]
|
28
|
+
|
29
|
+
The following options are available; none are required:
|
30
|
+
|
31
|
+
[-q/--queue] name of the Starling queue to read from (default: shell_elf)
|
32
|
+
[-h/--host] hostname at which the Starling daemon is running (default: localhost)
|
33
|
+
[-p/--port] port on which the Starling daemon is listening (default: 22122)
|
34
|
+
[--log-file] full path to a log file (default: no logging)
|
35
|
+
[--log-level] level at which to log (default: WARN)
|
36
|
+
|
37
|
+
=== Stopping the daemon
|
38
|
+
|
39
|
+
shell-elf stop
|
40
|
+
|
41
|
+
=== Sending commands from your application
|
42
|
+
|
43
|
+
require 'rubygems'
|
44
|
+
gem 'starling-starling'
|
45
|
+
require 'starling'
|
46
|
+
|
47
|
+
starling = Starling.new('localhost:22122')
|
48
|
+
|
49
|
+
# send the single command `touch /tmp/hey`
|
50
|
+
starling.set('shell_elf', :command => ['touch', '/tmp/hey'])
|
51
|
+
|
52
|
+
# send the two commands `touch /tmp/one` followed by `touch /tmp/2`
|
53
|
+
starling.set('shell_elf', :commands => [['touch', '/tmp/one'], ['touch', '/tmp/two']])
|
54
|
+
|
55
|
+
# send a command with success/failure postbacks
|
56
|
+
starling.set('shell_elf', :command => ['touch', '/tmp/hey'],
|
57
|
+
:options => { :success => { 'http://localhost:3000/command/success' },
|
58
|
+
{ :failure => { 'http://localhost:3000/command/failure' }})
|
59
|
+
|
60
|
+
# send a command with instructions to requeue if the daemon is interrupted
|
61
|
+
starling.set('shell_elf', :command => ['sleep', '120'],
|
62
|
+
:options => { :on_interrupt => :requeue })
|
63
|
+
|
64
|
+
=== A note about batches and postbacks
|
65
|
+
|
66
|
+
If the postbacks are specified (you can specify neither, either, or both),
|
67
|
+
ShellElf will send an HTTP POST request to the specified URL. The batch is
|
68
|
+
considered successful if all of the commands exit with a status code of 0. If
|
69
|
+
one of the commands in the batch exits with a non-zero status code, *the
|
70
|
+
rest of the commands in the batch are not run*, and the failure postback will
|
71
|
+
be sent. ShellElf waits until the postback request completes before retrieving
|
72
|
+
the next batch from the queue.
|
73
|
+
|
74
|
+
== The ShellElf Client
|
75
|
+
|
76
|
+
...doesn't exist. I'm a big fan of libraries presenting intuitive APIs to the
|
77
|
+
application layer, but in this case a general-purpose client would be so
|
78
|
+
trivial that it doesn't seem worth writing one. Further, I assume that in the
|
79
|
+
general use case, applications will want to build task-specific APIs that wrap
|
80
|
+
the task of shelling out via Starling. Having a further layer of abstraction
|
81
|
+
doesn't seem to add much value.
|
82
|
+
|
83
|
+
== Dependencies
|
84
|
+
|
85
|
+
* escape
|
86
|
+
* starling-starling
|
87
|
+
* daemons
|
88
|
+
* choice
|
89
|
+
|
90
|
+
== Contact and Futher Reading
|
91
|
+
|
92
|
+
Contact: Mat Brown <mat@patch.com>
|
93
|
+
Further Reading: http://outofti.me/tagged/shell_elf
|
94
|
+
|
95
|
+
== The MIT License
|
96
|
+
|
97
|
+
Copyright (c) 2009 Mat Brown
|
98
|
+
|
99
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
100
|
+
of this software and associated documentation files (the "Software"), to deal
|
101
|
+
in the Software without restriction, including without limitation the rights
|
102
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
103
|
+
copies of the Software, and to permit persons to whom the Software is
|
104
|
+
furnished to do so, subject to the following conditions:
|
105
|
+
|
106
|
+
The above copyright notice and this permission notice shall be included in
|
107
|
+
all copies or substantial portions of the Software.
|
108
|
+
|
109
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
110
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
111
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
112
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
113
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
114
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
115
|
+
THE SOFTWARE.
|
116
|
+
|
data/Rakefile
ADDED
data/VERSION.yml
ADDED
data/bin/shell-elf
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module ShellElf
|
2
|
+
class Batch
|
3
|
+
class <<self
|
4
|
+
def execute(params)
|
5
|
+
new(params).execute
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(params)
|
10
|
+
options = params[:options] || {}
|
11
|
+
@commands =
|
12
|
+
if params[:command]
|
13
|
+
[params[:command]]
|
14
|
+
elsif params[:commands]
|
15
|
+
params[:commands]
|
16
|
+
else
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
@success_postback = options.delete(:success)
|
20
|
+
@failure_postback = options.delete(:failure)
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
return self if @commands.empty?
|
25
|
+
if @commands.all? { |command| Job.execute(command).success? }
|
26
|
+
if @success_postback
|
27
|
+
ShellElf.logger.debug("Posting back to #{@success_postback}")
|
28
|
+
Net::HTTP.post_form(URI.parse(@success_postback), {})
|
29
|
+
else
|
30
|
+
ShellElf.logger.debug("No success postback given")
|
31
|
+
end
|
32
|
+
elsif @failure_postback
|
33
|
+
ShellElf.logger.debug("Posting back to #{@failure_postback}")
|
34
|
+
Net::HTTP.post_form(URI.parse(@failure_postback), {})
|
35
|
+
else
|
36
|
+
ShellElf.logger.debug("No failure postback given")
|
37
|
+
end
|
38
|
+
self
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module ShellElf
|
2
|
+
class Job
|
3
|
+
class <<self
|
4
|
+
def execute(command)
|
5
|
+
new(command).execute
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(command)
|
10
|
+
@command = command
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
escaped_command = Escape.shell_command(@command)
|
15
|
+
ShellElf.logger.info(escaped_command)
|
16
|
+
fork do
|
17
|
+
# this way we can detach from the shell session
|
18
|
+
# so the child process doesn't receive SIGINTs
|
19
|
+
Process.setsid
|
20
|
+
exec(escaped_command)
|
21
|
+
end
|
22
|
+
Process.wait(-1)
|
23
|
+
@success = $? == 0
|
24
|
+
ShellElf.logger.debug(@success ? 'success' : 'failed')
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def success?
|
29
|
+
@success
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module ShellElf
|
2
|
+
class Runner
|
3
|
+
class <<self
|
4
|
+
private :new
|
5
|
+
|
6
|
+
def run(options)
|
7
|
+
runner = new(options)
|
8
|
+
signal_handler = proc do
|
9
|
+
ShellElf.logger.info("Received signal - gracefully exiting...")
|
10
|
+
exit if runner.interrupted!
|
11
|
+
end
|
12
|
+
trap('INT', &signal_handler)
|
13
|
+
trap('TERM', &signal_handler)
|
14
|
+
runner.run
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(options)
|
19
|
+
@options = options
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
until @interrupted
|
24
|
+
if @current = starling.fetch(@options[:queue])
|
25
|
+
ShellElf.logger.debug("RUNNER: #{@current.inspect}")
|
26
|
+
ShellElf::Batch.execute(@current)
|
27
|
+
else
|
28
|
+
sleep Starling::WAIT_TIME
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def interrupted!
|
34
|
+
# return true unless @current
|
35
|
+
if @current && @current[:options] && @current[:options][:on_interrupt] == :requeue
|
36
|
+
starling.set(@options[:queue], @current)
|
37
|
+
true
|
38
|
+
else
|
39
|
+
@interrupted = true
|
40
|
+
false
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def starling(delay = nil)
|
47
|
+
begin
|
48
|
+
sleep(delay) if delay
|
49
|
+
@queue ||= Starling.new("#{@options[:host]}:#{@options[:port]}")
|
50
|
+
@queue.stats
|
51
|
+
@queue
|
52
|
+
rescue MemCache::MemCacheError => e
|
53
|
+
ShellElf.logger.error("Unable to connect to Starling: #{e.message}")
|
54
|
+
@queue = nil
|
55
|
+
delay ||= 0.125
|
56
|
+
delay = delay *= 2 if delay < 1
|
57
|
+
retry
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/shell_elf.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
gem 'escape'
|
2
|
+
require 'net/http'
|
3
|
+
require 'escape'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
module ShellElf
|
7
|
+
autoload :Job, File.expand_path(File.join(File.dirname(__FILE__), 'shell_elf', 'job.rb'))
|
8
|
+
autoload :Batch, File.expand_path(File.join(File.dirname(__FILE__), 'shell_elf', 'batch.rb'))
|
9
|
+
autoload :Runner, File.expand_path(File.join(File.dirname(__FILE__), 'shell_elf', 'runner.rb'))
|
10
|
+
|
11
|
+
class <<self
|
12
|
+
attr_writer :logger
|
13
|
+
|
14
|
+
def logger
|
15
|
+
@logger ||= Logger.new(nil) # stub logger
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
gem 'starling-starling'
|
5
|
+
gem 'choice'
|
6
|
+
require 'starling'
|
7
|
+
require 'choice'
|
8
|
+
require File.join(File.dirname(File.dirname(__FILE__)), 'lib', 'shell_elf')
|
9
|
+
|
10
|
+
Choice.options do
|
11
|
+
option :queue do
|
12
|
+
short '-q'
|
13
|
+
long '--queue=QUEUE'
|
14
|
+
desc 'Name of starling queue to process'
|
15
|
+
default 'shell_elf'
|
16
|
+
end
|
17
|
+
|
18
|
+
option :host do
|
19
|
+
short '-h'
|
20
|
+
long '--host=HOST'
|
21
|
+
desc 'Host at which to connect to Starling'
|
22
|
+
default '127.0.0.1'
|
23
|
+
end
|
24
|
+
|
25
|
+
option :port do
|
26
|
+
short '-p'
|
27
|
+
long '--port=PORT'
|
28
|
+
desc 'Port on which to connect to Starling'
|
29
|
+
default '22122'
|
30
|
+
end
|
31
|
+
|
32
|
+
option :log_file do
|
33
|
+
long '--log-file=LOG_FILE'
|
34
|
+
desc 'File for logging output'
|
35
|
+
end
|
36
|
+
|
37
|
+
option :log_level do
|
38
|
+
long '--log-level=LOG_LEVEL'
|
39
|
+
desc 'Log level'
|
40
|
+
valid Logger::SEV_LABEL
|
41
|
+
default 'WARN'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
ShellElf.logger = Logger.new(Choice.choices[:log_file])
|
46
|
+
ShellElf.logger.progname = "shell_elf[#{Choice.choices[:queue]}]"
|
47
|
+
ShellElf.logger.level = Logger.const_get(Choice.choices[:log_level])
|
48
|
+
|
49
|
+
ShellElf.logger.info("Starting ShellElf...")
|
50
|
+
ShellElf::Runner.run(Choice.choices)
|
51
|
+
ShellElf.logger.info("Exiting...")
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe 'ShellElf' do
|
4
|
+
include SpecHelper
|
5
|
+
|
6
|
+
shared_examples_for 'successful batch' do
|
7
|
+
it 'should postback to success URL' do
|
8
|
+
File.exist?(sandbox('success')).should be_true
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should not postback to failure URL' do
|
12
|
+
File.exist?(sandbox('failure')).should be_false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
shared_examples_for 'failed batch' do
|
17
|
+
it 'should postback to failure URL' do
|
18
|
+
File.exist?(sandbox('failure')).should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should not postback to success URL' do
|
22
|
+
File.exist?(sandbox('success')).should be_false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should run a single command' do
|
27
|
+
starling_send_and_wait(:command => ['touch', sandbox('test')])
|
28
|
+
File.exist?(sandbox('test')).should be_true
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should run multiple commands' do
|
32
|
+
starling_send_and_wait(:commands => [['touch', sandbox('first')], ['touch', sandbox('second')]])
|
33
|
+
%w(first second).all? { |f| File.exist?(sandbox(f)) }.should be_true
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'on success with single command' do
|
37
|
+
before :each do
|
38
|
+
starling_send_and_wait(:command => ['true'], :options => { :success => http_touch_url('success'), :failure => http_touch_url('failure') })
|
39
|
+
end
|
40
|
+
|
41
|
+
it_should_behave_like 'successful batch'
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'on failure with single command' do
|
45
|
+
before :each do
|
46
|
+
starling_send_and_wait(:command => ['false'], :options => { :success => http_touch_url('success'), :failure => http_touch_url('failure') })
|
47
|
+
end
|
48
|
+
|
49
|
+
it_should_behave_like 'failed batch'
|
50
|
+
end
|
51
|
+
|
52
|
+
describe 'with failed command followed by successful command' do
|
53
|
+
before :each do
|
54
|
+
starling_send_and_wait(:commands => [['false'], ['touch', sandbox('never_get_here')]], :options => { :success => http_touch_url('success'), :failure => http_touch_url('failure') })
|
55
|
+
end
|
56
|
+
|
57
|
+
it_should_behave_like 'failed batch'
|
58
|
+
|
59
|
+
it 'should not run successful command' do
|
60
|
+
File.exist?(sandbox('never_get_here')).should be_false
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'with successful command followed by failed command' do
|
65
|
+
before :each do
|
66
|
+
starling_send_and_wait(:commands => [['touch', sandbox('first')], ['false']], :options => { :success => http_touch_url('success'), :failure => http_touch_url('failure') })
|
67
|
+
end
|
68
|
+
|
69
|
+
it_should_behave_like 'failed batch'
|
70
|
+
|
71
|
+
it 'should run successful command' do
|
72
|
+
File.exist?(sandbox('first')).should be_true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'with bogus input' do
|
77
|
+
before :each do
|
78
|
+
starling_send('bogus')
|
79
|
+
starling_send_and_wait(:command => ['touch', sandbox('success')])
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'should continue running' do
|
83
|
+
File.exist?(sandbox('success')).should be_true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe 'with empty commands' do
|
88
|
+
before :each do
|
89
|
+
starling_send(:commands => [], :success => http_touch_url('success'), :failure => http_touch_url('failure'))
|
90
|
+
starling_send_and_wait(:command => ['touch', sandbox('after')])
|
91
|
+
end
|
92
|
+
|
93
|
+
%w(success failure).each do |postback|
|
94
|
+
it "should not send #{postback} postback" do
|
95
|
+
File.exist?(sandbox(postback)).should be_false
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should continue running' do
|
100
|
+
File.exist?(sandbox('after')).should be_true
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
%w(TERM INT).each do |signal|
|
105
|
+
describe "when SIG#{signal} sent" do
|
106
|
+
before :each do
|
107
|
+
starling_wait
|
108
|
+
starling_send(:commands => [['kill', '-s', signal, shell_elf_pid.to_s], ['touch', sandbox('done')]])
|
109
|
+
starling_send(:command => ['touch', sandbox('never_get_here')])
|
110
|
+
wait_for_exit
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should trap the signal and complete current command before exiting' do
|
114
|
+
File.exist?(sandbox('done')).should be_true
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should not run any further commands' do
|
118
|
+
File.exist?(sandbox('never_get_here')).should be_false
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
describe "when SIG#{signal} sent with requeue option" do
|
123
|
+
before :each do
|
124
|
+
@params = { :commands => [['kill', '-s', signal, shell_elf_pid.to_s], ['touch', sandbox('requeued')]], :options => { :on_interrupt => :requeue }}
|
125
|
+
starling_wait
|
126
|
+
starling_send(@params)
|
127
|
+
wait_for_exit
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'should trap the signal and requeue the job' do
|
131
|
+
@starling.fetch('shell_elf_test').should == @params
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should not finish the running command' do
|
135
|
+
File.exist?(sandbox('requeued')).should be_false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rspec'
|
3
|
+
gem 'starling-starling'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'tmpdir'
|
6
|
+
require 'spec'
|
7
|
+
require 'starling'
|
8
|
+
require 'ruby-debug'
|
9
|
+
|
10
|
+
Spec::Runner.configure do |config|
|
11
|
+
config.before(:all) do
|
12
|
+
@starling = Starling.new('localhost:73787')
|
13
|
+
begin
|
14
|
+
@starling.stats
|
15
|
+
rescue
|
16
|
+
abort('Starling is not responding! Run rake spec:environment to start background services.')
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
config.before(:each) do
|
21
|
+
@starling.delete('shell_elf_test')
|
22
|
+
for entry in Dir.glob(File.join(Dir.tmpdir, 'shell_elf', 'sandbox', '*'))
|
23
|
+
if File.file?(entry)
|
24
|
+
FileUtils.rm_r(File.expand_path(entry))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
config.after(:each) do
|
30
|
+
@starling.delete('shell_elf_test')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module SpecHelper
|
35
|
+
def sandbox(*path)
|
36
|
+
File.expand_path(File.join(Dir.tmpdir, 'shell_elf', 'sandbox', path))
|
37
|
+
end
|
38
|
+
|
39
|
+
def starling_send_and_wait(params)
|
40
|
+
starling_send(params)
|
41
|
+
starling_wait
|
42
|
+
end
|
43
|
+
|
44
|
+
def starling_send(params)
|
45
|
+
@starling.set('shell_elf_test', params)
|
46
|
+
end
|
47
|
+
|
48
|
+
def starling_wait
|
49
|
+
starling_send(:command => ['true'])
|
50
|
+
100.times do |i|
|
51
|
+
return if @starling.sizeof('shell_elf_test') == 0
|
52
|
+
sleep 0.01
|
53
|
+
end
|
54
|
+
raise 'Timed out waiting for Starling queue to clear.'
|
55
|
+
end
|
56
|
+
|
57
|
+
def http_touch_url(filename)
|
58
|
+
"http://localhost:7397/touch/#{filename}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def wait_for_exit(timeout = 1)
|
62
|
+
pid = shell_elf_pid
|
63
|
+
(timeout * 100).times do
|
64
|
+
begin
|
65
|
+
Process.kill(0, pid)
|
66
|
+
sleep 0.01
|
67
|
+
rescue Errno::ESRCH
|
68
|
+
return
|
69
|
+
end
|
70
|
+
end
|
71
|
+
raise "Timed out waiting for pid #{pid} to end!"
|
72
|
+
end
|
73
|
+
|
74
|
+
def shell_elf_pid
|
75
|
+
File.open(File.join(Dir.tmpdir, 'shell_elf', 'log', 'shell-elf.pid')) { |f| f.read.to_i }
|
76
|
+
end
|
77
|
+
end
|
data/tasks/gemspec.rake
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
begin
|
2
|
+
gem 'technicalpickles-jeweler', '~> 0.8.1'
|
3
|
+
require 'jeweler'
|
4
|
+
Jeweler::Tasks.new do |s|
|
5
|
+
s.name = 'shell_elf'
|
6
|
+
s.executables = 'shell-elf'
|
7
|
+
s.summary = 'Daemon which executes shell commands read out of a Starling queue and optionally posts back to an HTTP server on success/failure'
|
8
|
+
s.email = 'mat@patch.com'
|
9
|
+
s.homepage = 'http://github.com/outoftime/sunspot'
|
10
|
+
s.description = 'Daemon which executes shell commands read out of a Starling queue and optionally posts back to an HTTP server on success/failure'
|
11
|
+
s.authors = ['Mat Brown']
|
12
|
+
s.files = FileList['[A-Z]*', '{bin,lib,spec,tasks,script}/**/*']
|
13
|
+
s.has_rdoc = false
|
14
|
+
s.add_dependency 'escape', '>= 0.0.4'
|
15
|
+
s.add_dependency 'starling-starling', '~> 0.9'
|
16
|
+
s.add_dependency 'daemons', '~> 1.0'
|
17
|
+
s.add_dependency 'choice', '>= 0.1'
|
18
|
+
s.add_development_dependency 'rspec', '~> 1.1'
|
19
|
+
s.add_development_dependency 'ruby-debug', '~> 0.10'
|
20
|
+
s.add_development_dependency 'sinatra', '>= 0.9'
|
21
|
+
end
|
22
|
+
end
|
data/tasks/spec.rake
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'tmpdir'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
desc 'Run spec suite'
|
6
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
7
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
8
|
+
end
|
9
|
+
|
10
|
+
namespace :spec do
|
11
|
+
desc "Start spec environment processes"
|
12
|
+
task :environment do
|
13
|
+
base_dir = File.expand_path(File.dirname(File.dirname(__FILE__)))
|
14
|
+
spec_dir = File.join(base_dir, 'spec')
|
15
|
+
tmp_dir = File.join(Dir.tmpdir, 'shell_elf')
|
16
|
+
FileUtils.mkdir_p(File.join(tmp_dir, 'log'))
|
17
|
+
FileUtils.mkdir_p(File.join(tmp_dir, 'sandbox'))
|
18
|
+
running = true
|
19
|
+
trap('INT') { running = false }
|
20
|
+
trap('TERM') { running = false }
|
21
|
+
fork do
|
22
|
+
exec("starling -P #{File.join(tmp_dir, 'log', 'starling.pid')} -p 73787")
|
23
|
+
end
|
24
|
+
fork do
|
25
|
+
exec("ruby #{spec_dir}/services/http_service.rb -p 7397")
|
26
|
+
end
|
27
|
+
while running
|
28
|
+
shell_elf_pid = fork do
|
29
|
+
exec("#{File.join(base_dir, 'bin', 'shell-elf')} run -- -p 73787 -q shell_elf_test --log-file=#{File.join(tmp_dir, 'log', 'shell_elf.out')} --log-level=DEBUG")
|
30
|
+
end
|
31
|
+
File.open(File.join(tmp_dir, 'log', 'shell-elf.pid'), 'w') { |f| f << shell_elf_pid }
|
32
|
+
Process.waitpid(shell_elf_pid)
|
33
|
+
end
|
34
|
+
Process.waitall
|
35
|
+
end
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: outoftime-shell_elf
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mat Brown
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-04-01 00:00:00 -07:00
|
13
|
+
default_executable: shell-elf
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: escape
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: starling-starling
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0.9"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: daemons
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "1.0"
|
44
|
+
version:
|
45
|
+
- !ruby/object:Gem::Dependency
|
46
|
+
name: choice
|
47
|
+
type: :runtime
|
48
|
+
version_requirement:
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: "0.1"
|
54
|
+
version:
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
type: :development
|
58
|
+
version_requirement:
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ~>
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: "1.1"
|
64
|
+
version:
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: ruby-debug
|
67
|
+
type: :development
|
68
|
+
version_requirement:
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ~>
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: "0.10"
|
74
|
+
version:
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: sinatra
|
77
|
+
type: :development
|
78
|
+
version_requirement:
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: "0.9"
|
84
|
+
version:
|
85
|
+
description: Daemon which executes shell commands read out of a Starling queue and optionally posts back to an HTTP server on success/failure
|
86
|
+
email: mat@patch.com
|
87
|
+
executables:
|
88
|
+
- shell-elf
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files: []
|
92
|
+
|
93
|
+
files:
|
94
|
+
- Rakefile
|
95
|
+
- README.rdoc
|
96
|
+
- VERSION.yml
|
97
|
+
- bin/shell-elf
|
98
|
+
- lib/shell_elf
|
99
|
+
- lib/shell_elf/runner.rb
|
100
|
+
- lib/shell_elf/batch.rb
|
101
|
+
- lib/shell_elf/job.rb
|
102
|
+
- lib/shell_elf.rb
|
103
|
+
- spec/spec_helper.rb
|
104
|
+
- spec/services
|
105
|
+
- spec/services/http_service.rb
|
106
|
+
- spec/shell_elf_spec.rb
|
107
|
+
- tasks/gemspec.rake
|
108
|
+
- tasks/spec.rake
|
109
|
+
- script/shell-elf-listener
|
110
|
+
has_rdoc: true
|
111
|
+
homepage: http://github.com/outoftime/sunspot
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options:
|
114
|
+
- --inline-source
|
115
|
+
- --charset=UTF-8
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: "0"
|
123
|
+
version:
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
125
|
+
requirements:
|
126
|
+
- - ">="
|
127
|
+
- !ruby/object:Gem::Version
|
128
|
+
version: "0"
|
129
|
+
version:
|
130
|
+
requirements: []
|
131
|
+
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.2.0
|
134
|
+
signing_key:
|
135
|
+
specification_version: 2
|
136
|
+
summary: Daemon which executes shell commands read out of a Starling queue and optionally posts back to an HTTP server on success/failure
|
137
|
+
test_files: []
|
138
|
+
|