immortalize 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/VERSION +1 -1
  2. data/bin/immortalize +103 -18
  3. data/immortalize.gemspec +2 -2
  4. metadata +3 -3
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.1
1
+ 0.1.2
data/bin/immortalize CHANGED
@@ -20,7 +20,7 @@ require 'merb-core'
20
20
  require 'merb-mailer'
21
21
  Merb::Mailer.delivery_method = :sendmail
22
22
 
23
- $log_location = "/var/log/immortalize"
23
+ $log_location = "#{ENV['HOME']}/.immortalize"
24
24
  `mkdir -p "#{$log_location}"` unless File.directory?($log_location)
25
25
  $registry_filename = "#{$log_location}/registry.yaml"
26
26
  File.open($registry_filename, 'w'){|f| f << {}.to_yaml} unless File.exists?($registry_filename)
@@ -40,17 +40,22 @@ To remove a command:
40
40
  To inspect the current list of immortal commands:
41
41
  #{$0} list
42
42
 
43
+ To install the immortalize cron job (which does the actual work):
44
+ immortalize setup
45
+
43
46
  Run this command with no arguments as a cron job, to run every minute:
44
47
  * * * * * immortalize
48
+
49
+ Options:
45
50
  ENDBANNER
46
51
 
47
- # $options[:notification_recipient] = nil
48
- opts.on( '--notification_recipient=[EMAIL]', "The email address to which failure notifications should be sent." ) do |email|
52
+ $options[:notification_recipient] = nil
53
+ opts.on( '--notify=EMAIL', "The email address to which failure notifications should be sent." ) do |email|
49
54
  $options[:notification_recipient] = email
50
55
  end
51
56
 
52
57
  $options[:max_failures] = 5
53
- opts.on('--max_failures=[NUM]', "Notify on NUM or more failures within an hour (default 5)") do |num|
58
+ opts.on('--max_failures=NUM', "Notify on NUM or more failures within an hour (default 5)") do |num|
54
59
  $options[:max_failures] = num.to_i
55
60
  end
56
61
 
@@ -80,6 +85,16 @@ class Time
80
85
  end
81
86
 
82
87
  class Immortal
88
+ def self.new(identifier)
89
+ if $registry[identifier]
90
+ obj = allocate
91
+ obj.send :initialize, identifier
92
+ obj
93
+ else
94
+ puts "tried:\n\t#{identifier}commands:\n\t#{$registry.values.map {|r| r[:command]}.join("\n\t")}"
95
+ end
96
+ end
97
+
83
98
  attr_reader :identifier
84
99
  def initialize(identifier)
85
100
  @identifier = identifier
@@ -101,13 +116,23 @@ class Immortal
101
116
  def start!
102
117
  # Run the command and gather the pid
103
118
  pid = nil
104
- open("|#{@reg[:command]} & echo $!") do |f|
119
+ open("|echo \"#{@reg[:command]}\" | sh & echo $!") do |f|
105
120
  pid = f.sysread(5).chomp.to_i
106
121
  end
107
122
  # Log the pid
108
123
  puts "pid #{pid}"
109
124
  $registry[identifier][:pid] = pid
110
125
  end
126
+ def stop!
127
+ if running?
128
+ pid = $registry[identifier].delete(:pid)
129
+ `kill -INT #{pid}`
130
+ puts "attempted to kill pid #{pid}: \"#{@reg[:command]}\""
131
+ else
132
+ warn "\"#{@reg[:command]}\" not running!"
133
+ false
134
+ end
135
+ end
111
136
 
112
137
  def failed!
113
138
  failures << Time.now
@@ -137,25 +162,78 @@ class Immortal
137
162
  end
138
163
  end
139
164
 
165
+ # Curate the command string
166
+ if ARGV[1].to_s.length > 1 && ARGV[1] !~ /^\d+$/
167
+ puts ARGV.inspect
168
+ $command_string = ARGV[1]
169
+ # Complain about the string if it does not have proper output redirections
170
+ cmds = $command_string.split(/; ?/)
171
+ last_cmd = cmds.pop
172
+ lst,out_s = last_cmd.split(/(?: \d)? ?>/,2)
173
+ cmds << lst
174
+ outs = last_cmd.scan(/(?: \d)? ?> ?\S+/)
175
+ outs = outs.map {|o| o.sub(/ ?> ?/,">").sub(/^>/,"1>") }
176
+ unless outs.any? {|o| o =~ /^2>/}
177
+ warn "Appending default STDERR redirection: 2>&1 (STDERR > STDOUT)"
178
+ outs << "2>&1"
179
+ end
180
+ unless outs.any? {|o| o =~ /^1?>/}
181
+ warn "#{$command_string}\nInvalid command: You need to add proper STDOUT redirection, ex: > /dev/null or 1>log/run.log or 1 > log/run.log"
182
+ exit
183
+ end
184
+ $command_string = cmds.join('; ') + ' ' + outs.join(' ')
185
+ end
186
+
187
+
140
188
  # Main logic
141
189
  unless ::Object.const_defined?(:IRB)
142
190
  case $action
191
+ when 'setup'
192
+ crons = `crontab -l 2>/dev/null`.split(/\n/)
193
+ immortalize_cmd = `which immortalize`.chomp
194
+ if immortalize_cmd.empty?
195
+ warn "Couldn't find installed version of the 'immortalize' command! (Try `which immortalize`)"
196
+ exit
197
+ end
198
+ crons.reject! {|c| c =~ /immortalize > #{$log_location}\/cron.log$/}
199
+ crons << "* * * * * #{immortalize_cmd} > #{$log_location}/cron.log\n"
200
+ puts "Installing crons:\n\t#{crons.join("\n\t")}"
201
+ f = IO.popen("crontab -", 'w')
202
+ f << crons.join("\n")
203
+ f.close
204
+ crons = `crontab -l 2>/dev/null`.split(/\n/)
205
+ puts "Installed crons:\n\t#{crons.join("\n\t")}"
143
206
  when 'list'
144
- puts $registry.inspect
207
+ # puts $registry.inspect
145
208
  puts "Immortalized:"
146
- $registry.each_key do |identifier|
147
- puts "\t+ " + Immortal.new(identifier).inspect
209
+ keys = $registry.keys.sort
210
+ keys.each_with_index do |identifier,i|
211
+ puts "\t#{i+1}) " + Immortal.new(identifier).inspect
148
212
  end
213
+ puts "\nTo remove jobs, for example job #1, from this list, run `immortalize remove 1`"
149
214
  exit
150
215
 
216
+ when 'stop'
217
+ if ARGV[1] =~ /^\d+$/
218
+ identifier = $registry.keys.sort[ARGV[1].to_i-1]
219
+ else
220
+ identifier = SHA1.hexdigest($command_string)
221
+ end
222
+ immortal = Immortal.new(identifier)
223
+ immortal.stop!
224
+
151
225
  when 'run'
226
+ if $options[:notification_recipient].nil?
227
+ warn "Must include --notify EMAIL_ADDRESS when adding a command!"
228
+ exit
229
+ end
230
+
152
231
  # Running with a given command.
153
- command_string = ARGV[1]
154
- identifier = SHA1.hexdigest(command_string)
232
+ identifier = SHA1.hexdigest($command_string)
155
233
 
156
234
  # Create the command
157
235
  $registry[identifier] ||= {
158
- :command => command_string
236
+ :command => $command_string
159
237
  }
160
238
  $registry[identifier].merge!($options)
161
239
 
@@ -169,32 +247,39 @@ unless ::Object.const_defined?(:IRB)
169
247
  end
170
248
 
171
249
  when 'remove'
172
- command_string = ARGV[1]
173
- identifier = SHA1.hexdigest(command_string)
174
- $registry.delete(identifier)
250
+ if ARGV[1] =~ /^\d+$/
251
+ identifier = $registry.keys.sort[ARGV[1].to_i-1]
252
+ else
253
+ identifier = SHA1.hexdigest($command_string)
254
+ end
255
+ reg = $registry.delete(identifier)
256
+ puts "Deleted #{identifier}: \"#{reg[:command]}\""
175
257
 
176
258
  when nil
177
259
  # Running bare from cron.
178
260
  # Check all logged commands with pids.
179
- $registry.each do |identifier,info|
261
+ puts "[#{Time.now.strftime("%Y-%m-%d %H:%M:%S")}] #{$registry.length} jobs"
262
+ $registry.keys.sort.each_with_index do |identifier,i|
180
263
  immortal = Immortal.new(identifier)
181
264
 
182
265
  # Check if running
183
266
  if immortal.running?
184
- puts "`#{immortal[:command]}' is running fine..."
267
+ # puts " #{i+1}) `#{immortal[:command]}' is running fine..."
185
268
  else
186
- puts "`#{immortal[:command]}' HAS DIED! Reviving..."
269
+ puts " #{i+1}) `#{immortal[:command]}' HAS DIED! Reviving..."
187
270
  # Mark the failure
188
271
  immortal.failed!
189
272
  # Notify if failures have been frequent
190
273
  if immortal.frequent_failures?
191
- puts "! FREQUENT FAILURE ON #{identifier} (`#{immortal[:command]}')"
274
+ puts " #{i+1}) FREQUENT FAILURE ON #{identifier} (`#{immortal[:command]}')"
192
275
  notify(immortal, "ImmortalCommand failure!\n\nCommand `#{immortal[:command]}' failed, threshold is #{immortal[:max_failures]} / hour.\n\n#{immortal.failures_today.size} failures so far today, #{immortal.failures_this_hour.size} in the past hour.")
193
276
  end
194
277
  # Start it
195
278
  immortal.start!
196
279
  end
197
280
  end
281
+ else
282
+ puts optparse
198
283
  end
199
284
  end
200
285
 
data/immortalize.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{immortalize}
8
- s.version = "0.1.1"
8
+ s.version = "0.1.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["BehindLogic"]
12
- s.date = %q{2010-03-09}
12
+ s.date = %q{2010-03-17}
13
13
  s.default_executable = %q{immortalize}
14
14
  s.description = %q{Watch a specific process, restart it if it dies.}
15
15
  s.email = %q{gems@behindlogic.com}
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 1
9
- version: 0.1.1
8
+ - 2
9
+ version: 0.1.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - BehindLogic
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-03-09 00:00:00 -05:00
17
+ date: 2010-03-17 00:00:00 -04:00
18
18
  default_executable: immortalize
19
19
  dependencies: []
20
20