daemon-kit 0.1.7.9 → 0.1.7.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,4 @@
1
+ # Your starting point for daemon specific classes. This directory is
2
+ # already included in your load path, so no need to specify it.
3
+
4
+ require 'sample'
@@ -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
@@ -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.9'
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, 'daemon_kit/cron'
25
- autoload :Jabber, 'daemon_kit/jabber'
26
- autoload :AMQP, 'daemon_kit/amqp'
27
- autoload :Nanite, 'daemon_kit/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
@@ -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
@@ -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
- @data = config_data[ DAEMON_ENV ]
43
+ self.data = config_data[ DAEMON_ENV ]
44
44
  else
45
- @data = config_data
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.inject({}) { |m,c| m[c[0].to_sym] = c[1]; m } : @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
- # pick a key if we have it
64
- return @data[ method_name.to_s ] if @data.keys.include?( method_name.to_s )
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
@@ -0,0 +1,8 @@
1
+ module DaemonKit
2
+ # The core of daemon-kit exceptions
3
+ class Exception < ::StandardError
4
+ end
5
+
6
+ # Raised when no class is registered to process a ruote workitem
7
+ class MissingParticipant < Exception; end
8
+ 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