rake_server 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.
- data/README.rdoc +25 -0
- data/Rakefile +1 -0
- data/TODO +2 -0
- data/VERSION +1 -0
- data/bin/rake-client +5 -0
- data/bin/rake-server +27 -0
- data/lib/rake_server/client.rb +58 -0
- data/lib/rake_server/server.rb +138 -0
- data/lib/rake_server.rb +10 -0
- data/tasks/gemspec.rake +18 -0
- data/test/Rakefile +38 -0
- data/tmp/.gitignore +1 -0
- metadata +80 -0
data/README.rdoc
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
= RakeServer
|
2
|
+
|
3
|
+
=== What is it?
|
4
|
+
|
5
|
+
RakeServer is a lightweight client/server architecture for running Rake tasks.
|
6
|
+
The server is a long-running process that loads tasks from a Rakefile and waits
|
7
|
+
for requests to execute tasks. Each time a task is requested, the server forks,
|
8
|
+
runs the task, and streams the output from the task back to the client.
|
9
|
+
|
10
|
+
When the server is started, it can be given tasks to eager-run; this is
|
11
|
+
mainly useful for loading application code into the pre-fork environment.
|
12
|
+
|
13
|
+
=== What's the point?
|
14
|
+
|
15
|
+
The main intended use case for RakeServer is to provide a means for production
|
16
|
+
deployments to run Rake tasks in the background (e.g. via a cron) without
|
17
|
+
needing to load the application environment from scratch each time a task is
|
18
|
+
executed. It may also be useful for remote invocation of Rake tasks, but that is
|
19
|
+
not the main goal.
|
20
|
+
|
21
|
+
=== How do I use it?
|
22
|
+
|
23
|
+
$ cd /path/to/my/rails/app
|
24
|
+
$ rake-server environment
|
25
|
+
$ rake-client db:migrate
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
FileList['tasks/**/*.rake'].each { |tasks| load(tasks) }
|
data/TODO
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/rake-client
ADDED
data/bin/rake-server
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require File.join(File.dirname(__FILE__), '..', 'lib', 'rake_server')
|
4
|
+
|
5
|
+
options = {}
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = "Usage: rake-server [options] [startup-tasks] (start|stop|run|restart)"
|
8
|
+
|
9
|
+
opts.on("-q", "--quiet", "Don't output anything") { |q| options[:quiet] = q }
|
10
|
+
opts.on("-h", "--host [HOST]", "Host to listen on") { |h| options[:host] = h }
|
11
|
+
opts.on("-p", "--port [PORT]", "Port to listen on") { |p| options[:port] = p }
|
12
|
+
end.parse!
|
13
|
+
|
14
|
+
command = ARGV.shift
|
15
|
+
case command
|
16
|
+
when 'start'
|
17
|
+
RakeServer::Server.start(ARGV, options)
|
18
|
+
when 'run'
|
19
|
+
RakeServer::Server.run(ARGV, options)
|
20
|
+
when 'stop'
|
21
|
+
RakeServer::Server.stop(options)
|
22
|
+
when 'restart'
|
23
|
+
RakeServer::Server.stop(options)
|
24
|
+
RakeServer::Server.start(options)
|
25
|
+
else
|
26
|
+
abort("Unknown command #{command.inspect}")
|
27
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
begin
|
2
|
+
require 'eventmachine'
|
3
|
+
rescue LoadError => e
|
4
|
+
if require('rubygems') then retry
|
5
|
+
else raise(e)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module RakeServer
|
10
|
+
class Client < EventMachine::Connection
|
11
|
+
include EventMachine::Protocols::ObjectProtocol
|
12
|
+
|
13
|
+
class <<self
|
14
|
+
def run(args, options = {})
|
15
|
+
options = DEFAULT_OPTIONS.merge(options)
|
16
|
+
EventMachine.run do
|
17
|
+
EventMachine.connect options[:host], options[:port], self, args
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
ENV_PATTERN = /^(\w+)=(.*)$/
|
23
|
+
|
24
|
+
def initialize(args)
|
25
|
+
begin
|
26
|
+
super()
|
27
|
+
@tasks, @env = [], {}
|
28
|
+
args.each do |arg|
|
29
|
+
if match = ENV_PATTERN.match(arg)
|
30
|
+
@env[match[1]] = match[2]
|
31
|
+
else
|
32
|
+
@tasks << arg
|
33
|
+
end
|
34
|
+
end
|
35
|
+
rescue => e
|
36
|
+
STDERR.puts(e.inspect)
|
37
|
+
STDERR.puts(e.backtrace)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def post_init
|
42
|
+
message = Message.new(@tasks, @env)
|
43
|
+
send_object(message)
|
44
|
+
end
|
45
|
+
|
46
|
+
def receive_object(data)
|
47
|
+
if data.nil?
|
48
|
+
unbind
|
49
|
+
else
|
50
|
+
puts(data)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def unbind
|
55
|
+
EventMachine.stop_event_loop
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
begin
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'rake'
|
4
|
+
rescue LoadError => e
|
5
|
+
if require('rubygems') then retry
|
6
|
+
else raise(e)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module RakeServer
|
11
|
+
class Server < EventMachine::Connection
|
12
|
+
include EventMachine::Protocols::ObjectProtocol
|
13
|
+
|
14
|
+
class <<self
|
15
|
+
def start(eager_tasks, options = {})
|
16
|
+
pid_file = File.join(pid_dir(options), "rake-server.pid")
|
17
|
+
pid = fork do
|
18
|
+
fork do
|
19
|
+
File.open(pid_file, 'w') { |f| f << Process.pid }
|
20
|
+
run(eager_tasks, options)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
Process.waitpid(pid)
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop(options = {})
|
27
|
+
pid_file = File.join(pid_dir(options), "rake-server.pid")
|
28
|
+
pid = IO.read(pid_file).to_i
|
29
|
+
Process.kill("TERM", pid)
|
30
|
+
FileUtils.rm(pid_file)
|
31
|
+
end
|
32
|
+
|
33
|
+
def run(eager_tasks, options = {})
|
34
|
+
options = DEFAULT_OPTIONS.merge(options)
|
35
|
+
EventMachine.run do
|
36
|
+
Rake.application.init
|
37
|
+
Rake.application.load_rakefile
|
38
|
+
eager_tasks.each { |task| Rake.application[task].invoke }
|
39
|
+
EventMachine.start_server(options[:host], options[:port], self)
|
40
|
+
unless options[:quiet]
|
41
|
+
puts "rake-server listening on #{options[:host]}:#{options[:port]}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def before_fork(&block)
|
47
|
+
@before_fork = block
|
48
|
+
end
|
49
|
+
|
50
|
+
def after_fork(&block)
|
51
|
+
@after_fork = block
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_before_fork
|
55
|
+
@before_fork.call if @before_fork
|
56
|
+
end
|
57
|
+
|
58
|
+
def run_after_fork
|
59
|
+
@after_fork.call if @after_fork
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def pid_dir(options)
|
65
|
+
pid_dir = options[:pid_dir] || File.join(Dir.pwd, 'tmp', 'pids')
|
66
|
+
unless File.directory?(pid_dir)
|
67
|
+
raise "PID dir #{pid_dir} does not exist -- can't daemonize"
|
68
|
+
end
|
69
|
+
pid_dir
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def receive_object(message)
|
74
|
+
begin
|
75
|
+
tasks = message.tasks.map { |task| Rake.application[task.to_sym] }
|
76
|
+
pid = fork_and_run_tasks(tasks, message.env || {})
|
77
|
+
rescue => e
|
78
|
+
send_object("ERR #{e.message}\n")
|
79
|
+
send_object(nil)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def fork_and_run_tasks(tasks, env)
|
86
|
+
input, output = IO.pipe
|
87
|
+
self.class.run_before_fork
|
88
|
+
pid = fork do
|
89
|
+
self.class.run_after_fork
|
90
|
+
env.each_pair do |key, value|
|
91
|
+
ENV[key] = value
|
92
|
+
end
|
93
|
+
input.close
|
94
|
+
STDOUT.reopen(output)
|
95
|
+
STDERR.reopen(output)
|
96
|
+
begin
|
97
|
+
tasks.each { |task| task.invoke }
|
98
|
+
rescue => e
|
99
|
+
STDERR.puts(e.message)
|
100
|
+
STDERR.puts(e.backtrace)
|
101
|
+
ensure
|
102
|
+
output.close
|
103
|
+
end
|
104
|
+
end
|
105
|
+
output.close
|
106
|
+
monitor_tasks(pid, input)
|
107
|
+
end
|
108
|
+
|
109
|
+
def monitor_tasks(pid, input)
|
110
|
+
EventMachine.defer(monitor(pid, input), done(input))
|
111
|
+
end
|
112
|
+
|
113
|
+
def monitor(pid, input)
|
114
|
+
proc do
|
115
|
+
begin
|
116
|
+
until Process.waitpid(pid, Process::WNOHANG)
|
117
|
+
begin
|
118
|
+
until input.eof? || (data = input.read_nonblock(4096)).empty?
|
119
|
+
send_object(data)
|
120
|
+
end
|
121
|
+
rescue Errno::EAGAIN
|
122
|
+
end
|
123
|
+
end
|
124
|
+
sleep(0.1)
|
125
|
+
rescue => e
|
126
|
+
STDERR.puts(e.inspect)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def done(input)
|
132
|
+
lambda do
|
133
|
+
send_object(input.read) until input.eof?
|
134
|
+
send_object(nil)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/rake_server.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module RakeServer
|
2
|
+
autoload :Server, File.join(File.dirname(__FILE__), 'rake_server', 'server')
|
3
|
+
autoload :Client, File.join(File.dirname(__FILE__), 'rake_server', 'client')
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = {
|
6
|
+
:host => '127.0.0.1',
|
7
|
+
:port => 7253
|
8
|
+
}
|
9
|
+
Message = Struct.new(:tasks, :env)
|
10
|
+
end
|
data/tasks/gemspec.rake
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "rake_server"
|
5
|
+
gemspec.summary = "Run rake tasks in a client-server architecture"
|
6
|
+
gemspec.description = <<DESC
|
7
|
+
RakeServer is a library which allows Rake tasks to be run using client requests
|
8
|
+
to a long-running rake server, which can eagerly load required code into memory
|
9
|
+
for fast task invocation.
|
10
|
+
DESC
|
11
|
+
gemspec.email = "mat@patch.com"
|
12
|
+
gemspec.homepage = "http://github.com/outoftime/rake_server"
|
13
|
+
gemspec.authors = ['Mat Brown', 'Cedric Howe']
|
14
|
+
end
|
15
|
+
Jeweler::GemcutterTasks.new
|
16
|
+
rescue LoadError
|
17
|
+
STDERR.puts("Jeweler not available. Install it with `gem install jeweler'")
|
18
|
+
end
|
data/test/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
task :init do
|
2
|
+
system('notify-send "starting up"')
|
3
|
+
end
|
4
|
+
|
5
|
+
task :shout do
|
6
|
+
system('notify-send "Hey!"')
|
7
|
+
end
|
8
|
+
|
9
|
+
task :pid do
|
10
|
+
system("notify-send #{Process.pid}")
|
11
|
+
end
|
12
|
+
|
13
|
+
task :relax do
|
14
|
+
sleep(10)
|
15
|
+
end
|
16
|
+
|
17
|
+
task :print do
|
18
|
+
puts ENV['MESSAGE']
|
19
|
+
end
|
20
|
+
|
21
|
+
task :hello do
|
22
|
+
puts "Hello, world!"
|
23
|
+
end
|
24
|
+
|
25
|
+
task :setup_server do
|
26
|
+
RakeServer::Server.before_fork do
|
27
|
+
`notify-send "#{Process.pid}"`
|
28
|
+
end
|
29
|
+
RakeServer::Server.after_fork do
|
30
|
+
`notify-send "#{Process.pid}"`
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
namespace :namespace do
|
35
|
+
task :shout do
|
36
|
+
system('notify-send "Namespace::Hey!"')
|
37
|
+
end
|
38
|
+
end
|
data/tmp/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
*
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rake_server
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Mat Brown
|
13
|
+
- Cedric Howe
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-04-01 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: |
|
23
|
+
RakeServer is a library which allows Rake tasks to be run using client requests
|
24
|
+
to a long-running rake server, which can eagerly load required code into memory
|
25
|
+
for fast task invocation.
|
26
|
+
|
27
|
+
email: mat@patch.com
|
28
|
+
executables:
|
29
|
+
- rake-client
|
30
|
+
- rake-server
|
31
|
+
extensions: []
|
32
|
+
|
33
|
+
extra_rdoc_files:
|
34
|
+
- README.rdoc
|
35
|
+
- TODO
|
36
|
+
files:
|
37
|
+
- README.rdoc
|
38
|
+
- Rakefile
|
39
|
+
- TODO
|
40
|
+
- VERSION
|
41
|
+
- bin/rake-client
|
42
|
+
- bin/rake-server
|
43
|
+
- lib/rake_server.rb
|
44
|
+
- lib/rake_server/client.rb
|
45
|
+
- lib/rake_server/server.rb
|
46
|
+
- tasks/gemspec.rake
|
47
|
+
- test/Rakefile
|
48
|
+
- tmp/.gitignore
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/outoftime/rake_server
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --charset=UTF-8
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project:
|
75
|
+
rubygems_version: 1.3.6
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Run rake tasks in a client-server architecture
|
79
|
+
test_files: []
|
80
|
+
|