kennethkalmer-daemon-kit 0.1.6 → 0.1.7.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/Configuration.txt +58 -0
  2. data/History.txt +16 -0
  3. data/Manifest.txt +29 -2
  4. data/PostInstall.txt +1 -1
  5. data/{README.textile → README.rdoc} +31 -19
  6. data/Rakefile +2 -4
  7. data/TODO.txt +6 -5
  8. data/app_generators/daemon_kit/daemon_kit_generator.rb +29 -0
  9. data/app_generators/daemon_kit/templates/Rakefile +3 -1
  10. data/app_generators/daemon_kit/templates/config/arguments.rb +12 -0
  11. data/app_generators/daemon_kit/templates/config/boot.rb +2 -2
  12. data/app_generators/daemon_kit/templates/script/console +3 -0
  13. data/app_generators/daemon_kit/templates/script/destroy +14 -0
  14. data/app_generators/daemon_kit/templates/script/generate +14 -0
  15. data/daemon_generators/deploy_capistrano/deploy_capistrano_generator.rb +35 -0
  16. data/daemon_generators/deploy_capistrano/templates/Capfile +10 -0
  17. data/daemon_generators/deploy_capistrano/templates/USAGE +10 -0
  18. data/daemon_generators/deploy_capistrano/templates/config/deploy/production.rb +6 -0
  19. data/daemon_generators/deploy_capistrano/templates/config/deploy/staging.rb +6 -0
  20. data/daemon_generators/deploy_capistrano/templates/config/deploy.rb +51 -0
  21. data/daemon_generators/deploy_capistrano/templates/config/environments/staging.rb +0 -0
  22. data/lib/daemon_kit/application.rb +135 -11
  23. data/lib/daemon_kit/arguments.rb +151 -0
  24. data/lib/daemon_kit/commands/console.rb +38 -0
  25. data/lib/daemon_kit/config.rb +1 -0
  26. data/lib/daemon_kit/console_daemon.rb +2 -0
  27. data/lib/daemon_kit/core_ext/string.rb +22 -0
  28. data/lib/daemon_kit/core_ext.rb +1 -0
  29. data/lib/daemon_kit/deployment/capistrano.rb +485 -0
  30. data/lib/daemon_kit/initializer.rb +87 -25
  31. data/lib/daemon_kit/pid_file.rb +61 -0
  32. data/lib/daemon_kit/tasks/environment.rake +5 -4
  33. data/lib/daemon_kit/tasks/framework.rake +15 -1
  34. data/lib/daemon_kit/tasks/god.rake +62 -0
  35. data/lib/daemon_kit/tasks/monit.rake +29 -0
  36. data/lib/daemon_kit.rb +11 -5
  37. data/rubygems_generators/install_rspec/templates/spec/spec_helper.rb +1 -1
  38. data/spec/argument_spec.rb +51 -0
  39. data/spec/config_spec.rb +77 -0
  40. data/spec/daemon_kit_spec.rb +2 -2
  41. data/spec/fixtures/env.yml +15 -0
  42. data/spec/fixtures/noenv.yml +4 -0
  43. data/spec/initializer_spec.rb +4 -3
  44. data/spec/spec_helper.rb +8 -11
  45. data/templates/god/god.erb +69 -0
  46. data/templates/monit/monit.erb +14 -0
  47. data/test/test_daemon-kit_generator.rb +15 -1
  48. data/test/test_deploy_capistrano_generator.rb +48 -0
  49. metadata +41 -21
  50. data/lib/daemon_kit/patches/force_kill_wait.rb +0 -120
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ require File.dirname(__FILE__) + '/../config/boot'
3
+ require 'daemon_kit/commands/console'
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:daemon, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:daemon, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
@@ -0,0 +1,35 @@
1
+ class DeployCapistranoGenerator < RubiGen::Base
2
+
3
+ attr_reader :name
4
+
5
+ def initialize(runtime_args, runtime_options = {})
6
+ super
7
+ usage if args.empty?
8
+ @name = args.shift
9
+ end
10
+
11
+ def manifest
12
+ record do |m|
13
+
14
+ m.file "Capfile", "Capfile"
15
+ m.directory "config"
16
+ m.template "config/deploy.rb", "config/deploy.rb"
17
+ m.directory "config/deploy"
18
+ m.template "config/deploy/staging.rb", "config/deploy/staging.rb"
19
+ m.template "config/deploy/production.rb", "config/deploy/production.rb"
20
+ m.directory "config/environments"
21
+ m.file "config/environments/staging.rb", "config/environments/staging.rb", :collision => :skip
22
+ m.readme "USAGE"
23
+ end
24
+ end
25
+
26
+ protected
27
+ def banner
28
+ <<-EOS
29
+ Creates the required capistrano configurations for deploying your daemon code
30
+ to remote servers.
31
+
32
+ USAGE: #{$0} #{spec.name} daemon-name
33
+ EOS
34
+ end
35
+ end
@@ -0,0 +1,10 @@
1
+ unless respond_to?(:namespace) # cap2 differentiator
2
+ $stderr.puts "Requires capistrano version 2"
3
+ exit 1
4
+ end
5
+
6
+ require 'config/boot'
7
+ load DaemonKit.framework_root + '/lib/daemon_kit/deployment/capistrano.rb'
8
+
9
+ Dir['config/deploy/recipes/*.rb'].each { |plugin| load(plugin) }
10
+ load 'config/deploy.rb'
@@ -0,0 +1,10 @@
1
+
2
+ Capistrano deployment configuration generator completed
3
+
4
+ Usage:
5
+ Review the configuration files in config/deploy.rb and
6
+ config/deploy/*.rb
7
+
8
+ Once you're happy with the configurations, you can review
9
+ a list of available capistrano commands by running `cap -T'
10
+
@@ -0,0 +1,6 @@
1
+ #set :deploy_to, "/svc/<%= name %>" # defaults to "/u/apps/#{application}"
2
+ #set :user, "<% name %>" # defaults to the currently logged in user
3
+ set :daemon_env, 'production'
4
+
5
+ set :domain, 'example.com'
6
+ server domain
@@ -0,0 +1,6 @@
1
+ #set :deploy_to, "/svc/<%= name %>" # defaults to "/u/apps/#{application}"
2
+ #set :user, "<% name %>" # defaults to the currently logged in user
3
+ set :daemon_env, 'staging'
4
+
5
+ set :domain, 'example.com'
6
+ server domain
@@ -0,0 +1,51 @@
1
+ # Modified capistrano recipe, based on the standard 'deploy' recipe
2
+ # provided by capistrano but without the Rails-specific dependencies
3
+
4
+ set :stages, %w(staging production)
5
+ set :default_stage, "staging"
6
+ require "capistrano/ext/multistage"
7
+
8
+ # Set some globals
9
+ default_run_options[:pty] = true
10
+ set :application, "<%= name %>"
11
+
12
+ # Deployment
13
+ set :deploy_to, "/svc/#{application}"
14
+ #set :user, 'someone'
15
+
16
+ # Get repo configuration
17
+ set :repository, "git@github.com:yourname/#{application}.git"
18
+ set :scm, "git"
19
+ set :branch, "master"
20
+ set :deploy_via, :remote_cache
21
+ set :git_enable_submodules, 1
22
+
23
+ # No sudo
24
+ set :use_sudo, false
25
+
26
+ # File list in the config_files setting will be copied from the
27
+ # 'deploy_to' directory into config, overwriting files from the repo
28
+ # with the same name
29
+ set :config_files, %w{}
30
+
31
+ # List any work directories here that you need persisted between
32
+ # deployments. They are created in 'deploy_to'/shared and symlinked
33
+ # into the root directory of the deployment.
34
+ set :shared_children, %w{log tmp}
35
+
36
+ # Record our dependencies
37
+ depend :remote, :gem, "daemon-kit", ">=0.0.0"
38
+
39
+ # Hook into capistrano's events
40
+ before "deploy:update_code", "deploy:check"
41
+
42
+ # Create some tasks related to deployment
43
+ namespace :deploy do
44
+
45
+ desc "Get the current revision of the deployed code"
46
+ task :get_current_version do
47
+ run "cat #{current_path}/REVISION" do |ch, stream, out|
48
+ puts "Current revision: " + out.chomp
49
+ end
50
+ end
51
+ end
@@ -1,4 +1,4 @@
1
- require 'daemons'
1
+ require 'timeout'
2
2
 
3
3
  module DaemonKit
4
4
 
@@ -7,29 +7,153 @@ module DaemonKit
7
7
 
8
8
  class << self
9
9
 
10
- # Run the file as a daemon
11
- def run( file )
10
+ # Run the specified file as a daemon process.
11
+ def exec( file )
12
12
  raise DaemonNotFound.new( file ) unless File.exist?( file )
13
13
 
14
- app_name = DaemonKit.configuration.daemon_name || File.basename( file )
15
- options = { :backtrace => true, :log_output => true, :app_name => app_name }
14
+ DaemonKit.configuration.daemon_name ||= File.basename( file )
15
+
16
+ command, configs, args = Arguments.parse( ARGV )
17
+
18
+ case command
19
+ when :run
20
+ parse_arguments( args )
21
+ run( file )
22
+ when :start
23
+ parse_arguments( args )
24
+ start( file )
25
+ when :stop
26
+ stop
27
+ end
28
+ end
29
+
30
+ # Run the daemon in the foreground without daemonizing
31
+ def run( file )
32
+ self.chroot
33
+ self.clean_fd
34
+ self.redirect_io( true )
35
+
36
+ require file
37
+ end
38
+
39
+ # Run our file properly
40
+ def start( file )
41
+ self.daemonize
42
+ self.chroot
43
+ self.clean_fd
44
+ self.redirect_io
45
+
46
+ require file
47
+ end
48
+
49
+ def stop
50
+ @pid_file = PidFile.new( DaemonKit.configuration.pid_file )
51
+
52
+ unless @pid_file.running?
53
+ puts "Nothing to stop"
54
+ exit
55
+ end
56
+
57
+ target_pid = @pid_file.pid
58
+
59
+ puts "Sending TERM to #{target_pid}"
60
+ Process.kill( 'TERM', target_pid )
61
+
62
+ if seconds = DaemonKit.configuration.force_kill_wait
63
+ begin
64
+ Timeout::timeout( seconds ) do
65
+ loop do
66
+ puts "Waiting #{seconds} seconds for #{target_pid} before sending KILL"
67
+
68
+ break unless @pid_file.running?
16
69
 
17
- options[:dir_mode] = DaemonKit.configuration.dir_mode
18
- options[:dir] = DaemonKit.configuration.dir
19
- options[:multiple] = DaemonKit.configuration.multiple
20
- options[:force_kill_wait] = DaemonKit.configuration.force_kill_wait if DaemonKit.configuration.force_kill_wait
70
+ seconds -= 1
71
+ sleep 1
72
+ end
73
+ end
74
+ rescue Timeout::Error
75
+ Process.kill( 'KILL', target_pid )
76
+ end
77
+ end
21
78
 
22
- Daemons.run( file, options )
79
+ @pid_file.cleanup
23
80
  end
24
81
 
25
82
  # Call this from inside a daemonized process to complete the
26
83
  # initialization process
27
84
  def running!
28
- DaemonKit::Initializer.continue!
85
+ Initializer.continue!
29
86
 
30
87
  yield DaemonKit.configuration if block_given?
31
88
  end
32
89
 
90
+ # Exit the daemon
91
+ # TODO: Make configurable callback chain
92
+ # TODO: Hook into at_exit()
93
+ def exit!( code = 0 )
94
+ end
95
+
96
+ protected
97
+
98
+ def parse_arguments( args )
99
+ DaemonKit.arguments = Arguments.new
100
+ DaemonKit.arguments.parse( args )
101
+ end
102
+
103
+ # Daemonize the process
104
+ def daemonize
105
+ @pid_file = PidFile.new( DaemonKit.configuration.pid_file )
106
+ @pid_file.ensure_stopped!
107
+
108
+ if RUBY_VERSION < "1.9"
109
+ exit if fork
110
+ Process.setsid
111
+ exit if fork
112
+ else
113
+ Process.daemon( true, true )
114
+ end
115
+
116
+ @pid_file.write!
117
+
118
+ # TODO: Convert into shutdown hook
119
+ at_exit { @pid_file.cleanup }
120
+ end
121
+
122
+ # Release the old working directory and insure a sensible umask
123
+ # TODO: Make chroot directory configurable
124
+ def chroot
125
+ Dir.chdir '/'
126
+ File.umask 0000
127
+ end
128
+
129
+ # Make sure all file descriptors are closed (with the exception
130
+ # of STDIN, STDOUT & STDERR)
131
+ def clean_fd
132
+ ObjectSpace.each_object(IO) do |io|
133
+ unless [STDIN, STDOUT, STDERR].include?(io)
134
+ begin
135
+ unless io.closed?
136
+ io.close
137
+ end
138
+ rescue ::Exception
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ # Redirect our IO
145
+ # TODO: make this configurable
146
+ def redirect_io( simulate = false )
147
+ begin
148
+ STDIN.reopen '/dev/null'
149
+ rescue ::Exception
150
+ end
151
+
152
+ unless simulate
153
+ STDOUT.reopen '/dev/null', 'a'
154
+ STDERR.reopen '/dev/null', 'a'
155
+ end
156
+ end
33
157
  end
34
158
 
35
159
  end
@@ -0,0 +1,151 @@
1
+ require 'optparse'
2
+
3
+ module DaemonKit
4
+
5
+ # A wrapper around OptParse for setting up arguments to the daemon
6
+ # process.
7
+ #
8
+ # TODO: Set rules for basic options that go for all daemons
9
+ # TODO: Load options from config/arguments.rb
10
+ class Arguments
11
+
12
+ # Default command
13
+ @default_command = 'run'
14
+
15
+ # Valid commands
16
+ @commands = [
17
+ 'start',
18
+ 'stop',
19
+ 'run'
20
+ ]
21
+
22
+ class << self
23
+
24
+ attr_reader :default_command, :commands
25
+
26
+ # Parse the argument values and return an array with the command
27
+ # name, config values and argument values
28
+ def parse( argv )
29
+ cmd, argv = self.command( argv )
30
+
31
+ return cmd, *self.configuration( argv )
32
+ end
33
+
34
+ # Parse the provided argument array for a given command, or
35
+ # return the default command and the remaining arguments
36
+ def command( argv )
37
+ # extract command or set default
38
+ cmd = self.commands.include?( argv[0] ) ? argv.shift : self.default_command
39
+
40
+ return cmd.to_sym, argv
41
+ end
42
+
43
+ # Extracts any values for arguments matching '--config' as well
44
+ # as some implication arguments like '-e'. Returns an array with
45
+ # the configs as the first value and the remaing args as the
46
+ # last value.
47
+ #
48
+ # To set a value on the default #Configuration instance, use the
49
+ # following notation:
50
+ #
51
+ # --config attribute=value
52
+ #
53
+ # The above notation can be used several times to set different
54
+ # values.
55
+ #
56
+ # Special, or 'normal' arguments that are mapped to the default
57
+ # #Configuration instance are listed below:
58
+ #
59
+ # -e value or --env value => environment
60
+ # --pid pidfile => pid_file
61
+ #
62
+ def configuration( argv )
63
+ configs = []
64
+
65
+ i = 0
66
+ while i < argv.size
67
+ if argv[i] == "--config"
68
+ argv.delete_at( i )
69
+ configs << argv.delete_at(i)
70
+ end
71
+
72
+ if argv[i] == "-e" || argv[i] == "--env"
73
+ argv.delete_at( i )
74
+ configs << "environment=#{argv.delete_at(i)}"
75
+ end
76
+
77
+ if argv[i] == "--pid"
78
+ argv.delete_at( i )
79
+ configs << "pid_file=#{argv.delete_at(i)}"
80
+ end
81
+
82
+ i += 1
83
+ end
84
+
85
+ return configs, argv
86
+ end
87
+
88
+ # Return the arguments remaining after running through #configuration
89
+ def arguments( argv )
90
+ self.configuration( argv ).last
91
+ end
92
+ end
93
+
94
+ attr_reader :options
95
+
96
+ def initialize
97
+ @options = {}
98
+
99
+ @parser = OptionParser.new do |opts|
100
+ opts.banner = "Usage: #{File.basename($0)} [command] [options]"
101
+
102
+ opts.separator ""
103
+
104
+ opts.separator "Command is one of the following:"
105
+ opts.separator " run - Run the daemon without forking (default)"
106
+ opts.separator " start - Run the daemon"
107
+ opts.separator " stop - Stop the running daemon"
108
+
109
+ opts.separator ""
110
+
111
+ opts.separator "Options can be:"
112
+
113
+ arg_file = File.join( DaemonKit.root, 'config', 'arguments.rb' )
114
+ eval(IO.read(arg_file), binding, arg_file) if File.exists?( arg_file )
115
+
116
+ opts.on("-e", "--env ENVIRONMENT", "Environment for the process", "Defaults to development") do
117
+ # Nothing, just here for show
118
+ end
119
+
120
+ opts.on("--pidfile PATH", "Path to the pidfile", "Defaults to log/#{DaemonKit.configuration.daemon_name}.pid") do
121
+ # Nothing, just here for show
122
+ end
123
+
124
+ opts.separator ""
125
+ opts.separator "Advanced configurations:"
126
+ opts.on("--config ATTRIBUTE=VALUE",
127
+ "Change values of the daemon-kit Configuration instance",
128
+ "Example: log_dir=/path/to/log-directory") do
129
+ # Nothing, just here for show
130
+ end
131
+
132
+ opts.separator ""
133
+
134
+ opts.separator "Common options:"
135
+ opts.on("-v", "--version", "Show version information and exit") do
136
+ puts "daemon-kit #{DaemonKit::VERSION} (http://github.com/kennethkalmer/daemon-kit)"
137
+ exit
138
+ end
139
+
140
+ opts.on_tail("-h", "--help", "Show this message") do
141
+ puts opts
142
+ exit
143
+ end
144
+ end
145
+ end
146
+
147
+ def parse( argv )
148
+ @parser.parse!( argv )
149
+ end
150
+ end
151
+ end