freyr 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.irbrc ADDED
@@ -0,0 +1,4 @@
1
+ require File.dirname(__FILE__) + '/lib/freyr.rb'
2
+ include Freyr
3
+
4
+ Service.add_file("Freyrfile")
data/Freyrfile ADDED
@@ -0,0 +1,3 @@
1
+ service :sleep do
2
+ start 'sudo sleep 20'
3
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tal Atlas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,73 @@
1
+ # freyr
2
+
3
+ Manage all your application's services.
4
+
5
+ Freyr will start any services you want in the background and capture stdout into a log file for you to view whenever you want.
6
+ Great for if you are working on an app which has peripheral services that need to be run.
7
+
8
+ ## Basic use
9
+
10
+ $ gem install freyr
11
+
12
+ Install freyr it'll also give you a binary named freyr.
13
+
14
+ To use it you can create a Freyrfile or .freyrrc. It will automatically look for those files in whatever your current
15
+ directory is. It will also look for a .freyrrc in your home directory. You can also specify a file manually.
16
+
17
+ The file has this basic structure:
18
+
19
+ service :memcache do
20
+ start 'memcached -vvv'
21
+ end
22
+
23
+ That will allow you to start, stop, restart, and watch the STDOUT of a service.
24
+
25
+ Basic usage can be done like
26
+
27
+ $ freyr start memcache
28
+
29
+ Just calling `freyr` or `freyr list` will output all of the services being tracked and their status.
30
+
31
+ ## Some more options
32
+
33
+ A few more of the options for defining a service are:
34
+
35
+ * start/stop/restart - Commands to be run when you send any of those individual commands. If stop is not defined it will
36
+ default to sending a KILL signal, if restart is not defined it will run stop then start.
37
+ * dir - The directory to run the file in, defaults to '/' (considering defaulting to current directory)
38
+ * proc_match - a string or regular expression to check for after starting. This is useful if freyr can't capture the pid
39
+ from a normal launch, eg when launching though a shell script.
40
+ * ping - A url to ping to alert you when the service is up and running, will let you know if there's an error.
41
+ * group - assign this service to a group. Calling start/stop/restart on the group will run across all of the members
42
+
43
+ See a complete list of definition options [here](https://github.com/Talby/freyr/wiki/Service-Definition-Options)
44
+
45
+ These options also give you some more options with to use with the CLI
46
+
47
+ * `freyr update_pid [service]` - Updates the pid stored by checking for the proc_match. Useful if you started the service
48
+ by some method other than freyr or it restarted with a different pid.
49
+ * `freyr ping` - Tries to ping the service once
50
+ * `freyr -p` - Shows you all services and the status of pings for each.
51
+
52
+ To see a complete list of options type in `freyr help`.
53
+
54
+ ## TODO
55
+
56
+ * Growl notifications
57
+ * Better error handling
58
+ * Service definition validation
59
+ * Plugin architecture
60
+
61
+ ## Note on Patches/Pull Requests
62
+
63
+ * Fork the project.
64
+ * Make your feature addition or bug fix.
65
+ * Add tests for it. This is important so I don't break it in a
66
+ future version unintentionally.
67
+ * Commit, do not mess with rakefile, version, or history.
68
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
69
+ * Send me a pull request. Bonus points for topic branches.
70
+
71
+ ## Copyright
72
+
73
+ Copyright (c) 2010 Tal Atlas. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "freyr"
8
+ gem.summary = %Q{Service manager and runner}
9
+ gem.description = %Q{Define all services you need to run and this will launch,daemonize,and monitor them for you.}
10
+ gem.email = "me@tal.by"
11
+ gem.homepage = "http://github.com/Talby/freyr"
12
+ gem.authors = ["Tal Atlas"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ gem.add_development_dependency "yard", ">= 0"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ gem.add_dependency 'thor', ">= 0.10"
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ begin
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
28
+ end
29
+
30
+
31
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.pattern = 'spec/**/*_spec.rb'
34
+ spec.rcov = true
35
+ end
36
+
37
+ task :spec => :check_dependencies
38
+
39
+ task :default => :spec
40
+ rescue LoadError
41
+ puts "Expecting rspec to be installed. Please install with: gem install rspec"
42
+ end
43
+
44
+ begin
45
+ require 'yard'
46
+ YARD::Rake::YardocTask.new
47
+ rescue LoadError
48
+ task :yardoc do
49
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
50
+ end
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
data/bin/freyr ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
+ require 'freyr'
5
+ require 'freyr/cli'
6
+
7
+ Freyr::CLI.start
data/freyr.gemspec ADDED
@@ -0,0 +1,73 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{freyr}
8
+ s.version = "0.3.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Tal Atlas"]
12
+ s.date = %q{2011-01-24}
13
+ s.default_executable = %q{freyr}
14
+ s.description = %q{Define all services you need to run and this will launch,daemonize,and monitor them for you.}
15
+ s.email = %q{me@tal.by}
16
+ s.executables = ["freyr"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.markdown"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".irbrc",
24
+ "Freyrfile",
25
+ "LICENSE",
26
+ "README.markdown",
27
+ "Rakefile",
28
+ "VERSION",
29
+ "bin/freyr",
30
+ "freyr.gemspec",
31
+ "lib/freyr.rb",
32
+ "lib/freyr/cli.rb",
33
+ "lib/freyr/cli/helpers.rb",
34
+ "lib/freyr/cli/launching.rb",
35
+ "lib/freyr/cli/management.rb",
36
+ "lib/freyr/cli/monitor.rb",
37
+ "lib/freyr/command.rb",
38
+ "lib/freyr/pinger.rb",
39
+ "lib/freyr/service.rb",
40
+ "lib/freyr/service_info.rb",
41
+ "spec/freyr_spec.rb",
42
+ "spec/spec.opts",
43
+ "spec/spec_helper.rb"
44
+ ]
45
+ s.homepage = %q{http://github.com/Talby/freyr}
46
+ s.require_paths = ["lib"]
47
+ s.rubygems_version = %q{1.3.7}
48
+ s.summary = %q{Service manager and runner}
49
+ s.test_files = [
50
+ "spec/freyr_spec.rb",
51
+ "spec/spec_helper.rb"
52
+ ]
53
+
54
+ if s.respond_to? :specification_version then
55
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
+ s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
60
+ s.add_development_dependency(%q<yard>, [">= 0"])
61
+ s.add_runtime_dependency(%q<thor>, [">= 0.10"])
62
+ else
63
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
64
+ s.add_dependency(%q<yard>, [">= 0"])
65
+ s.add_dependency(%q<thor>, [">= 0.10"])
66
+ end
67
+ else
68
+ s.add_dependency(%q<rspec>, [">= 1.2.9"])
69
+ s.add_dependency(%q<yard>, [">= 0"])
70
+ s.add_dependency(%q<thor>, [">= 0.10"])
71
+ end
72
+ end
73
+
@@ -0,0 +1,113 @@
1
+ module Freyr
2
+ class CLI < Thor
3
+
4
+ private
5
+ # Ugh, this is ugly, colorizing stuff is rough
6
+ def list_all_services(args={})
7
+ args[:highlight_name] = (args[:highlight_name]||[]).collect {|n| n.to_s}
8
+ args[:highlight_state] = (args[:highlight_state]||[]).collect {|n| n.to_s}
9
+
10
+ max_length = 0
11
+
12
+ groups_join = '|'
13
+
14
+ lengths = Service.s.collect do |s|
15
+ n = " #{s.name}(#{s.groups.join(groups_join)})"
16
+ max_length = n.length if n.length > max_length
17
+ n.length
18
+ end
19
+
20
+ max_length += 3 # min distance between name and group
21
+
22
+ strs = []
23
+ Service.s.each_with_index do |s,i|
24
+ str = ' '
25
+ if s.sudo
26
+ str << set_color('*', :yellow)
27
+ else
28
+ str << ' '
29
+ end
30
+
31
+ name = set_color(s.name, :blue)
32
+ if args[:highlight_name].include?(s.name.to_s)
33
+ name = set_color(name, :on_white)
34
+ end
35
+ str << name
36
+ str << " "*(max_length - lengths[i])
37
+ if s.groups.empty?
38
+ str << ' '
39
+ else
40
+ str << '('
41
+ s.groups.each do |g|
42
+ str << set_color(g,:red)
43
+ next if g == s.groups.last
44
+ str << groups_join
45
+ end
46
+ str << ')'
47
+ end
48
+
49
+ str << ' - '
50
+ if s.alive?
51
+ state = set_color(' Alive ', :green, false)
52
+ else
53
+ state = set_color(' Dead ', :red, true)
54
+ end
55
+
56
+ if args[:highlight_state].include?(s.name.to_s)
57
+ state = set_color(state, :on_white)
58
+ end
59
+
60
+ str << state
61
+
62
+ if args[:ping] && s.alive? && pinger = s.ping!
63
+ png = "(#{pinger.code})"
64
+ if pinger.success?
65
+ png = set_color(png, :green, false)
66
+ elsif pinger.server_error?
67
+ png = set_color(png, :yellow, true)
68
+ else
69
+ png = set_color(png, :red, true)
70
+ end
71
+
72
+ str << png
73
+ elsif args[:ping]
74
+ str << ' '*'(123)'.size
75
+ end
76
+
77
+ str << "\n" #if str =~ /\s$/ # Thor's display only adds a new line if the last char isn't a space
78
+
79
+ strs << str
80
+ end
81
+
82
+ strs
83
+ end
84
+
85
+ def set_color *args
86
+ @shell.set_color(*args)
87
+ end
88
+
89
+ def get_from_name name
90
+ name ||= Dir.pwd.match(/.*\/(.+)/)[1]
91
+
92
+ if options.namespace && s = Service["#{options.namespace}:#{name}"].first
93
+ [s] # only pickng one because if it's namespaced it's not a group
94
+ else
95
+ Service[name]
96
+ end
97
+ end
98
+
99
+ def get_services
100
+ if options['config-file'] && !options['config-file'].empty?
101
+ if File.exist?(options['config-file'])
102
+ Service.add_file(options['config-file'])
103
+ else
104
+ say("Can't find file #{options['config-file']}",:red)
105
+ end
106
+ end
107
+
108
+ ['Freyrfile','.freyrrc','~/.freyrrc'].each do |f|
109
+ Service.add_file(f)
110
+ end unless options['ignore-local']
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,74 @@
1
+ module Freyr
2
+ class CLI < Thor
3
+ # TODO: DRY up start/restart/stop commands
4
+
5
+ desc 'start [SERVICE=dirname]', 'Start particular service'
6
+ def start(name=nil)
7
+ services = get_from_name(name)
8
+ if !services.empty?
9
+ names = services.collect {|s| s.name}
10
+ say "Starting the " << set_color(names.join(', '), :blue) << ' services'
11
+
12
+ changed_names = services.collect do |s|
13
+ begin
14
+ pid = s.start!
15
+ s.name if pid
16
+ rescue AdminRequired
17
+ say "Please run in sudo to launch #{s.name}.", :red
18
+ nil
19
+ end
20
+ end.compact
21
+
22
+ list_all_services(:highlight_state => changed_names).each {|l| say(l)}
23
+ else
24
+ say "Can't find the #{name} service", :red
25
+ end
26
+ end
27
+
28
+ desc 'stop [SERVICE=dirname]', 'Stop particular service'
29
+ def stop(name=nil)
30
+ services = get_from_name(name)
31
+ if !services.empty?
32
+ names = services.collect {|s| s.name}
33
+ say "Stopping the " << set_color(names.join(', '), :blue) << ' services'
34
+ changed_names = services.collect {|s| s.name if s.alive?}.compact
35
+
36
+ services.each do |s|
37
+ begin
38
+ s.stop!
39
+ rescue AdminRequired
40
+ say "Please run in sudo to stop #{s.name}.", :red
41
+ end
42
+ end
43
+
44
+ list_all_services(:highlight_state => changed_names).each {|l| say(l)}
45
+ else
46
+ say "Can't find the #{name} service", :red
47
+ end
48
+ end
49
+
50
+ desc 'restart [SERVICE=dirname]', 'restart particular service'
51
+ def restart(name=nil)
52
+ services = get_from_name(name)
53
+ if !services.empty?
54
+ names = services.collect {|s| s.name}
55
+ say "Restarting the " << set_color(names.join(', '), :blue) << ' services'
56
+
57
+ services.each do |s|
58
+ begin
59
+ s.restart!
60
+ rescue AdminRequired
61
+ say "Please run in sudo to launch #{s.name}.", :red
62
+ name.delete(s.name)
63
+ nil
64
+ end
65
+ end
66
+
67
+ list_all_services(:highlight_state => names).each {|l| say(l)}
68
+ else
69
+ say "Can't find the #{name} service", :red
70
+ end
71
+ end
72
+
73
+ end
74
+ end
@@ -0,0 +1,21 @@
1
+ module Freyr
2
+ class CLI < Thor
3
+
4
+ desc 'update_pid [SERVICE=dirname]', 'Update pid from proc_match (good to use if service already launched)'
5
+ def update_pid(name=nil)
6
+ services = get_from_name(name)
7
+ if s = services.first
8
+ if pid = s.command.update_pid
9
+ say "Updated pid for "<< set_color(s.name,:blue) << ' to ' << set_color(pid,:red)
10
+ elsif s.proc_match
11
+ say "Couldn't find pid for process matcher #{s.proc_match.inspect}", :red
12
+ else
13
+ say "Service #{s.name} doesn't have a value for proc_match set.", :red
14
+ end
15
+ else
16
+ say "Couldn't find service with name #{name}.", :red
17
+ end
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,58 @@
1
+ module Freyr
2
+ class CLI < Thor
3
+
4
+ desc 'list', 'lists all available services:'
5
+ method_option :ping, :type => :boolean, :default => false, :aliases => '-p', :desc => 'also ping each service which can ping'
6
+ def list
7
+ strs = list_all_services(:ping => options.ping?)
8
+
9
+ if strs.empty?
10
+ say "No services available", :red
11
+ else
12
+ say "List of all available services (#{set_color('*', :yellow)} denotes root proc)"
13
+ strs.each_with_index do |s,i|
14
+ say s
15
+ end
16
+ end
17
+ end
18
+
19
+ desc 'tail [SERVICE=dirname]', 'read stdout of the service'
20
+ method_option :lines, :type => :numeric, :default => 50, :desc => 'Number of lines to show on initial tail'
21
+ method_option :'no-follow', :type => :boolean, :default => false, :desc => 'Disable auto follow, just print tail and exit'
22
+ def tail(name=nil)
23
+ services = get_from_name(name)
24
+ if !services.empty?
25
+
26
+ services.first.tail!(options.lines, !options['no-follow'])
27
+ else
28
+ say "Can't find the #{name} service", :red
29
+ end
30
+ end
31
+
32
+ desc 'ping', 'see the response from pinging the url'
33
+ def ping(name=nil)
34
+ service = get_from_name(name).first
35
+
36
+ if service
37
+ if service.ping
38
+ pinger = Pinger.new(service)
39
+ resp = pinger.ping
40
+ if pinger.success?
41
+ say "Up and running", :green
42
+ elsif pinger.server_error?
43
+ say "500 Error"
44
+ elsif resp
45
+ say "Returned #{resp.code} code", :red
46
+ else
47
+ say "Couldn't reach service", :red
48
+ end
49
+ else
50
+ say 'No url to ping for this service'
51
+ end
52
+ else
53
+ say "Can't find the #{name} service", :red
54
+ end
55
+ end
56
+
57
+ end
58
+ end
data/lib/freyr/cli.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'thor'
2
+ %w{launching management monitor helpers}.each do |f|
3
+ require File.expand_path(File.dirname(__FILE__)+"/cli/#{f}.rb")
4
+ end
5
+
6
+ module Freyr
7
+ class CLI < Thor
8
+ include Thor::Actions
9
+
10
+ def initialize(*)
11
+ super
12
+ get_services
13
+ end
14
+
15
+ default_task :list
16
+ class_option :'config-file', :desc => 'config file to use', :type => :string
17
+ class_option :'ignore-local', :desc => "don't use the local Freyrfile or .freyrrc", :type => :boolean, :default => false
18
+ class_option :namespace, :type => :string, :desc => 'namespace to look in'
19
+ map "-v" => :version
20
+
21
+ desc 'version', 'displays current gem version'
22
+ def version
23
+ say Freyr::VERSION
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,269 @@
1
+ module Freyr
2
+
3
+ class Command
4
+ extend Forwardable
5
+
6
+ ROOT_PIDS = '/var/run/freyr'
7
+ FREYR_PIDS = ENV['USER'] == 'root' ? ROOT_PIDS : File.expand_path('.freyr', '~')
8
+
9
+ if !File.exist?(FREYR_PIDS)
10
+ Dir.mkdir(FREYR_PIDS)
11
+ elsif !File.directory?(FREYR_PIDS)
12
+ File.delete(FREYR_PIDS)
13
+ Dir.mkdir(FREYR_PIDS)
14
+ end
15
+
16
+ ServiceInfo::ATTRS.each do |meth|
17
+ define_method(meth) do |*args|
18
+ @service.__send__(meth,*args) if @service
19
+ end
20
+ end
21
+
22
+ attr_reader :command, :name, :service
23
+
24
+ def initialize(name, command=nil, args = {})
25
+ if name.is_a?(Service)
26
+ @service = name
27
+ @name = service.name
28
+ @command = service.start_command
29
+ @env = service.env
30
+ else
31
+ @name = name
32
+ @command = command
33
+
34
+ @env = args[:env]
35
+ end
36
+ end
37
+
38
+ def pid_file
39
+ File.join(file_dir,"#{@name}.pid")
40
+ end
41
+
42
+ def file_dir
43
+ admin? ? ROOT_PIDS : FREYR_PIDS
44
+ end
45
+
46
+ def read_pid
47
+ return unless File.exist?(pid_file)
48
+ p = File.open(pid_file).read
49
+ p ? p.to_i : nil
50
+ end
51
+
52
+ def pid(force = false)
53
+ @pid = nil if force
54
+ @pid ||= read_pid
55
+ end
56
+
57
+ def alive?
58
+ return unless pid
59
+ Process.getpgid(pid)
60
+ true
61
+ rescue Errno::ESRCH
62
+ retried = false unless defined?(retried)
63
+
64
+ if !retried && @pid = pid_from_procname
65
+ save
66
+ retried = true
67
+ retry
68
+ end
69
+
70
+ if pid(true)
71
+ File.delete(pid_file) unless admin? && !is_root?
72
+ end
73
+
74
+ false
75
+ end
76
+
77
+ def delete_if_dead
78
+ return if admin? && !is_root?
79
+ File.delete(pid_file) unless alive?
80
+ end
81
+
82
+ def save
83
+ if File.exist?(pid_file)
84
+ old_pid = read_pid
85
+ begin
86
+ Process.kill('KILL', old_pid) if old_pid && old_pid.to_s != @pid.to_s
87
+ rescue Errno::ESRCH
88
+ end
89
+ end
90
+
91
+ File.open(pid_file, 'w') {|f| f.write(@pid)}
92
+ end
93
+
94
+ def run!
95
+ return unless command
96
+ kill! if alive?
97
+
98
+ require_admin
99
+
100
+ total_time = Time.now
101
+
102
+ pid = spawn(command)
103
+
104
+ @pid = pid
105
+
106
+ Process.detach(@pid)
107
+
108
+ if proc_match
109
+ puts "Waiting for pid from match of #{proc_match.inspect}"
110
+
111
+ start = Time.now
112
+ until (pid = pid_from_procname) || (Time.now-start) > 40
113
+ print '.'; STDOUT.flush
114
+ sleep(0.2)
115
+ end
116
+
117
+ raise "\nCouldnt find pid after 40 seconds" unless pid
118
+
119
+ puts '*'
120
+
121
+ @pid = pid
122
+ end
123
+
124
+ puts "PID of new #{name} process is #{@pid}"
125
+ save
126
+
127
+ if ping
128
+ pinger = Pinger.new(self)
129
+
130
+ puts '',"Waiting for response from #{pinger.url}"
131
+
132
+ # Move this pinger code somewhere else
133
+ start = Time.now
134
+ begin
135
+ print '.'; STDOUT.flush
136
+ pinger.ping
137
+ sleep(0.6)
138
+ end until pinger.server_probably_launched? || (Time.now-start) > 40 || !alive?
139
+
140
+ if alive?
141
+
142
+ if pinger.response
143
+ puts '*',"Last response recieved with code #{pinger.response.code}"
144
+ else
145
+ puts 'x',"Couldn't reach #{name} service"
146
+ end
147
+ else
148
+ puts 'x'
149
+ end
150
+ end
151
+
152
+ if alive?
153
+ puts "Launch took about #{(Time.now-total_time).ceil} seconds"
154
+
155
+ @pid
156
+ else
157
+ puts "#{name} service wasn't launched correctly. For details see: #{log}"
158
+ delete_if_dead
159
+ end
160
+ end
161
+
162
+ def update_pid
163
+ if @pid = pid_from_procname
164
+ save
165
+ @pid
166
+ end
167
+ end
168
+
169
+ def pid_from_procname
170
+ return unless proc_match
171
+
172
+ pids = `ps -eo pid,command`.split("\n").inject({}) do |r, pid|
173
+ if m = pid.match(/^\s*(\d+)\s(.+)$/)
174
+ r[m[2]] = m[1].to_i
175
+ end
176
+ r
177
+ end
178
+
179
+ if procline = pids.keys.find {|p| p.match(proc_match)}
180
+ pids[procline]
181
+ end
182
+ end
183
+
184
+ def kill!(sig=nil)
185
+ require_admin
186
+ sig ||= stop_sig || 'KILL'
187
+
188
+ if pid(true)
189
+ Process.kill(sig, pid)
190
+ end
191
+ end
192
+
193
+ def restart!
194
+ require_admin
195
+
196
+ if restart
197
+ chdir
198
+ system(restart)
199
+ update_pid
200
+ elsif restart_sig
201
+ kill!(restart_sig)
202
+ else
203
+ run!
204
+ end
205
+ end
206
+
207
+ def admin?
208
+ sudo
209
+ end
210
+
211
+ private
212
+
213
+ def require_admin
214
+ raise AdminRequired if admin? && !is_root?
215
+ end
216
+
217
+ def is_root?
218
+ ENV['USER'] == 'root'
219
+ end
220
+
221
+ def chdir
222
+ Dir.chdir File.expand_path(dir||'/')
223
+ end
224
+
225
+ def spawn(command)
226
+ fork do
227
+ # File.umask self.umask if self.umask
228
+ # uid_num = Etc.getpwnam(self.uid).uid if uid
229
+ # gid_num = Etc.getgrnam(self.gid).gid if gid
230
+
231
+ # ::Dir.chroot(self.chroot) if self.chroot
232
+ ::Process.setsid
233
+ # ::Process.groups = [gid_num] if self.gid
234
+ # ::Process::Sys.setgid(gid_num) if self.gid
235
+ # ::Process::Sys.setuid(uid_num) if self.uid
236
+ chdir
237
+ $0 = "freyr - #{name} (#{command})"
238
+ STDIN.reopen "/dev/null"
239
+ if log_cmd
240
+ STDOUT.reopen IO.popen(log_cmd, "a")
241
+ elsif log
242
+ STDOUT.reopen log, "a"
243
+ end
244
+ if err_log_cmd
245
+ STDERR.reopen IO.popen(err_log_cmd, "a")
246
+ elsif err_log && (log_cmd || err_log != log)
247
+ STDERR.reopen err_log, "a"
248
+ else
249
+ STDERR.reopen STDOUT
250
+ end
251
+
252
+ # close any other file descriptors --- I think this is for anything after stderr
253
+ 3.upto(256){|fd| IO::new(fd).close rescue nil}
254
+
255
+ if @env && @env.is_a?(Hash)
256
+ @env.each do |(key, value)|
257
+ ENV[key] = value
258
+ end
259
+ end
260
+
261
+ exec(command) unless command.empty?
262
+ end
263
+ end
264
+
265
+ end
266
+
267
+ class AdminRequired < Errno::EACCES; end
268
+
269
+ end
@@ -0,0 +1,52 @@
1
+ require 'net/http'
2
+
3
+ module Freyr
4
+ class Pinger
5
+ # URL to ping
6
+ attr_reader :url
7
+ # Time that the ping took
8
+ attr_reader :time
9
+ # Response object
10
+ attr_reader :response
11
+
12
+ def initialize(command)
13
+ @command = command
14
+ @url = command.ping
15
+ end
16
+
17
+ # The URI object for the given URL
18
+ def uri
19
+ @uri ||= URI.parse(url)
20
+ end
21
+
22
+ # Send a ping to the url
23
+ def ping
24
+ t = Time.now
25
+ @response = Net::HTTP.get_response(uri)
26
+ rescue Errno::ECONNREFUSED
27
+ ensure
28
+ @time = Time.now-t
29
+ @response
30
+ end
31
+
32
+ def code
33
+ response.code if response
34
+ end
35
+
36
+ # Did the response recieve a success http code
37
+ def success?
38
+ response.is_a?(Net::HTTPSuccess)
39
+ end
40
+
41
+ # Did the response recieve a 500 error
42
+ def server_error?
43
+ response.is_a?(Net::HTTPInternalServerError)
44
+ end
45
+
46
+ # Did it recieve 2xx or 500
47
+ def server_probably_launched?
48
+ success? || server_error?
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,111 @@
1
+ module Freyr
2
+ class Service
3
+ extend Forwardable
4
+
5
+ attr_reader :service_info, :command
6
+
7
+ def initialize(s)
8
+ @service_info = s
9
+ @command = Command.new(self)
10
+ end
11
+
12
+ def_delegators :@service_info, *ServiceInfo::ATTRS
13
+ def_delegator :@service_info, :start, :start_command
14
+
15
+ def env
16
+ service_info.env || {}
17
+ end
18
+
19
+ def log
20
+ @service_info.log || File.join(command.file_dir,"#{name}.log")
21
+ end
22
+
23
+ def start!
24
+ return unless start_command
25
+ command.run! unless alive?
26
+ end
27
+
28
+ def stop!
29
+ command.kill! if start_command
30
+ end
31
+
32
+ def restart!
33
+ command.restart!
34
+ end
35
+
36
+ def is_group?(name)
37
+ groups.find {|g| g.to_s == name.to_s}
38
+ end
39
+
40
+ def alive?
41
+ command.alive?
42
+ end
43
+
44
+ def ping!
45
+ if ping
46
+ pinger = Pinger.new(self)
47
+ pinger.ping
48
+ pinger
49
+ end
50
+ end
51
+
52
+ def tail!(size = 600, follow = true)
53
+ f = follow ? 'f' : ''
54
+ exec("tail -#{size}#{f} #{log}")
55
+ end
56
+
57
+ def describe
58
+ %Q{#{name}(#{groups.join(',')}) - #{start_command}}
59
+ end
60
+
61
+ def matches?(n)
62
+ n = n.to_s
63
+ return true if name.to_s == n
64
+
65
+ also.find {|a| a.to_s == n}
66
+ end
67
+
68
+ class << self
69
+
70
+ def s
71
+ @all_services ||= []
72
+ end
73
+
74
+ def names
75
+ @all_names ||= []
76
+ end
77
+
78
+ def groups
79
+ @all_groups ||= []
80
+ end
81
+
82
+ def selectors
83
+ names+groups
84
+ end
85
+
86
+ def add_file f
87
+ s
88
+
89
+ services = ServiceInfo.from_file(f).collect do |ser|
90
+ raise 'Cannot have two things of the same name' if selectors.include?(ser.name)
91
+ names << ser.name
92
+ @all_groups |= ser.groups
93
+ new(ser)
94
+ end
95
+
96
+ @all_services += services
97
+ end
98
+
99
+ def [](name)
100
+ if ser = s.find {|sr| sr.matches?(name)}
101
+ [ser]
102
+ else
103
+ s.select {|sr| sr.is_group?(name)}
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,89 @@
1
+ module Freyr
2
+ class ServiceInfo
3
+ attr_reader :groups
4
+
5
+ ATTRS = [:name,:dir,:log_cmd,:log,:err_log_cmd,:err_log,:umask,
6
+ :uid,:gid,:chroot,:proc_match,:restart,:stop,:stop_sig,
7
+ :restart_sig,:sudo,:groups,:ping,:also]
8
+
9
+ def initialize(name=nil, args={}, &block)
10
+ @groups = []
11
+ @also = []
12
+ if name.is_a?(Hash)
13
+ @name = name.keys.first
14
+ @groups << name[@name]
15
+ else
16
+ @name = name
17
+ end
18
+
19
+ instance_eval(&block)
20
+ end
21
+
22
+ def use_sudo
23
+ @sudo = true
24
+ end
25
+
26
+ def group(*val)
27
+ @groups |= val
28
+ end
29
+
30
+ def also_as(*val)
31
+ @also |= val
32
+ end
33
+
34
+ MODIFIERS = {
35
+ :start => :_sudo_checker,
36
+ :restart => :_sudo_checker,
37
+ :stop => :_sudo_checker
38
+ }
39
+
40
+ def method_missing key, val=nil
41
+ key = key.to_s.gsub(/\=$/,'').to_sym
42
+
43
+ if val
44
+ val = send(MODIFIERS[key],val) if MODIFIERS[key]
45
+ instance_variable_set("@#{key}", val)
46
+ else
47
+ instance_variable_get("@#{key}")
48
+ end
49
+ end
50
+
51
+ SUDO_MATCHER = /^sudo\s+/
52
+
53
+ private
54
+
55
+ # If someone doesn't explicitly not want the script running as admin
56
+ # and the val looks like it's supposed to be run in sudo remove sudo
57
+ # and force to be run as admin
58
+ def _sudo_checker(val)
59
+ if @admin != false && val =~ SUDO_MATCHER
60
+ val.gsub!(SUDO_MATCHER,'')
61
+ use_sudo
62
+ end
63
+ val.chomp
64
+ end
65
+
66
+ class << self
67
+
68
+ def from_file file
69
+ file = File.expand_path(file)
70
+ return [] unless File.exist?(file)
71
+ @added_services = []
72
+ instance_eval(File.open(file).read)
73
+ @added_services
74
+ end
75
+
76
+ private
77
+
78
+ def namespace name
79
+ @namespace = name
80
+ end
81
+
82
+ def service name=nil, &blk
83
+ name = "#{@namespace}:#{name}" if @namespace
84
+ @added_services << new(name,&blk)
85
+ end
86
+ end
87
+
88
+ end
89
+ end
data/lib/freyr.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'forwardable'
2
+
3
+ module Freyr
4
+ VERSION = File.open(File.expand_path(File.dirname(__FILE__)+'/../VERSION')).read
5
+ end
6
+
7
+ %w{service_info service command pinger}.each do |f|
8
+ require File.expand_path(File.dirname(__FILE__)+"/freyr/#{f}.rb")
9
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "Freyr" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,9 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'freyr'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: freyr
3
+ version: !ruby/object:Gem::Version
4
+ hash: 19
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 0
10
+ version: 0.3.0
11
+ platform: ruby
12
+ authors:
13
+ - Tal Atlas
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-24 00:00:00 -05:00
19
+ default_executable: freyr
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 1
32
+ - 2
33
+ - 9
34
+ version: 1.2.9
35
+ type: :development
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: yard
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: thor
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 31
60
+ segments:
61
+ - 0
62
+ - 10
63
+ version: "0.10"
64
+ type: :runtime
65
+ version_requirements: *id003
66
+ description: Define all services you need to run and this will launch,daemonize,and monitor them for you.
67
+ email: me@tal.by
68
+ executables:
69
+ - freyr
70
+ extensions: []
71
+
72
+ extra_rdoc_files:
73
+ - LICENSE
74
+ - README.markdown
75
+ files:
76
+ - .document
77
+ - .irbrc
78
+ - Freyrfile
79
+ - LICENSE
80
+ - README.markdown
81
+ - Rakefile
82
+ - VERSION
83
+ - bin/freyr
84
+ - freyr.gemspec
85
+ - lib/freyr.rb
86
+ - lib/freyr/cli.rb
87
+ - lib/freyr/cli/helpers.rb
88
+ - lib/freyr/cli/launching.rb
89
+ - lib/freyr/cli/management.rb
90
+ - lib/freyr/cli/monitor.rb
91
+ - lib/freyr/command.rb
92
+ - lib/freyr/pinger.rb
93
+ - lib/freyr/service.rb
94
+ - lib/freyr/service_info.rb
95
+ - spec/freyr_spec.rb
96
+ - spec/spec.opts
97
+ - spec/spec_helper.rb
98
+ has_rdoc: true
99
+ homepage: http://github.com/Talby/freyr
100
+ licenses: []
101
+
102
+ post_install_message:
103
+ rdoc_options: []
104
+
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ none: false
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ hash: 3
113
+ segments:
114
+ - 0
115
+ version: "0"
116
+ required_rubygems_version: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ hash: 3
122
+ segments:
123
+ - 0
124
+ version: "0"
125
+ requirements: []
126
+
127
+ rubyforge_project:
128
+ rubygems_version: 1.3.7
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: Service manager and runner
132
+ test_files:
133
+ - spec/freyr_spec.rb
134
+ - spec/spec_helper.rb