kennethkalmer-daemon-kit 0.1.6 → 0.1.7.3

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