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 +5 -0
- data/.irbrc +4 -0
- data/Freyrfile +3 -0
- data/LICENSE +20 -0
- data/README.markdown +73 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/bin/freyr +7 -0
- data/freyr.gemspec +73 -0
- data/lib/freyr/cli/helpers.rb +113 -0
- data/lib/freyr/cli/launching.rb +74 -0
- data/lib/freyr/cli/management.rb +21 -0
- data/lib/freyr/cli/monitor.rb +58 -0
- data/lib/freyr/cli.rb +27 -0
- data/lib/freyr/command.rb +269 -0
- data/lib/freyr/pinger.rb +52 -0
- data/lib/freyr/service.rb +111 -0
- data/lib/freyr/service_info.rb +89 -0
- data/lib/freyr.rb +9 -0
- data/spec/freyr_spec.rb +7 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +9 -0
- metadata +134 -0
data/.document
ADDED
data/.irbrc
ADDED
data/Freyrfile
ADDED
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
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
|
data/lib/freyr/pinger.rb
ADDED
@@ -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
data/spec/freyr_spec.rb
ADDED
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
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
|