daemon-kit 0.1.7.9 → 0.1.7.10
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/Configuration.txt +43 -0
- data/History.txt +8 -0
- data/Manifest.txt +14 -0
- data/README.rdoc +26 -7
- data/Rakefile +9 -2
- data/RuoteParticipants.txt +113 -0
- data/app_generators/daemon_kit/daemon_kit_generator.rb +5 -5
- data/daemon_generators/ruote/USAGE +5 -0
- data/daemon_generators/ruote/ruote_generator.rb +67 -0
- data/daemon_generators/ruote/templates/config/amqp.yml +30 -0
- data/daemon_generators/ruote/templates/config/initializers/ruote.rb +13 -0
- data/daemon_generators/ruote/templates/config/ruote.yml +23 -0
- data/daemon_generators/ruote/templates/lib/daemon.rb +4 -0
- data/daemon_generators/ruote/templates/lib/sample.rb +26 -0
- data/daemon_generators/ruote/templates/libexec/daemon.rb +33 -0
- data/lib/daemon_kit.rb +9 -5
- data/lib/daemon_kit/application.rb +20 -0
- data/lib/daemon_kit/arguments.rb +4 -0
- data/lib/daemon_kit/config.rb +44 -6
- data/lib/daemon_kit/exceptions.rb +8 -0
- data/lib/daemon_kit/initializer.rb +47 -1
- data/lib/daemon_kit/ruote_participants.rb +119 -0
- data/lib/daemon_kit/ruote_pseudo_participant.rb +68 -0
- data/lib/daemon_kit/ruote_workitem.rb +169 -0
- data/spec/argument_spec.rb +19 -0
- data/spec/config_spec.rb +8 -6
- data/spec/initializer_spec.rb +3 -9
- data/test/test_ruote_generator.rb +45 -0
- metadata +40 -12
@@ -0,0 +1,13 @@
|
|
1
|
+
begin
|
2
|
+
require 'json'
|
3
|
+
rescue LoadError
|
4
|
+
$stderr.puts "Missing json gem. Please run 'gem install json'"
|
5
|
+
exit 1
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'amqp'
|
10
|
+
require 'mq'
|
11
|
+
rescue LoadError
|
12
|
+
$stderr.puts "Missing amqp gem. Please run 'gem install amqp' if you wish to use the AMQP participant/listener pair in ruote"
|
13
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Sample configuration file for a remote participant daemon
|
2
|
+
|
3
|
+
# If your using the AMQP listener/participant pair in ruote, you only
|
4
|
+
# need to specify the names of the queues to subscribe to.
|
5
|
+
|
6
|
+
defaults: &defaults
|
7
|
+
amqp:
|
8
|
+
queues:
|
9
|
+
- work1
|
10
|
+
#- work2
|
11
|
+
#- work3
|
12
|
+
|
13
|
+
development:
|
14
|
+
<<: *defaults
|
15
|
+
|
16
|
+
test:
|
17
|
+
<<: *defaults
|
18
|
+
|
19
|
+
staging:
|
20
|
+
<<: *defaults
|
21
|
+
|
22
|
+
production:
|
23
|
+
<<: *defaults
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
|
3
|
+
# Sample pseudo participant
|
4
|
+
#
|
5
|
+
# See http://gist.github.com/144861 for a test engine
|
6
|
+
class Sample < DaemonKit::RuotePseudoParticipant
|
7
|
+
|
8
|
+
on_exception :dammit
|
9
|
+
|
10
|
+
on_complete do |workitem|
|
11
|
+
workitem['success'] = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def quote
|
15
|
+
workitem["quote"] = open("http://www.iheartquotes.com/api/v1/random").read
|
16
|
+
end
|
17
|
+
|
18
|
+
def err
|
19
|
+
raise ArgumentError, "Does not compute"
|
20
|
+
end
|
21
|
+
|
22
|
+
def dammit( exception )
|
23
|
+
workitem["error"] = exception.message
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# Generated remote participant for the ruote workflow engine
|
2
|
+
# (http://openwferu.rubyforge.org)
|
3
|
+
|
4
|
+
# Do your post daemonization configuration here
|
5
|
+
# At minimum you need just the first line (without the block), or a lot
|
6
|
+
# of strange things might start happening...
|
7
|
+
DaemonKit::Application.running! do |config|
|
8
|
+
# Trap signals with blocks or procs
|
9
|
+
# config.trap( 'INT' ) do
|
10
|
+
# # do something clever
|
11
|
+
# end
|
12
|
+
# config.trap( 'TERM', Proc.new { puts 'Going down' } )
|
13
|
+
end
|
14
|
+
|
15
|
+
# IMPORTANT CONFIGURATION NOTE
|
16
|
+
#
|
17
|
+
# Please review and update 'config/amqp.yml' accordingly if you wish to use
|
18
|
+
# AMQP as a transport mechanism for workitems sent between ruote and this
|
19
|
+
# daemon.
|
20
|
+
|
21
|
+
# Configuration of the remote participant shell
|
22
|
+
DaemonKit::RuoteParticipants.configure do |config|
|
23
|
+
# Use AMQP as a workitem transport mechanism
|
24
|
+
config.use :amqp
|
25
|
+
|
26
|
+
# Register your classes as pseudo-participants, with work being delegated
|
27
|
+
# according to the 'command' parameter passed in the process definition
|
28
|
+
config.register Sample
|
29
|
+
end
|
30
|
+
|
31
|
+
DaemonKit::RuoteParticipants.run do
|
32
|
+
# Place any additional daemon-specific code in here...
|
33
|
+
end
|
data/lib/daemon_kit.rb
CHANGED
@@ -4,12 +4,13 @@ require 'rubygems'
|
|
4
4
|
require 'eventmachine'
|
5
5
|
|
6
6
|
require File.dirname(__FILE__) + '/daemon_kit/core_ext'
|
7
|
+
require File.dirname(__FILE__) + '/daemon_kit/exceptions'
|
7
8
|
|
8
9
|
$:.unshift( File.dirname(__FILE__).to_absolute_path ) unless
|
9
10
|
$:.include?( File.dirname(__FILE__).to_absolute_path )
|
10
11
|
|
11
12
|
module DaemonKit
|
12
|
-
VERSION = '0.1.7.
|
13
|
+
VERSION = '0.1.7.10'
|
13
14
|
|
14
15
|
autoload :Initializer, 'daemon_kit/initializer'
|
15
16
|
autoload :Application, 'daemon_kit/application'
|
@@ -21,10 +22,13 @@ module DaemonKit
|
|
21
22
|
autoload :EM, 'daemon_kit/em'
|
22
23
|
autoload :Configurable, 'daemon_kit/core_ext/configurable'
|
23
24
|
|
24
|
-
autoload :Cron,
|
25
|
-
autoload :Jabber,
|
26
|
-
autoload :AMQP,
|
27
|
-
autoload :Nanite,
|
25
|
+
autoload :Cron, 'daemon_kit/cron'
|
26
|
+
autoload :Jabber, 'daemon_kit/jabber'
|
27
|
+
autoload :AMQP, 'daemon_kit/amqp'
|
28
|
+
autoload :Nanite, 'daemon_kit/nanite'
|
29
|
+
autoload :RuoteParticipants, 'daemon_kit/ruote_participants'
|
30
|
+
autoload :RuoteWorkitem, 'daemon_kit/ruote_workitem'
|
31
|
+
autoload :RuotePseudoParticipant, 'daemon_kit/ruote_pseudo_participant'
|
28
32
|
|
29
33
|
class << self
|
30
34
|
def logger
|
@@ -40,6 +40,7 @@ module DaemonKit
|
|
40
40
|
|
41
41
|
# Run our file properly
|
42
42
|
def start( file )
|
43
|
+
self.drop_privileges
|
43
44
|
self.daemonize
|
44
45
|
self.chroot
|
45
46
|
self.clean_fd
|
@@ -161,6 +162,25 @@ module DaemonKit
|
|
161
162
|
STDERR.reopen '/dev/null', 'a'
|
162
163
|
end
|
163
164
|
end
|
165
|
+
|
166
|
+
def drop_privileges
|
167
|
+
if DaemonKit.configuration.group
|
168
|
+
begin
|
169
|
+
group = Etc.getgrnam( DaemonKit.configuration.group )
|
170
|
+
Process::Sys.setgid( group.gid.to_i )
|
171
|
+
rescue => e
|
172
|
+
$stderr.puts "Caught exception while trying to drop group privileges: #{e.message}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
if DaemonKit.configuration.user
|
176
|
+
begin
|
177
|
+
user = Etc.getpwnam( DaemonKit.configuration.user )
|
178
|
+
Process::Sys.setuid( user.uid.to_i )
|
179
|
+
rescue => e
|
180
|
+
$stderr.puts "Caught exception while trying to drop user privileges: #{e.message}"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
164
184
|
end
|
165
185
|
|
166
186
|
end
|
data/lib/daemon_kit/arguments.rb
CHANGED
@@ -68,21 +68,25 @@ module DaemonKit
|
|
68
68
|
if argv[i] == "--config"
|
69
69
|
argv.delete_at( i )
|
70
70
|
configs << argv.delete_at(i)
|
71
|
+
next
|
71
72
|
end
|
72
73
|
|
73
74
|
if argv[i] == "-e" || argv[i] == "--env"
|
74
75
|
argv.delete_at( i )
|
75
76
|
configs << "environment=#{argv.delete_at(i)}"
|
77
|
+
next
|
76
78
|
end
|
77
79
|
|
78
80
|
if argv[i] == "-l" || argv[i] == "--log"
|
79
81
|
argv.delete_at( i )
|
80
82
|
configs << "log_path=#{argv.delete_at(i)}"
|
83
|
+
next
|
81
84
|
end
|
82
85
|
|
83
86
|
if argv[i] == "--pid"
|
84
87
|
argv.delete_at( i )
|
85
88
|
configs << "pid_file=#{argv.delete_at(i)}"
|
89
|
+
next
|
86
90
|
end
|
87
91
|
|
88
92
|
i += 1
|
data/lib/daemon_kit/config.rb
CHANGED
@@ -40,9 +40,9 @@ module DaemonKit
|
|
40
40
|
# Expects a hash, looks for DAEMON_ENV key
|
41
41
|
def initialize( config_data ) #:nodoc:
|
42
42
|
if config_data.has_key?( DAEMON_ENV )
|
43
|
-
|
43
|
+
self.data = config_data[ DAEMON_ENV ]
|
44
44
|
else
|
45
|
-
|
45
|
+
self.data = config_data
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
@@ -54,17 +54,55 @@ module DaemonKit
|
|
54
54
|
# Return the internal hash structure used, optionally symbolizing
|
55
55
|
# the first level of keys in the hash
|
56
56
|
def to_h( symbolize = false )
|
57
|
-
symbolize ? @data.
|
57
|
+
symbolize ? @data.symbolize_keys : @data
|
58
58
|
end
|
59
59
|
|
60
60
|
def method_missing( method_name, *args ) #:nodoc:
|
61
|
-
# don't match setters
|
62
61
|
unless method_name.to_s =~ /[\w_]+=$/
|
63
|
-
|
64
|
-
|
62
|
+
if @data.keys.include?( method_name.to_s )
|
63
|
+
return @data.send( method_name.to_s )
|
64
|
+
end
|
65
65
|
end
|
66
66
|
|
67
67
|
super
|
68
68
|
end
|
69
|
+
|
70
|
+
def data=( hash )
|
71
|
+
@data = hash
|
72
|
+
class << @data
|
73
|
+
def symbolize_keys( hash = self )
|
74
|
+
hash.inject({}) { |result, (key, value)|
|
75
|
+
new_key = case key
|
76
|
+
when String then key.to_sym
|
77
|
+
else key
|
78
|
+
end
|
79
|
+
new_value = case value
|
80
|
+
when Hash then symbolize_keys(value)
|
81
|
+
else value
|
82
|
+
end
|
83
|
+
result[new_key] = new_value
|
84
|
+
result
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
extend_hash( @data )
|
90
|
+
end
|
91
|
+
|
92
|
+
def extend_hash( hash )
|
93
|
+
hash.keys.each do |k|
|
94
|
+
hash.instance_eval <<-KEY
|
95
|
+
def #{k}
|
96
|
+
fetch("#{k}")
|
97
|
+
end
|
98
|
+
KEY
|
99
|
+
end
|
100
|
+
|
101
|
+
hash.each do |(key, value)|
|
102
|
+
case value
|
103
|
+
when Hash then extend_hash( value )
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
69
107
|
end
|
70
108
|
end
|
@@ -37,6 +37,10 @@ module DaemonKit
|
|
37
37
|
self.configuration.trap( *args, &block )
|
38
38
|
end
|
39
39
|
|
40
|
+
def at_shutdown( &block )
|
41
|
+
self.configuration.at_shutdown( &block )
|
42
|
+
end
|
43
|
+
|
40
44
|
end
|
41
45
|
|
42
46
|
|
@@ -63,9 +67,17 @@ module DaemonKit
|
|
63
67
|
def self.shutdown( clean = false )
|
64
68
|
return unless $daemon_kit_shutdown_hooks_ran.nil?
|
65
69
|
$daemon_kit_shutdown_hooks_ran = true
|
66
|
-
|
70
|
+
|
67
71
|
DaemonKit.logger.info "Running shutdown hooks"
|
68
72
|
|
73
|
+
DaemonKit.configuration.shutdown_hooks.each do |hook|
|
74
|
+
begin
|
75
|
+
hook.call
|
76
|
+
rescue => e
|
77
|
+
DaemonKit.logger.exception( e )
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
69
81
|
log_exceptions if DaemonKit.configuration.backtraces && !clean
|
70
82
|
|
71
83
|
DaemonKit.logger.warn "Shutting down #{DaemonKit.configuration.daemon_name}"
|
@@ -87,6 +99,8 @@ module DaemonKit
|
|
87
99
|
end
|
88
100
|
|
89
101
|
def after_daemonize
|
102
|
+
set_umask
|
103
|
+
|
90
104
|
initialize_logger
|
91
105
|
initialize_signal_traps
|
92
106
|
|
@@ -97,6 +111,14 @@ module DaemonKit
|
|
97
111
|
set_process_name
|
98
112
|
|
99
113
|
DaemonKit.logger.info( "DaemonKit (#{DaemonKit::VERSION}) booted, now running #{DaemonKit.configuration.daemon_name}" )
|
114
|
+
|
115
|
+
if DaemonKit.configuration.user || DaemonKit.configuration.group
|
116
|
+
euid = Process.euid
|
117
|
+
egid = Process.egid
|
118
|
+
uid = Process.uid
|
119
|
+
gid = Process.gid
|
120
|
+
DaemonKit.logger.info( "DaemonKit dropped privileges to: #{euid} (EUID), #{egid} (EGID), #{uid} (UID), #{gid} (GID)" )
|
121
|
+
end
|
100
122
|
end
|
101
123
|
|
102
124
|
def set_load_path
|
@@ -138,6 +160,10 @@ module DaemonKit
|
|
138
160
|
end
|
139
161
|
end
|
140
162
|
|
163
|
+
def set_umask
|
164
|
+
File.umask configuration.umask
|
165
|
+
end
|
166
|
+
|
141
167
|
def initialize_logger
|
142
168
|
return if DaemonKit.logger
|
143
169
|
|
@@ -246,11 +272,23 @@ module DaemonKit
|
|
246
272
|
# Should be log backtraces
|
247
273
|
configurable :backtraces, false
|
248
274
|
|
275
|
+
# Configurable umask
|
276
|
+
configurable :umask, 0022
|
277
|
+
|
278
|
+
# Configurable user
|
279
|
+
configurable :user, :locked => true
|
280
|
+
|
281
|
+
# Confgiruable group
|
282
|
+
configurable :group, :locked => true
|
283
|
+
|
249
284
|
# Collection of signal traps
|
250
285
|
attr_reader :signal_traps
|
251
286
|
|
252
287
|
# Our safety net (#Safety) instance
|
253
288
|
attr_accessor :safety_net
|
289
|
+
|
290
|
+
# :nodoc: Shutdown hooks
|
291
|
+
attr_reader :shutdown_hooks
|
254
292
|
|
255
293
|
def initialize
|
256
294
|
parse_arguments!
|
@@ -267,6 +305,7 @@ module DaemonKit
|
|
267
305
|
self.safety_net = DaemonKit::Safety.instance
|
268
306
|
|
269
307
|
@signal_traps = {}
|
308
|
+
@shutdown_hooks = []
|
270
309
|
end
|
271
310
|
|
272
311
|
def environment
|
@@ -294,6 +333,13 @@ module DaemonKit
|
|
294
333
|
@signal_traps[signal].unshift( proc || block )
|
295
334
|
end
|
296
335
|
|
336
|
+
# Add a block or proc to be called during shutdown
|
337
|
+
def at_shutdown( proc = nil, &block )
|
338
|
+
return if proc.nil? && !block_given?
|
339
|
+
|
340
|
+
@shutdown_hooks << ( proc || block )
|
341
|
+
end
|
342
|
+
|
297
343
|
def pid_file
|
298
344
|
@pid_file ||= "#{File.dirname(self.log_path)}/#{self.daemon_name}.pid"
|
299
345
|
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module DaemonKit
|
2
|
+
# Class that cleanly abstracts away the different remote participants in
|
3
|
+
# ruote and allows daemon writers to just worry about processing workitems
|
4
|
+
# without worrying over the transport mechanism or anything else...
|
5
|
+
class RuoteParticipants
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Configure this daemon as a remote participant to ruote.
|
10
|
+
def configure(&block)
|
11
|
+
instance.configure(&block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Activate and run the remote participant code, calling the optional
|
15
|
+
# block for additional daemon logic.
|
16
|
+
def run(&block)
|
17
|
+
instance.run(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
private :new
|
21
|
+
|
22
|
+
def instance
|
23
|
+
@instance ||= new
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def instance=( obj )
|
29
|
+
@instance = obj
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :participants
|
34
|
+
|
35
|
+
def initialize
|
36
|
+
@transports = []
|
37
|
+
@participants = {}
|
38
|
+
|
39
|
+
@configuration = Config.load('ruote')
|
40
|
+
end
|
41
|
+
|
42
|
+
# Yields +self+ and configures the remote participants
|
43
|
+
def configure(&block)
|
44
|
+
block.call( self )
|
45
|
+
|
46
|
+
@transports.freeze
|
47
|
+
@participants.freeze
|
48
|
+
end
|
49
|
+
|
50
|
+
# Enable the use of a specific transport for workitems. Can be :amqp to use
|
51
|
+
# the AMQPParticipant/AMQPListener pair in ruote.
|
52
|
+
def use( transport )
|
53
|
+
@transports << transport
|
54
|
+
end
|
55
|
+
|
56
|
+
# Register classes as pseudo-participants. Two styles of registration are
|
57
|
+
# supported:
|
58
|
+
#
|
59
|
+
# register( Foo )
|
60
|
+
# register( 'short', ShortParticipant )
|
61
|
+
#
|
62
|
+
# The first format uses the class name (downcased and underscored) as the
|
63
|
+
# key for identifying the pseudo-participant, the second uses the the
|
64
|
+
# provided key.
|
65
|
+
#
|
66
|
+
# Pseudo-participant classes are instantiated when registered, and the
|
67
|
+
# instances are re-used.
|
68
|
+
def register( *args )
|
69
|
+
key, klass = if args.size == 1
|
70
|
+
[ underscore( args.first.to_s ), args.first ]
|
71
|
+
else
|
72
|
+
[ args[0].to_s, args[1] ]
|
73
|
+
end
|
74
|
+
|
75
|
+
@participants[ key ] = klass.new
|
76
|
+
end
|
77
|
+
|
78
|
+
# Run the participants
|
79
|
+
def run(&block)
|
80
|
+
run_amqp! if @transports.include?( :amqp )
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def run_amqp!
|
86
|
+
AMQP.run do
|
87
|
+
mq = ::MQ.new
|
88
|
+
queues = @configuration['amqp']['queues'].to_a
|
89
|
+
|
90
|
+
queues.each do |q|
|
91
|
+
DaemonKit.logger.debug("Subscribing to #{q} for workitems")
|
92
|
+
|
93
|
+
cmdq = mq.queue( q, :durable => true )
|
94
|
+
cmdq.subscribe( :ack => true ) do |header, message|
|
95
|
+
safely do
|
96
|
+
DaemonKit.logger.debug("Received workitem: #{message.inspect}")
|
97
|
+
|
98
|
+
RuoteWorkitem.process( :amqp, message )
|
99
|
+
|
100
|
+
DaemonKit.logger.debug("Processed workitem.")
|
101
|
+
|
102
|
+
header.ack
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Shamelessly lifted from the ActiveSupport inflector
|
110
|
+
def underscore(camel_cased_word)
|
111
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
112
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
113
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
114
|
+
tr("-", "_").
|
115
|
+
downcase
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|