asmodai 0.1.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/.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
+