freyr 0.3.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/.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