asmodai 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.bundle/config ADDED
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_DISABLE_SHARED_GEMS: "1"
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/*
2
+ *.gem
3
+ play.rb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in asmodai.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,18 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ asmodai (0.1.0)
5
+ thor (>= 0.14.6)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ thor (0.14.6)
11
+
12
+ PLATFORMS
13
+ ruby
14
+
15
+ DEPENDENCIES
16
+ asmodai!
17
+ bundler (>= 1.0.0.rc.5)
18
+ thor (>= 0.14.6)
data/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # Asmodai
2
+
3
+ ## Description
4
+
5
+ Asmodai is a simple-to-use generator for daemon tasks written in Ruby. It makes
6
+ creating a daemon as straight forward as creating a Rails-application and saves
7
+ you the hassle of boring and repetitive tasks like creating start- and
8
+ stop-scripts, pid-file logic or implementing logging.
9
+
10
+ ## Installation
11
+
12
+ $ gem install asmodai
13
+
14
+ ## Usage
15
+
16
+ $ asmodai new foobar
17
+
18
+ $ cd foobar
19
+
20
+ Here you will find a dummy implementation:
21
+
22
+ class Foobar < Asmodai::Daemon
23
+ attr_accessor :running
24
+
25
+ def on_signal(sig)
26
+ # perform cleanup or stop an eventloop
27
+ self.running=false
28
+ end
29
+
30
+ def run
31
+ self.running=true
32
+ while running
33
+ logger.info { "I'm still running" }
34
+ sleep 1
35
+ end
36
+ end
37
+ end
38
+
39
+ You can develop your daemon by executing
40
+
41
+ $ asmodai foreground
42
+
43
+ This executes the daemon in the foreground and outputs all logging to standard
44
+ output. It can be terminated with Ctrl-C.
45
+
46
+ To start your daemon in the background run
47
+
48
+ $ asmodai start
49
+
50
+ Check the status of the daemon
51
+
52
+ $ asmodai status
53
+ => foobar runs with pid 75952
54
+
55
+ Stop the daemon
56
+
57
+ $ asmodai stop
58
+
59
+ ## Example: Using Asmodai with EventMachine
60
+
61
+ $ asmodai new echo
62
+ $ cd echo
63
+
64
+ Edit the Gemfile
65
+
66
+ source 'http://rubygems.org'
67
+
68
+ gem 'activesupport', ">= 3.0.0"
69
+ gem 'eventmachine'
70
+
71
+ Edit lib/server.rb
72
+
73
+ module Echo::Server
74
+ def post_init
75
+ puts "-- someone connected to the echo server!"
76
+ end
77
+
78
+ def receive_data data
79
+ send_data ">>>you sent: #{data}"
80
+ close_connection if data =~ /quit/i
81
+ end
82
+
83
+ def unbind
84
+ puts "-- someone disconnected from the echo server!"
85
+ end
86
+ end
87
+
88
+ Edit echo.rb
89
+
90
+ class Echo < Asmodai::Daemon
91
+ require 'server' # lib is automatically added to the $LOAD_PATH
92
+
93
+ attr_accessor :running
94
+
95
+ def on_signal(sig)
96
+ EventMachine::stop_event_loop
97
+ end
98
+
99
+ def run
100
+ EventMachine::run do
101
+ EventMachine::start_server "127.0.0.1", 8081, Echo::Server
102
+ end
103
+ end
104
+ end
105
+
106
+
107
+
108
+
109
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ namespace :asmodai do
5
+ desc "Runs the unit tests"
6
+ task :test do
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ Dir['test/*_test.rb'].each do |f|
9
+ require f
10
+ end
11
+ end
12
+ end
data/asmodai.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
3
+ require 'asmodai/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "asmodai"
7
+ s.version = Asmodai::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Sebastian Morawietz"]
10
+ s.email = []
11
+ s.homepage = "http://rubygems.org/gems/asmodai"
12
+ s.summary = "A simple daemon generator"
13
+ s.description = "A simple daemon generator"
14
+
15
+ s.required_rubygems_version = ">= 1.3.6"
16
+ s.rubyforge_project = "asmodai"
17
+
18
+ s.add_development_dependency "bundler", ">= 1.0.0.rc.5"
19
+ s.add_dependency 'thor', ">=0.14.6"
20
+
21
+ s.files = (`git ls-files`.split("\n")+Dir['lib/asmodai/generator/templates/*']).uniq
22
+ s.executables = ['asmodai']
23
+ s.require_path = 'lib'
24
+ end
data/bin/asmodai ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'asmodai'
4
+ require 'asmodai/cli'
5
+
6
+ Asmodai.root = Pathname.new(Dir.pwd)
7
+ $LOAD_PATH.unshift(Asmodai.root.join("lib").to_s)
8
+ Asmodai::CLI.start
data/lib/asmodai.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'pathname'
2
+ require 'logger'
3
+
4
+ module Asmodai
5
+ class << self
6
+ attr_accessor :root
7
+
8
+ def log_file_path
9
+ @log_file_path ||=
10
+ Asmodai.root.join("log/#{Asmodai::Info.current.daemon_name}.log")
11
+ end
12
+
13
+ def log_file
14
+ @log_file ||=
15
+ log_file_path.open("a").tap do |r|
16
+ r.sync = true
17
+ end
18
+ end
19
+
20
+ def logger
21
+ @logger ||= Logger.new($stdout).tap do |logger|
22
+ logger.formatter = Logger::Formatter.new
23
+ end
24
+ end
25
+ end
26
+
27
+ require 'asmodai/info'
28
+ require 'asmodai/logging'
29
+ require 'asmodai/daemon'
30
+ end
@@ -0,0 +1,86 @@
1
+ require 'thor'
2
+ require 'thor/group'
3
+ require 'thor/actions'
4
+ require 'active_support'
5
+ require 'active_support/core_ext'
6
+
7
+ class Asmodai::CLI < Thor
8
+ include Thor::Actions
9
+ attr_accessor :app_name
10
+
11
+ def self.source_root
12
+ File.join(File.dirname(__FILE__), "generator")
13
+ end
14
+
15
+ desc "new <appname>", "create a new Asmodai-App"
16
+ def new(name)
17
+ @app_name=name
18
+ empty_directory "#{name}/log"
19
+ empty_directory "#{name}/lib"
20
+ template 'templates/daemon.rb.erb', "#{name}/#{name}.rb"
21
+ copy_file 'templates/Gemfile', "#{name}/Gemfile"
22
+ end
23
+
24
+ desc "install", "Installs startup scripts to /etc/init.d"
25
+ method_option :autostart,
26
+ :type => :boolean,
27
+ :desc => %{If you provide this, startup-links will be generated for the given runlevel. This is currently only supported on Debian/Ubuntu.}
28
+ def install
29
+ @info = Asmodai::Info.current
30
+ path = "/etc/init.d/#{@info.daemon_name}"
31
+ template "templates/init_d.erb", path
32
+ system "chmod a+x #{path}"
33
+ if options[:autostart]
34
+ if (update_bin=`which update-rc.d`.strip).blank?
35
+ warn "update-rc.d was not found. Omitting autostart installation."
36
+ else
37
+ `#{update_bin} #{@info.daemon_name} defaults`
38
+ end
39
+ end
40
+ end
41
+
42
+ desc "foreground", "Runs the daemon in foreground logging to stdout"
43
+ def foreground
44
+ instance=Asmodai::Info.current.daemon_class.new
45
+ instance.foreground
46
+ end
47
+
48
+ desc "start", "Runs the daemon in the background"
49
+ def start
50
+ klass = Asmodai::Info.current.daemon_class
51
+ puts "Starting #{klass.daemon_name}"
52
+ if klass.start
53
+ puts "Ok."
54
+ else
55
+ puts "#{klass.daemon_name} seems to be already running"
56
+ end
57
+ end
58
+
59
+ desc "stop", "Stops the daemon"
60
+ def stop
61
+ Asmodai::Info.current.daemon_class.tap do |klass|
62
+ puts "Stopping #{klass.daemon_name}"
63
+ if klass.terminate
64
+ puts "Ok."
65
+ else
66
+ puts "#{klass.daemon_name} doesn't seem to be running."
67
+ end
68
+ end
69
+ end
70
+
71
+ desc "status", "Prints the current status of the daemon"
72
+ def status
73
+ klass = Asmodai::Info.current.daemon_class
74
+ if !klass.is_running?
75
+ puts "#{klass.daemon_name} doesn't seem to be running"
76
+ else
77
+ puts "#{klass.daemon_name} runs with pid #{klass.pid}"
78
+ end
79
+ end
80
+
81
+ desc "console", "Starts a console with loaded environment"
82
+ def console
83
+ klass = Asmodai::Info.current.daemon_class
84
+ exec "irb -r rubygems -r asmodai -r ./#{klass.daemon_name}"
85
+ end
86
+ end
@@ -0,0 +1,83 @@
1
+ class Asmodai::Daemon
2
+ require 'asmodai/daemon/process_management'
3
+ require 'asmodai/daemon/rake_task'
4
+
5
+ extend Asmodai::Logging
6
+ include Asmodai::Logging
7
+ extend ProcessManagement
8
+ extend RakeTask
9
+
10
+ class << self
11
+ def autoload_paths
12
+ @autoload_paths ||= []
13
+ end
14
+
15
+ def set_daemon_name(name)
16
+ @daemon_name=name.to_s.underscore
17
+ end
18
+
19
+ def daemon_name
20
+ @daemon_name || name.split("::").last.to_s.underscore
21
+ end
22
+ end
23
+
24
+ def initialize
25
+ end
26
+
27
+ def daemon_name
28
+ self.class.daemon_name
29
+ end
30
+
31
+ def on_signal(sig)
32
+ # dummy implementation
33
+ end
34
+
35
+
36
+ def foreground
37
+ prepare_run
38
+ perform_run
39
+ end
40
+
41
+ def start
42
+ prepare_run
43
+ pid = fork do
44
+ $stdin.close
45
+ $stdout.reopen(log_file)
46
+ $stderr.reopen(log_file)
47
+ logger.info "Starting up #{daemon_name} at #{Time.now}, pid: #{Process.pid}"
48
+
49
+ perform_run
50
+ end
51
+
52
+ self.class.pid_file_path.open("w") do |f|
53
+ f.puts pid
54
+ end
55
+
56
+ Process.detach(pid)
57
+ end
58
+
59
+ protected
60
+
61
+ def prepare_run
62
+ %w(INT TERM).each do |sig|
63
+ trap sig do
64
+ logger.info { "Received signal #{sig}"}
65
+ on_signal(sig)
66
+ end
67
+ end
68
+
69
+
70
+ require 'rubygems'
71
+ require 'bundler'
72
+
73
+ Bundler.setup
74
+ Bundler.require
75
+ end
76
+
77
+ def perform_run
78
+ unless respond_to? :run
79
+ raise ArgumentError.new("#{self.class} does not implement 'run'")
80
+ end
81
+ run
82
+ end
83
+ end
@@ -0,0 +1,35 @@
1
+ module Asmodai::Daemon::ProcessManagement
2
+ def pid_file_path
3
+ Asmodai.root.join("log/#{daemon_name}.pid")
4
+ end
5
+
6
+ def pid
7
+ pid_file_path.read.strip.to_i rescue 0
8
+ end
9
+
10
+ def is_running?
11
+ pid_file_path.exist? and ( Process.kill(0,pid); true ) rescue false
12
+ end
13
+
14
+ def start
15
+ if is_running?
16
+ false
17
+ else
18
+ self.new.start
19
+ true
20
+ end
21
+ end
22
+
23
+ def terminate
24
+ if is_running?
25
+ Process.kill('TERM', pid)
26
+ while is_running?
27
+ sleep 0.1
28
+ end
29
+ FileUtils.rm pid_file_path.to_s
30
+ true
31
+ else
32
+ false
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ module Asmodai::Daemon::RakeTask
2
+ def install_rake_task(namespace)
3
+ daemon = self
4
+ namespace.class_eval do
5
+ desc "Run the Master server in the background"
6
+ task :start do
7
+ daemon.start
8
+ end
9
+
10
+ desc "Terminate the Master server"
11
+ task :stop do
12
+ daemon.terminate
13
+ end
14
+
15
+ desc "Run the Master server"
16
+ task :run do
17
+ daemon.new.perform_run
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'activesupport', ">= 3.0.0"
@@ -0,0 +1,18 @@
1
+ class <%= @app_name.camelize %> < Asmodai::Daemon
2
+ attr_accessor :running
3
+
4
+ def on_signal(sig)
5
+ # perform cleanup or stop an eventloop
6
+ self.running=false
7
+ end
8
+
9
+ def run
10
+ # Your real implementation goes here
11
+
12
+ self.running=true
13
+ while running
14
+ logger.info { "I'm still running" }
15
+ sleep 1
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,55 @@
1
+ #! /bin/sh
2
+ ### BEGIN INIT INFO
3
+ # Provides: <%= @info.daemon_name %>
4
+ # Required-Start: $remote_fs $network
5
+ # Required-Stop: $remote_fs $network
6
+ # Default-Start: 2 3 4 5
7
+ # Default-Stop: 0 1 6
8
+ # Short-Description: Example initscript
9
+ # Description: This file should be used to construct scripts to be
10
+ # placed in /etc/init.d.
11
+ ### END INIT INFO
12
+
13
+ DESC="Asmodai-generated daemon"
14
+ NAME="<%= @info.daemon_name %>"
15
+ SCRIPTNAME=/etc/init.d/$NAME
16
+
17
+ do_start()
18
+ {
19
+ sudo -u <%= @info.base_file_owner.name %> bash -l -c "cd <%= @info.path %> && asmodai start"
20
+ }
21
+
22
+ do_stop()
23
+ {
24
+ sudo -u <%= @info.base_file_owner.name %> bash -l -c "cd <%= @info.path %> && asmodai stop"
25
+
26
+ return 0
27
+ }
28
+
29
+ #
30
+ # Function that sends a SIGHUP to the daemon/service
31
+ #
32
+ do_reload() {
33
+ do_stop
34
+ do_start
35
+ return 0
36
+ }
37
+
38
+ case "$1" in
39
+ start)
40
+ do_start
41
+ ;;
42
+ stop)
43
+ do_stop
44
+ ;;
45
+ restart|force-reload)
46
+ do_stop
47
+ do_start
48
+ ;;
49
+ *)
50
+ echo "Usage: $SCRIPTNAME {start|stop|restart}" >&2
51
+ exit 3
52
+ ;;
53
+ esac
54
+
55
+ :
@@ -0,0 +1,43 @@
1
+ require 'pathname'
2
+ require 'etc'
3
+
4
+ class Asmodai::Info
5
+ attr_accessor :path
6
+
7
+ class << self
8
+ def current
9
+ @current||=self.new
10
+ end
11
+ end
12
+
13
+ def initialize(path=Asmodai.root)
14
+ self.path = path
15
+ end
16
+
17
+ # Returns the name of the daemon which is equal to the
18
+ # name of the directory, the daemon lives in
19
+ def daemon_name
20
+ File.basename(self.path)
21
+ end
22
+
23
+ # Returns the camelized daemon name
24
+ def daemon_class_name
25
+ daemon_name.camelize
26
+ end
27
+
28
+ # Returns the class of the Daemon
29
+ def daemon_class
30
+ require "./#{daemon_name}"
31
+ eval(daemon_class_name)
32
+ end
33
+
34
+ # Returns the generated base file where the daemon class is
35
+ # declared in.
36
+ def base_file
37
+ path.join("#{daemon_name}.rb")
38
+ end
39
+
40
+ def base_file_owner
41
+ Etc.getpwuid(Pathname.new(base_file).stat.uid)
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ module Asmodai::Logging
2
+ %w(log_file_path log_file logger).each do |method|
3
+ define_method method do
4
+ Asmodai.send(method)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Asmodai
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,64 @@
1
+ require 'test/test_helper'
2
+
3
+ class DaemonTest < ActiveSupport::TestCase
4
+ TEST_APP_PATH = Pathname.new(File.join(File.dirname(__FILE__), "test_daemon" ))
5
+
6
+ class TestDaemon < Asmodai::Daemon
7
+ attr_accessor :running
8
+
9
+ def on_signal(signal)
10
+ self.running=false
11
+ end
12
+
13
+ def run
14
+ self.running = true
15
+ while running
16
+ puts "Still running #{self}"
17
+ sleep 0.01
18
+ end
19
+ end
20
+ end
21
+
22
+ Asmodai.root = TEST_APP_PATH
23
+
24
+ def setup
25
+ %w(log/test_daemon.log log/test_daemon.pid).map do |e|
26
+ TEST_APP_PATH.join(e)
27
+ end.each do |p|
28
+ p.delete rescue nil
29
+ end
30
+ end
31
+
32
+ test "Naming the daemons" do
33
+ assert_equal "test_daemon", TestDaemon.daemon_name
34
+ end
35
+
36
+ test "Daemon-specific paths" do
37
+ assert_equal TEST_APP_PATH, Asmodai.root
38
+ assert TestDaemon.log_file_path.to_s.match( /test_daemon\.log$/ )
39
+ assert TestDaemon.pid_file_path.to_s.match( /test_daemon\.pid$/ )
40
+ end
41
+
42
+ test "Starting and stopping the daemons" do
43
+ assert !TestDaemon.is_running?
44
+ assert !TestDaemon.log_file_path.exist?
45
+
46
+ TestDaemon.start
47
+ assert TestDaemon.is_running?
48
+ assert TestDaemon.pid > 0
49
+
50
+ assert TestDaemon.log_file_path.exist?
51
+
52
+ # Wait until some lines have been written to the log file
53
+ while TestDaemon.log_file_path.size < 500
54
+ sleep 0.1
55
+ end
56
+
57
+ TestDaemon.terminate
58
+ assert !TestDaemon.pid_file_path.exist?
59
+ assert TestDaemon.log_file_path.size > 0
60
+ TestDaemon.log_file_path.open do |f|
61
+ assert f.lines.to_a.last.match( /Received signal TERM/)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,7 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "../lib"))
2
+
3
+ require 'test/unit'
4
+ require 'active_support/test_case'
5
+ require 'asmodai'
6
+
7
+ ASMODAI_APP_ROOT = Pathname.new(File.join(File.dirname(__FILE__), ".."))
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: asmodai
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Sebastian Morawietz
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-01-21 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: bundler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 0
31
+ - 0
32
+ - rc
33
+ - 5
34
+ version: 1.0.0.rc.5
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: thor
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ segments:
46
+ - 0
47
+ - 14
48
+ - 6
49
+ version: 0.14.6
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: A simple daemon generator
53
+ email: []
54
+
55
+ executables:
56
+ - asmodai
57
+ extensions: []
58
+
59
+ extra_rdoc_files: []
60
+
61
+ files:
62
+ - .bundle/config
63
+ - .gitignore
64
+ - Gemfile
65
+ - Gemfile.lock
66
+ - README.md
67
+ - Rakefile
68
+ - asmodai.gemspec
69
+ - bin/asmodai
70
+ - lib/asmodai.rb
71
+ - lib/asmodai/cli.rb
72
+ - lib/asmodai/daemon.rb
73
+ - lib/asmodai/daemon/process_management.rb
74
+ - lib/asmodai/daemon/rake_task.rb
75
+ - lib/asmodai/generator/templates/Gemfile
76
+ - lib/asmodai/generator/templates/daemon.rb.erb
77
+ - lib/asmodai/generator/templates/init_d.erb
78
+ - lib/asmodai/info.rb
79
+ - lib/asmodai/logging.rb
80
+ - lib/asmodai/version.rb
81
+ - test/daemon_test.rb
82
+ - test/test_daemon/.gitignore
83
+ - test/test_helper.rb
84
+ has_rdoc: true
85
+ homepage: http://rubygems.org/gems/asmodai
86
+ licenses: []
87
+
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ segments:
99
+ - 0
100
+ version: "0"
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ segments:
107
+ - 1
108
+ - 3
109
+ - 6
110
+ version: 1.3.6
111
+ requirements: []
112
+
113
+ rubyforge_project: asmodai
114
+ rubygems_version: 1.3.7
115
+ signing_key:
116
+ specification_version: 3
117
+ summary: A simple daemon generator
118
+ test_files: []
119
+