immortalize 0.1.1 → 0.1.2

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.
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