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.
@@ -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