dctl 1.0.0

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