freyr 0.4.2 → 0.5.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/.irbrc CHANGED
@@ -1,4 +1,5 @@
1
1
  require File.dirname(__FILE__) + '/lib/freyr.rb'
2
+ Freyr.logger = Logger.new(STDOUT)
2
3
  include Freyr
3
4
 
4
5
  Service.add_file("Freyrfile")
data/Freyrfile CHANGED
@@ -1,7 +1,21 @@
1
1
  service :sleep do
2
+ group :sleepers
3
+
4
+ also_as :short_sleep
5
+ start 'sleep 20'
6
+ # rvm 'ree-1.8.7-2011.03@foobar'
7
+
8
+ proc_match /sleep 20/
9
+ end
10
+
11
+ service :sleep2 do
12
+ requires :sleep, :sudosleep
2
13
  start 'sleep 20'
14
+ proc_match /sleep 20/
3
15
  end
4
16
 
5
17
  service :sudosleep do
18
+ group :sleepers
6
19
  start 'sudo sleep 20'
20
+ proc_match /sleep 20/
7
21
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.2
1
+ 0.5.0
data/bin/freyr CHANGED
@@ -4,4 +4,6 @@ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
4
4
  require 'freyr'
5
5
  require 'freyr/cli'
6
6
 
7
+ trap("SIGINT") { exit(false) }
8
+
7
9
  Freyr::CLI.start
@@ -5,7 +5,7 @@ module Freyr
5
5
  super
6
6
 
7
7
  if options.trace?
8
- Freyr.logger = Logger.new(STDOUT)
8
+ Freyr.logger.level = Logger::DEBUG
9
9
  end
10
10
 
11
11
  get_services
@@ -21,18 +21,19 @@ module Freyr
21
21
 
22
22
  groups_join = '|'
23
23
 
24
- lengths = Service.s.collect do |s|
24
+ lengths = {}
25
+ Service.s.each do |name,s|
25
26
  n = " #{s.name}(#{s.groups.join(groups_join)})"
26
27
  max_length = n.length if n.length > max_length
27
- n.length
28
+ lengths[name] = n.length
28
29
  end
29
30
 
30
31
  max_length += 3 # min distance between name and group
31
32
 
32
33
  strs = []
33
- Service.s.each_with_index do |s,i|
34
+ Service.s.each do |i,s|
34
35
  str = ' '
35
- if s.sudo
36
+ if s.info.sudo
36
37
  str << set_color('*', :yellow)
37
38
  else
38
39
  str << ' '
@@ -86,11 +87,11 @@ module Freyr
86
87
 
87
88
  if args[:procinfo]
88
89
  begin
89
- pid = s.command.pid
90
+ pid = s.pid_file.pid
90
91
 
91
92
  proc = ProcessInfo[pid]
92
93
 
93
- str << " CPU: #{proc.pcpu}% - MEM: #{proc.mem_in_mb.to_i}mb" if proc
94
+ str << " CPU: #{proc.pcpu}% - MEM: #{proc.mem_in_mb.to_i}mb PID: #{pid}" if proc
94
95
  # rescue => e
95
96
  end
96
97
  end
@@ -108,24 +109,19 @@ module Freyr
108
109
  end
109
110
 
110
111
  def get_from_name name
111
- group = ServiceGroup.new
112
-
113
- unless name
114
- s = Service.s.find {|svc| svc.dir == Dir.pwd}
115
- Freyr.logger.debug('getting service from directory') {"in #{Dir.pwd} found service: #{s.inspect}"}
116
- return group << s if s
117
- end
118
-
119
- if options.namespace && s = Service["#{options.namespace}:#{name}"].first
120
- group << s # only pickng one because if it's namespaced it's not a group
112
+ if name
113
+ group = Service[name.to_sym]
114
+ Freyr.logger.debug('find service by name') {"#{name.to_sym} #{group.inspect}"}
115
+ group
121
116
  else
122
- Service[name].each do |s|
123
- group << s
117
+ if s = Service.by_dir[Dir.pwd]
118
+ Freyr.logger.debug('getting service from directory') {"in #{Dir.pwd} found service: #{s.inspect}"}
119
+ else
120
+ Freyr.logger.debug('getting service from directory') {"in #{Dir.pwd} unable to find any service"}
124
121
  end
122
+
123
+ Service[s.name]
125
124
  end
126
-
127
- Freyr.logger.debug('getting service') {group.inspect}
128
- group
129
125
  end
130
126
 
131
127
  def get_services
@@ -5,9 +5,9 @@ module Freyr
5
5
  desc 'start [SERVICE=dirname]', 'Start particular service'
6
6
  def start(name=nil)
7
7
  services = get_from_name(name)
8
- if !services.empty?
8
+ if services && !services.empty?
9
9
  Freyr.logger.debug('starting services') {services.inspect}
10
- names = services.collect {|s| s.name}
10
+ names = services.call_order.collect {|s| s.name}
11
11
  say "Starting the " << set_color(names.join(', '), :blue) << ' services'
12
12
 
13
13
  changed_names = services.run
@@ -15,15 +15,20 @@ module Freyr
15
15
  list_all_services(:highlight_state => changed_names).each {|l| say(l)}
16
16
  else
17
17
  say "Can't find the #{name} service", :red
18
+ exit(false)
18
19
  end
19
20
  rescue AdminRequired
20
21
  say "Please run in sudo to launch #{name}.", :red
22
+ exit(false)
23
+ rescue Service::MissingDependency => e
24
+ say "Missing dependency, could not launch service: #{e.to_s}"
25
+ exit(false)
21
26
  end
22
27
 
23
28
  desc 'stop [SERVICE=dirname]', 'Stop particular service'
24
29
  def stop(name=nil)
25
30
  services = get_from_name(name)
26
- if !services.empty?
31
+ if services && !services.empty?
27
32
  Freyr.logger.debug('stopping services') {services.inspect}
28
33
  names = services.collect {|s| s.name}
29
34
  say "Stopping the " << set_color(names.join(', '), :blue) << ' services'
@@ -33,15 +38,17 @@ module Freyr
33
38
  list_all_services(:highlight_state => changed_names).each {|l| say(l)}
34
39
  else
35
40
  say "Can't find the #{name} service", :red
41
+ exit(false)
36
42
  end
37
43
  rescue AdminRequired
38
44
  say "Please run in sudo to stop #{name}.", :red
45
+ exit(false)
39
46
  end
40
47
 
41
48
  desc 'restart [SERVICE=dirname]', 'restart particular service'
42
49
  def restart(name=nil)
43
50
  services = get_from_name(name)
44
- if !services.empty?
51
+ if services && !services.empty?
45
52
  Freyr.logger.debug('restarting services') {services.inspect}
46
53
  say "Restarting the " << set_color(services.collect {|s| s.name}.join(', '), :blue) << ' services'
47
54
 
@@ -50,11 +57,13 @@ module Freyr
50
57
  list_all_services(:highlight_state => names).each {|l| say(l)}
51
58
  else
52
59
  say "Can't find the #{name} service", :red
60
+ exit(false)
53
61
  end
54
62
 
55
63
  rescue AdminRequired
56
64
  say "Please run in sudo to launch #{name}.", :red
65
+ exit(false)
57
66
  end
58
67
 
59
68
  end
60
- end
69
+ end
@@ -1,21 +1,21 @@
1
1
  module Freyr
2
2
  class CLI < Thor
3
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
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
19
 
20
20
  end
21
21
  end
data/lib/freyr/command.rb CHANGED
@@ -3,96 +3,22 @@ module Freyr
3
3
  class Command
4
4
  extend Forwardable
5
5
 
6
- ROOT_PIDS = '/var/run/freyr'
7
- USER_PIDS = File.expand_path('.freyr', '~')
6
+ attr_reader :name, :service
8
7
 
9
- if !File.exist?(USER_PIDS)
10
- Dir.mkdir(USER_PIDS)
11
- elsif !File.directory?(USER_PIDS)
12
- File.delete(USER_PIDS)
13
- Dir.mkdir(USER_PIDS)
8
+ def initialize(service, command=nil, args = {})
9
+ @service = service
14
10
  end
15
-
16
- attr_reader :command, :name, :service
17
-
18
- def initialize(name, command=nil, args = {})
19
- if name.is_a?(Service)
20
- @service = name
21
- @name = service.name
22
- @command = service.start_command
23
- @env = service.env
24
- else
25
- @name = name
26
- @command = command
27
-
28
- @env = args[:env]
29
- end
30
- end
31
-
32
- def pid_file
33
- path = File.join(file_dir,"#{@name}.pid")
34
- if File.exist?(path)
35
- path
36
- else
37
- File.join(file_dir(true),"#{@name}.pid")
38
- end
39
- end
40
-
41
- def file_dir(force_user = false)
42
- return USER_PIDS if force_user
43
- admin? ? ROOT_PIDS : USER_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
- !alive?
79
- end
80
-
81
- def save
82
- if File.exist?(pid_file)
83
- old_pid = read_pid
84
- begin
85
- Process.kill('KILL', old_pid) if old_pid && old_pid.to_s != @pid.to_s
86
- rescue Errno::ESRCH
87
- end
88
- end
89
-
90
- File.open(pid_file, 'w') {|f| f.write(@pid)}
11
+
12
+ def_delegators :'@service', :info
13
+ def_delegators :info, :name, :env
14
+
15
+ def command
16
+ info.start
91
17
  end
92
18
 
93
19
  def run!
94
20
  return unless command
95
- kill! if alive?
21
+ kill! if service.alive?
96
22
 
97
23
  require_admin
98
24
 
@@ -101,90 +27,31 @@ module Freyr
101
27
  pid = spawn(command)
102
28
 
103
29
  Freyr.logger.debug("attempting to run command") {command.inspect}
30
+ str = "\nStarting #{info.name} with #{command.inspect}"
31
+ OUT.puts '',"Starting #{info.name} with #{command.inspect}", '='*str.length
32
+ Process.detach(pid)
104
33
 
105
- @pid = pid
106
-
107
- Process.detach(@pid)
108
-
109
- if proc_match
110
- puts "Waiting for pid from match of #{proc_match.inspect}"
111
-
112
- start = Time.now
113
- until (pid = pid_from_procname) || (Time.now-start) > 40
114
- print '.'; STDOUT.flush
115
- sleep(0.2)
116
- end
117
-
118
- raise "\nCouldnt find pid after 40 seconds" unless pid
119
-
120
- puts '*'
121
-
122
- @pid = pid
123
- end
34
+ pid = service.pid_file.wait_for_pid
124
35
 
125
- puts "PID of new #{name} process is #{@pid}"
126
- save
36
+ OUT.puts "PID of new #{info.name} process is #{pid}"
127
37
 
128
- if ping
129
- pinger = Pinger.new(self)
130
-
131
- puts '',"Waiting for response from #{pinger.url}"
38
+ if info.ping
39
+ pinger = Pinger.new(@service)
132
40
 
133
- # Move this pinger code somewhere else
134
- start = Time.now
135
- begin
136
- print '.'; STDOUT.flush
137
- pinger.ping
138
- sleep(0.6)
139
- end until pinger.server_probably_launched? || (Time.now-start) > 40 || !alive?
140
-
141
- if alive?
142
-
143
- if pinger.response
144
- puts '*',"Last response recieved with code #{pinger.response.code}"
145
- else
146
- puts 'x',"Couldn't reach #{name} service"
147
- end
148
- else
149
- puts 'x', "Service died durring launch"
150
- end
41
+ pinger.wait_for_resp { @service.alive? }
151
42
  end
152
43
 
153
- if alive?
154
- puts "Launch took about #{(Time.now-total_time).ceil} seconds"
155
-
156
- @pid
44
+ if @service.alive?
45
+ OUT.puts "Launch took about #{(Time.now-total_time).ceil} seconds"
46
+ pid
157
47
  else
158
- puts "#{name} service wasn't launched correctly. For details see: #{log}"
159
- delete_if_dead
160
- end
161
- end
162
-
163
- def update_pid
164
- if @pid = pid_from_procname
165
- save
166
- @pid
167
- end
168
- end
169
-
170
- def pid_from_procname
171
- return unless proc_match
172
-
173
- pids = `ps -eo pid,command`.split("\n").inject({}) do |r, pid|
174
- if m = pid.match(/^\s*(\d+)\s(.+)$/)
175
- r[m[2]] = m[1].to_i
176
- end
177
- r
178
- end
179
-
180
- if procline = pids.keys.find {|p| p.match(proc_match)}
181
- pids[procline]
48
+ OUT.puts "#{info.name} service wasn't launched correctly. For details see: #{info.log}"
182
49
  end
183
50
  end
184
51
 
185
52
  def kill!(sig=nil)
186
53
  require_admin
187
- sig ||= stop_sig || 'KILL'
54
+ sig ||= info.stop_sig || 'KILL'
188
55
 
189
56
  Freyr.logger.debug("sending signal to process") {"Signal: #{sig}, PID: #{pid}"}
190
57
 
@@ -192,64 +59,71 @@ module Freyr
192
59
  Process.kill(sig, pid)
193
60
  end
194
61
  end
62
+
63
+ def pid force = false
64
+ @service.pid_file.pid(force)
65
+ end
195
66
 
196
67
  def restart!
197
68
  require_admin
198
69
 
199
- if restart
70
+ if info.restart
200
71
  chdir
201
- system(restart)
202
- update_pid
203
- elsif restart_sig
204
- kill!(restart_sig)
72
+ system(info.restart)
73
+ elsif info.restart_sig
74
+ kill!(info.restart_sig)
205
75
  else
206
76
  run!
207
77
  end
208
78
  end
209
79
 
210
- def admin?
211
- sudo
212
- end
213
-
214
80
  private
215
81
 
216
82
  def require_admin
217
- raise AdminRequired if admin? && !is_root?
218
- end
219
-
220
- def is_root?
221
- ENV['USER'] == 'root'
83
+ raise AdminRequired if info.sudo && !Freyr.is_root?
222
84
  end
223
85
 
224
86
  def chdir
225
- Dir.chdir File.expand_path(dir||'/')
87
+ Dir.chdir File.expand_path(info.dir||'/')
226
88
  end
227
89
 
228
90
  def spawn(command)
91
+ if info.rvm && RVM.installed?
92
+ Freyr.logger.debug('attempting to set rvm') {info.rvm}
93
+ if RVM.installed?(info.rvm)
94
+ command = "rvm #{info.rvm} exec #{command}"
95
+ Freyr.logger.debug('changed command to') {command}
96
+ else
97
+ abort("must setup rvm correctly, run: rvm --install --create #{info.rvm}")
98
+ end
99
+ elsif info.rvm
100
+ Freyr.logger.debug("rvm not installed so can't switch to") {info.rvm}
101
+ end
102
+
229
103
  fork do
230
- File.umask self.umask if self.umask
231
- uid_num = Etc.getpwnam(self.uid).uid if uid
232
- gid_num = Etc.getgrnam(self.gid).gid if gid
104
+ File.umask info.umask if info.umask
105
+ uid_num = Etc.getpwnam(info.uid).uid if info.uid
106
+ gid_num = Etc.getgrnam(info.gid).gid if info.gid
233
107
 
234
- ::Dir.chroot(self.chroot) if self.chroot
108
+ ::Dir.chroot(info.chroot) if info.chroot
235
109
  ::Process.setsid
236
- ::Process.groups = [gid_num] if self.gid
237
- ::Process::Sys.setgid(gid_num) if self.gid
238
- ::Process::Sys.setuid(uid_num) if self.uid
110
+ ::Process.groups = [info.guid] if info.gid
111
+ ::Process::Sys.setgid(info.guid) if info.gid
112
+ ::Process::Sys.setuid(info.guid) if info.uid
239
113
  chdir
240
114
  $0 = "freyr - #{name} (#{command})"
241
115
  STDIN.reopen "/dev/null"
242
- if log_cmd
116
+ if info.log_cmd
243
117
  STDOUT.reopen IO.popen(log_cmd, "a")
244
- elsif log && service.write_log?
245
- STDOUT.reopen log, "a"
118
+ elsif info.log && !info.dont_write_log
119
+ STDOUT.reopen info.log, "a"
246
120
  else
247
121
  STDOUT.reopen "/dev/null"
248
122
  end
249
- if err_log_cmd
250
- STDERR.reopen IO.popen(err_log_cmd, "a")
251
- elsif err_log && (log_cmd || err_log != log)
252
- STDERR.reopen err_log, "a"
123
+ if info.err_log_cmd
124
+ STDERR.reopen IO.popen(info.err_log_cmd, "a")
125
+ elsif info.err_log && (info.log_cmd || info.err_log != info.log)
126
+ STDERR.reopen info.err_log, "a"
253
127
  else
254
128
  STDERR.reopen STDOUT
255
129
  end
@@ -263,18 +137,13 @@ module Freyr
263
137
  end
264
138
  end
265
139
 
266
- exec(command) unless command.empty?
140
+ exec(command)
267
141
  end
268
142
  end
269
143
 
270
144
 
271
145
  class << self
272
146
  def add_service_method *methods
273
- methods.each do |meth|
274
- define_method(meth) do |*args|
275
- @service.__send__(meth,*args) if @service
276
- end
277
- end
278
147
  end
279
148
  end
280
149
  end
@@ -0,0 +1,10 @@
1
+ module Freyr
2
+ def is_root?
3
+ Process.euid == 0
4
+ end
5
+
6
+ def has_rvm?
7
+ ENV["rvm_loaded_flag"] == "1"
8
+ end
9
+ class Timeout < StandardError; end
10
+ end
@@ -0,0 +1,73 @@
1
+ module Freyr
2
+ class PidFile
3
+ def initialize path, procname=nil
4
+ @path = path
5
+ @procname = procname
6
+ end
7
+
8
+ def process_info
9
+ @proces_info ||= ProcessInfo.new(@pid) if @pid
10
+ end
11
+
12
+ def alive?
13
+ return unless pid
14
+ Process.getpgid(pid)
15
+ true
16
+ rescue Errno::ESRCH
17
+ false
18
+ end
19
+
20
+ def pid_from_file
21
+ return unless File.exist?(@path)
22
+ p = File.open(@path).read.chomp
23
+ p ? p.to_i : nil
24
+ end
25
+
26
+ def pid force=false
27
+ if !force && File.exist?(@path)
28
+ pid_from_file
29
+ elsif @procname
30
+ pid_from_procname
31
+ else
32
+ pid_from_file
33
+ end
34
+ end
35
+
36
+ def pid_from_procname force=false
37
+ pids = PidFile.pid_command_hash(force)
38
+
39
+ if procline = pids.keys.find {|p| p.match(@procname)}
40
+ pids[procline]
41
+ end
42
+ end
43
+
44
+ def wait_for_pid wait = 40, interval=0.2
45
+ OUT.puts "Waiting #{wait}s for pid from match of #{@procname}"
46
+
47
+ start = Time.now
48
+
49
+ until (pid = pid_from_procname(true)) || (Time.now-start) > wait
50
+ OUT.print '.';OUT.flush
51
+ sleep(interval)
52
+ end
53
+
54
+ raise Timeout, "\n Couldn't find pid after" unless pid
55
+ OUT.puts '*'
56
+ pid
57
+ end
58
+
59
+ class << self
60
+
61
+ def pid_command_hash force=false
62
+ @pid_command_hash = nil if force
63
+ @pid_command_hash ||= `ps -eo pid,command`.split("\n").inject({}) do |r, pid|
64
+ if m = pid.match(/^\s*(\d+)\s(.+)$/)
65
+ r[m[2]] = m[1].to_i
66
+ end
67
+ r
68
+ end
69
+ end
70
+
71
+ end
72
+ end
73
+ end
data/lib/freyr/pinger.rb CHANGED
@@ -9,9 +9,32 @@ module Freyr
9
9
  # Response object
10
10
  attr_reader :response
11
11
 
12
- def initialize(command)
13
- @command = command
14
- @url = command.ping
12
+ def initialize(service)
13
+ @service = service
14
+ @url = service.info.ping
15
+ end
16
+
17
+ def wait_for_resp wait=40, interval = 0.6, &blk
18
+ OUT.puts "\nWaiting for response from #{url}"
19
+ start = Time.now
20
+
21
+ blk ||= lambda {true}
22
+
23
+ begin
24
+ OUT.print '.'; OUT.flush
25
+ ping
26
+ sleep(interval)
27
+ end until server_probably_launched? || (Time.now-start) > wait || !blk.call
28
+
29
+ if blk.call
30
+ if response
31
+ OUT.puts '*', "Last response received with code #{response.code}"
32
+ else
33
+ OUT.puts 'x', "Couldn't reach #{@service.name} service"
34
+ end
35
+ else
36
+ OUT.puts 'x',"Service died durring launch"
37
+ end
15
38
  end
16
39
 
17
40
  # The URI object for the given URL
@@ -35,7 +58,7 @@ module Freyr
35
58
 
36
59
  # Did the response recieve a success http code
37
60
  def success?
38
- response.is_a?(Net::HTTPSuccess)
61
+ response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
39
62
  end
40
63
 
41
64
  # Did the response recieve a 500 error
@@ -43,7 +66,7 @@ module Freyr
43
66
  response.is_a?(Net::HTTPInternalServerError)
44
67
  end
45
68
 
46
- # Did it recieve 2xx or 500
69
+ # Did it receive 2xx or 500
47
70
  def server_probably_launched?
48
71
  success? || server_error?
49
72
  end