dctl 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,14 @@
1
+ *1.0.0* (07th April, 2005) Bruno Carnazzi <mailto:bcarnazzi@gmail.com>
2
+ * Full rewrite from scratch
3
+ * Multiple daemon instances does not exists anymore
4
+ * Load option does not exists anymore
5
+ * Strongly unit-tested (I hope so)
6
+ * Smaller, better, stronger :)
7
+ * Added a 'setup.rb' script
8
+ * Added a 'rakefile'
9
+ * Added a bit of docs
10
+ * ...
11
+
12
+ *0.0.8* (14th March, 2005) Bruno Carnazzi <mailto:bcarnazzi@gmail.com>
13
+ * initial release
14
+ * so ugly...
data/README ADDED
@@ -0,0 +1,63 @@
1
+ = dctl - a daemon controller written in Ruby
2
+
3
+ dctl is a small script for providing daemon functionnalty (start, stop, ...)
4
+ to non-daemon process or script, with automatic handling of pidfiles.
5
+ dctl is mainly an extended rewrite of the daemons[http://rubyforge.org/projects/daemons/]
6
+ project for learning purpose.
7
+ It is designed to be small and smart and I try to apply XP's good practice (unit test)
8
+
9
+ Major features:
10
+ * Convert any executable file with endless lifecycle into a controllable daemon
11
+ * Automatic handling of pidfiles
12
+ * Command-oriented style (dctl requires cmdparser)
13
+
14
+
15
+ == Examples
16
+
17
+ === Start a daemon with redirection of stdout
18
+ % dctl start -oout test/infinite.sh
19
+
20
+ === Get the status of a daemon
21
+ % dctl status test/infinite.sh
22
+
23
+ === Stop a daemon
24
+ % dctl stop test/infinite.sh
25
+
26
+
27
+ == Download
28
+
29
+ The latest version of dctl can be found at :
30
+ * http://rubyforge.org/projects/dctl/
31
+
32
+
33
+ == Installation
34
+
35
+ The prefered method of installing dctl is currently through its setup.rb file.
36
+ As dctl stands on cmdparser, you need to have cmdparser installed before.
37
+ I plan to make a GEM for dctl soon.
38
+
39
+ from dctl distribution directory:
40
+ % [sudo] ruby setup.rb
41
+
42
+ === Notes
43
+ If you installed cmdparser as a gem, please check your RUBYOPTS which should looks like:
44
+ % echo $RUBYOPTS
45
+ -rubygems
46
+
47
+ If not, do the following :
48
+ % export RUBYOPTS="-rubygems"
49
+
50
+
51
+ == Author
52
+
53
+ Written in 2005 by Bruno Carnazzi <mailto:bcarnazzi@gmail.com>
54
+
55
+ == License
56
+
57
+ dctl is free software. You can redistribute it under the terms specified in
58
+ the COPYING file of the Ruby distribution.
59
+
60
+
61
+ == Feedback and other resources
62
+
63
+ At http://rubyforge.org/projects/dctl/
data/Rakefile ADDED
@@ -0,0 +1,211 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/packagetask'
6
+ require 'rake/gempackagetask'
7
+ require 'rake/contrib/rubyforgepublisher'
8
+ require 'lib/dctl/metainf'
9
+
10
+
11
+ PKG_BUILD = ENV['PKG_BUILD'] ? '.' + ENV['PKG_BUILD'] : ''
12
+ PKG_NAME = 'dctl'
13
+ PKG_VERSION = Dctl::VERSION.join('.') + PKG_BUILD
14
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
+
16
+ RELEASE_NAME = "REL #{PKG_VERSION}"
17
+
18
+ RUBY_FORGE_PROJECT = "dctl"
19
+ RUBY_FORGE_USER = "bcarnazzi"
20
+
21
+ PKG_FILES = FileList[
22
+ "lib/**/*", "test/*", "bin/*", "CHANGELOG", "README", "setup.rb", "Rakefile", "TODO"
23
+ ].exclude(/\bCVS\b|~$/)
24
+
25
+
26
+ desc "Default Task"
27
+ task :default => [ :test ]
28
+
29
+ # Run the unit tests
30
+
31
+ Rake::TestTask.new("test") do |t|
32
+ t.libs << "test"
33
+ t.test_files = FileList['test/dctl_test.rb']
34
+ t.verbose = true
35
+ end
36
+
37
+ # Generate the RDoc documentation
38
+
39
+ Rake::RDocTask.new do |rdoc|
40
+ rdoc.rdoc_dir = 'html'
41
+ rdoc.title = "dctl - a daemon controller written in Ruby"
42
+ rdoc.options << '--line-numbers --inline-source --accessor cattr_accessor=object'
43
+ rdoc.template = "#{ENV['template']}.rb" if ENV['template']
44
+ rdoc.rdoc_files.include('README', 'TODO', 'CHANGELOG')
45
+ rdoc.rdoc_files.include('lib/**/*.rb')
46
+ end
47
+
48
+
49
+ # Create compressed packages
50
+
51
+ dist_dirs = [ "lib", "bin", "test" ]
52
+
53
+ spec = Gem::Specification.new do |s|
54
+ s.name = PKG_NAME
55
+ s.version = PKG_VERSION
56
+ s.platform = Gem::Platform::RUBY
57
+ s.summary = "dctl - a daemon controller written in Ruby"
58
+ s.description = %q{dctl is a small script for providing daemon functionnalty (start, stop, ...) to non-daemon process or script, with automatic handling of pidfiles.}
59
+
60
+ s.files = [ "Rakefile", "setup.rb", "README", "TODO", "CHANGELOG" ]
61
+ dist_dirs.each do |dir|
62
+ s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| item.include?( "\.svn" ) }
63
+ end
64
+ s.executables = ['dctl']
65
+ s.bindir = 'bin'
66
+
67
+ # s.test_suite_file = 'test/dctl_test.rb'
68
+
69
+ s.add_dependency('cmdparse', '= 1.0.0')
70
+
71
+ s.require_path = 'lib'
72
+ s.autorequire = 'dctl'
73
+
74
+ s.has_rdoc = true
75
+ s.extra_rdoc_files = %w( README TODO CHANGELOG )
76
+ s.rdoc_options.concat ['--main', 'README']
77
+
78
+ s.author = "Bruno Carnazzi"
79
+ s.email = "bcarnazzi@gmail.com"
80
+ s.homepage = "http://rubyforge.org/project/dctl/"
81
+ s.rubyforge_project = "dctl"
82
+ end
83
+
84
+ Rake::GemPackageTask.new(spec) do |p|
85
+ p.gem_spec = spec
86
+ p.need_tar = true
87
+ p.need_zip = true
88
+ end
89
+
90
+
91
+ # Publishing ------------------------------------------------------
92
+
93
+ desc "Publish the API documentation"
94
+ task :pdoc => [:rdoc] do
95
+ Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload
96
+ end
97
+
98
+ desc "Publish the release files to RubyForge."
99
+ task :release => [:package] do
100
+ files = ["gem", "tgz", "zip"].map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
101
+
102
+ if RUBY_FORGE_PROJECT then
103
+ require 'net/http'
104
+ require 'open-uri'
105
+
106
+ project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
107
+ project_data = open(project_uri) { |data| data.read }
108
+ group_id = project_data[/[?&]group_id=(\d+)/, 1]
109
+ raise "Couldn't get group id" unless group_id
110
+
111
+ # This echos password to shell which is a bit sucky
112
+ if ENV["RUBY_FORGE_PASSWORD"]
113
+ password = ENV["RUBY_FORGE_PASSWORD"]
114
+ else
115
+ print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
116
+ password = STDIN.gets.chomp
117
+ end
118
+
119
+ login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
120
+ data = [
121
+ "login=1",
122
+ "form_loginname=#{RUBY_FORGE_USER}",
123
+ "form_pw=#{password}"
124
+ ].join("&")
125
+ http.post("/account/login.php", data)
126
+ end
127
+
128
+ cookie = login_response["set-cookie"]
129
+ raise "Login failed" unless cookie
130
+ headers = { "Cookie" => cookie }
131
+
132
+ release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
133
+ release_data = open(release_uri, headers) { |data| data.read }
134
+ package_id = release_data[/[?&]package_id=(\d+)/, 1]
135
+ raise "Couldn't get package id" unless package_id
136
+
137
+ first_file = true
138
+ release_id = ""
139
+
140
+ files.each do |filename|
141
+ basename = File.basename(filename)
142
+ file_ext = File.extname(filename)
143
+ file_data = File.open(filename, "rb") { |file| file.read }
144
+
145
+ puts "Releasing #{basename}..."
146
+
147
+ release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
148
+ release_date = Time.now.strftime("%Y-%m-%d %H:%M")
149
+ type_map = {
150
+ ".zip" => "3000",
151
+ ".tgz" => "3110",
152
+ ".gz" => "3110",
153
+ ".gem" => "1400"
154
+ }; type_map.default = "9999"
155
+ type = type_map[file_ext]
156
+ boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
157
+
158
+ query_hash = if first_file then
159
+ {
160
+ "group_id" => group_id,
161
+ "package_id" => package_id,
162
+ "release_name" => RELEASE_NAME,
163
+ "release_date" => release_date,
164
+ "type_id" => type,
165
+ "processor_id" => "8000", # Any
166
+ "release_notes" => "",
167
+ "release_changes" => "",
168
+ "preformatted" => "1",
169
+ "submit" => "1"
170
+ }
171
+ else
172
+ {
173
+ "group_id" => group_id,
174
+ "release_id" => release_id,
175
+ "package_id" => package_id,
176
+ "step2" => "1",
177
+ "type_id" => type,
178
+ "processor_id" => "8000", # Any
179
+ "submit" => "Add This File"
180
+ }
181
+ end
182
+
183
+ query = "?" + query_hash.map do |(name, value)|
184
+ [name, URI.encode(value)].join("=")
185
+ end.join("&")
186
+
187
+ data = [
188
+ "--" + boundary,
189
+ "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
190
+ "Content-Type: application/octet-stream",
191
+ "Content-Transfer-Encoding: binary",
192
+ "", file_data, ""
193
+ ].join("\x0D\x0A")
194
+
195
+ release_headers = headers.merge(
196
+ "Content-Type" => "multipart/form-data; boundary=#{boundary}"
197
+ )
198
+
199
+ target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
200
+ http.post(target + query, data, release_headers)
201
+ end
202
+
203
+ if first_file then
204
+ release_id = release_response.body[/release_id=(\d+)/, 1]
205
+ raise("Couldn't get release id") unless release_id
206
+ end
207
+
208
+ first_file = false
209
+ end
210
+ end
211
+ end
data/TODO ADDED
@@ -0,0 +1,5 @@
1
+ - write a *real* todo file -> DONE (05/04/05)
2
+ - write a bit of rdoc -> DONE (05/04/05)
3
+ - write a 'status' command -> DONE (05/04/05)
4
+ - write a 'rakefile'
5
+ - package as a gem
data/bin/dctl ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'dctl'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require_gem 'dctl'
8
+ end
9
+ DctlApp.new.run
data/lib/dctl.rb ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'dctl/cmdparser'
4
+
5
+ class DctlApp
6
+ def run
7
+ begin
8
+ Dctl::CommandLineParser.parse!(File.basename($0, '.*'), ARGV)
9
+ rescue
10
+ puts $!
11
+ puts 'Use -h or --help for usage.'
12
+ exit 1
13
+ end
14
+ end
15
+ end
16
+
17
+ if __FILE__ == $0 then
18
+ DctlApp.new.run
19
+ end
@@ -0,0 +1,42 @@
1
+ begin
2
+ require 'rubygems'
3
+ require 'cmdparse' # require_gem fails !
4
+ rescue LoadError
5
+ require 'cmdparse'
6
+ end
7
+
8
+ require 'dctl/metainf'
9
+ require 'dctl/defaults'
10
+ require 'dctl/command'
11
+
12
+ module Dctl
13
+
14
+ # This class is used to parse user command line.
15
+ class CommandLineParser
16
+ def self.parse!(program_name, argv)
17
+ cmd_parser = CommandParser.new
18
+
19
+ cmd_parser.options { |opt|
20
+ opt.program_name = program_name
21
+ opt.version = Dctl::VERSION
22
+ opt.release = Dctl::RELEASE
23
+ opt.separator 'args contains target program to daemonize and its arguments.'
24
+ opt.separator ''
25
+ opt.separator 'Global options:'
26
+ opt.on('-d', '--dir DIR', "Path where to store pidfiles (default is '#{$DIR}')") { |$DIR| }
27
+ }
28
+
29
+ cmd_parser.add_command Dctl::Command::StartCommand.new
30
+ cmd_parser.add_command Dctl::Command::StopCommand.new
31
+ cmd_parser.add_command Dctl::Command::RunCommand.new
32
+ cmd_parser.add_command Dctl::Command::RestartCommand.new
33
+ cmd_parser.add_command Dctl::Command::ZapCommand.new
34
+ cmd_parser.add_command Dctl::Command::StatusCommand.new
35
+ cmd_parser.add_command CommandParser::HelpCommand.new
36
+ cmd_parser.add_command CommandParser::VersionCommand.new
37
+
38
+ cmd_parser.parse! argv
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,135 @@
1
+ begin
2
+ require 'rubygems'
3
+ require 'cmdparse' # require_gem fails !
4
+ rescue LoadError
5
+ require 'cmdparse'
6
+ end
7
+
8
+ require 'dctl/defaults'
9
+ require 'dctl/daemon'
10
+
11
+ module Dctl
12
+
13
+ # This module contains all possible commands allowed by Dctl
14
+ module Command
15
+
16
+ # This module is used by command that redirect their standard io streams.
17
+ module Redirectible
18
+ def std_init
19
+ @stdin = $IN
20
+ @stdout = $OUT
21
+ @stderr = $ERR
22
+ options.separator "Options:"
23
+ options.on('-i', '--stdin FILE', "Use FILE as stdin when daemonized (default is '#{@stdin}').") { |@stdin| }
24
+ options.on('-o', '--stdout FILE', "Use FILE as stdout when daemonized (default is '#{@stdout}').") { |@stdout| }
25
+ options.on('-e', '--stderr FILE', "Use FILE as stderr when daemonized (default is '#{@stderr}').") { |@stderr| }
26
+ end
27
+ end
28
+
29
+ class StartCommand < CommandParser::Command
30
+ include Redirectible
31
+
32
+ def initialize
33
+ super 'start'
34
+ std_init
35
+ end
36
+
37
+ def description
38
+ 'Start a new daemonized instance of a specified application.'
39
+ end
40
+
41
+ def execute(cmd_parser, args)
42
+ cmd_args = args.slice! 1, args.length
43
+ cmd = args[0]
44
+ Dctl::Daemon.new(cmd, $DIR).start(cmd_args, @stdin, @stdout, @stderr)
45
+ end
46
+ end
47
+
48
+ class RestartCommand < CommandParser::Command
49
+ include Redirectible
50
+
51
+ def initialize
52
+ super 'restart'
53
+ std_init
54
+ end
55
+
56
+ def description
57
+ 'Restart a daemonized instance of a specified application with new arguments.'
58
+ end
59
+
60
+ def execute(cmd_parser, args)
61
+ cmd_args = args.slice! 1, args.length
62
+ cmd = args[0]
63
+ Dctl::Daemon.new(cmd, $DIR).restart(cmd_args, @stdin, @stdout, @stderr)
64
+ end
65
+ end
66
+
67
+ class StopCommand < CommandParser::Command
68
+ def initialize
69
+ super 'stop'
70
+ options.separator "Options:"
71
+ end
72
+
73
+ def description
74
+ 'Stop a daemonized instance of a specified application.'
75
+ end
76
+
77
+ def execute(cmd_parser, args)
78
+ cmd = args[0]
79
+ Dctl::Daemon.new(cmd, $DIR).stop
80
+ end
81
+ end
82
+
83
+ class ZapCommand < CommandParser::Command
84
+ def initialize
85
+ super 'zap'
86
+ options.separator "Options:"
87
+ end
88
+
89
+ def description
90
+ "Manually set a daemonized instance of a specified application to 'stopped' state."
91
+ end
92
+
93
+ def execute(cmd_parser, args)
94
+ cmd = args[0]
95
+ Dctl::Daemon.new(cmd, $DIR).zap
96
+ end
97
+ end
98
+
99
+ class RunCommand < CommandParser::Command
100
+ def initialize
101
+ super 'run'
102
+ options.separator "Options:"
103
+ end
104
+
105
+ def description
106
+ 'Run a new un-daemonized instance of a specified application.'
107
+ end
108
+
109
+ def execute(cmd_parser, args)
110
+ cmd_args = args.slice! 1, args.length
111
+ cmd = args[0]
112
+ Dctl::Daemon.new(cmd, $DIR).run(cmd_args)
113
+ end
114
+ end
115
+
116
+ class StatusCommand < CommandParser::Command
117
+ def initialize
118
+ super 'status'
119
+ options.separator "Options:"
120
+ end
121
+
122
+ def description
123
+ "Get the state of a specified application."
124
+ end
125
+
126
+ def execute(cmd_parser, args)
127
+ cmd = args[0]
128
+ puts "#{File.basename cmd}: #{Dctl::Daemon.new(cmd, $DIR).status}"
129
+ end
130
+ end
131
+
132
+
133
+ end
134
+
135
+ end