taskr 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,22 @@
1
+ === 0.4.0 :: 2008-12-23
2
+
3
+ * Log entries in task view can now be filtered by date. By default only entries
4
+ from the last 24 hours are shown.
5
+ * Added ability to reload and edit existing tasks [dvdplm]
6
+ * Added experimental 'dont_wait' parameter to the Taskr4Rails action that
7
+ forces the remote code to be forked so that it does not hold up the entire
8
+ server. Currently this will only work if the remote Rails server is running
9
+ on Mongrel.
10
+ * Taskr can be configured to send SNMP traps whenever task events are logged.
11
+ See the config.example.yml file's "LOGGING" section for more info.
12
+ * Task actions and parameters are now again included in the XML views for
13
+ the task list and for individual tasks.
14
+ * Restr 0.5.0 is now required, due to a bug in Restr 0.4.0 that caused
15
+ some actions to log their activities incorrectly.
16
+ * Picnic 0.7.0 is now required, due to changes in environment configuration.
17
+ * Fixed dependency loading problems introduced by upstream changes in RubyGems
18
+ 1.3.1.
19
+
1
20
  === 0.3.0 :: 2008-06-19
2
21
 
3
22
  * Added "Run Now" function allowing a task to be immediately triggered.
@@ -38,4 +57,4 @@
38
57
 
39
58
  === 0.1.0 :: 2007-12-21
40
59
 
41
- * First public release.
60
+ * First public release.
data/Rakefile CHANGED
@@ -11,8 +11,8 @@ require 'hoe'
11
11
  include FileUtils
12
12
  require File.join(File.dirname(__FILE__), 'lib', 'taskr', 'version')
13
13
 
14
- AUTHOR = "Matt Zukowski" # can also be an array of Authors
15
- EMAIL = "matt at roughest dot net"
14
+ AUTHOR = ["Matt Zukowski", "David Palm"] # can also be an array of Authors
15
+ EMAIL = ["matt at roughest dot net", "dvd plm on googles free email service"]
16
16
  DESCRIPTION = "cron-like scheduler service with a RESTful interface"
17
17
  GEM_NAME = "taskr" # what ppl will type to install your gem
18
18
  RUBYFORGE_PROJECT = "taskr" # The unix name for your project
@@ -21,7 +21,7 @@ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
21
21
 
22
22
  NAME = "taskr"
23
23
  REV = nil
24
- #REV = `svn info`[/Revision: (\d+)/, 1] rescue nil
24
+ #REV = YAML.load(`svn info`)['Revision']
25
25
  VERS = ENV['VERSION'] || (Taskr::VERSION::STRING + (REV ? ".#{REV}" : ""))
26
26
  CLEAN.include ['**/.*.sw?', '*.gem', '.config']
27
27
  RDOC_OPTS = ['--quiet', '--title', "taskr documentation",
@@ -54,10 +54,33 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
54
54
  #p.spec_extras - A hash of extra values to set in the gemspec.
55
55
 
56
56
  p.extra_deps = [
57
- ['picnic', '~> 0.6.4'],
57
+ ['picnic', '~> 0.7.0'],
58
58
  ['reststop', '~> 0.3.0'],
59
- ['restr', '~> 0.4.0'],
59
+ ['restr', '~> 0.5.0'],
60
60
  ['rufus-scheduler', '~> 1.0.7']
61
61
  ]
62
62
  p.spec_extras = {:executables => ['taskr', 'taskr-ctl']}
63
63
  end
64
+
65
+ desc "Generate gemspec"
66
+ task :gemspec do |x|
67
+ # Check the manifest before generating the gemspec
68
+ manifest = %x[rake check_manifest]
69
+ manifest.gsub!(/\(in .{1,}\)\n/, "")
70
+
71
+ unless manifest.empty?
72
+ print "\n", "#"*68, "\n"
73
+ print <<-EOS
74
+ Manifest.txt is not up-to-date. Please review the changes below.
75
+ If the changes are correct, run 'rake check_manifest | patch'
76
+ and then run this command again.
77
+ EOS
78
+ print "#"*68, "\n\n"
79
+ puts manifest
80
+ else
81
+ gemspec = %x[rake debug_gem]
82
+ gemspec.gsub!(/\(in .{1,}\)\n/, "")
83
+ File.open("#{GEM_NAME}.gemspec",'w'){|f| f<<gemspec}
84
+ %x[rake debug_gem > #{GEM_NAME}.gemspec]
85
+ end
86
+ end
data/bin/taskr CHANGED
File without changes
File without changes
@@ -139,6 +139,19 @@ task_log:
139
139
  # ERROR, WARN, INFO, or DEBUG
140
140
  level: DEBUG
141
141
 
142
+ ### SNMP Traps
143
+ # Taskr can be configured to send out SNMP traps whenever task events occur.
144
+ # That is, in addition to logging task events to the Task Log (see above),
145
+ # Taskr can send out SNMP traps notifying an SNMP listener of the event.
146
+ # This functionality relies on the Net-SNMP package's `snmptrap` command-line
147
+ # utility, so make sure you have the PERL Net-SNMP installed on your system
148
+ # before enabling the following configuration options.
149
+
150
+ #snmp:
151
+ # send_traps: true
152
+ # to_host: snmp.example.net
153
+ # community: public
154
+ # enterprise_oid: 1.3.6.1.4.1.55555.7007
142
155
 
143
156
  ##### MISC #####################################################################
144
157
 
@@ -58,13 +58,13 @@ module Taskr
58
58
 
59
59
  def trigger(trigger_args = {})
60
60
  begin
61
- $LOG.info("Executing task #{self.task.name}")
61
+ $LOG.info("Executing task #{self.task.name.inspect}")
62
62
  execute
63
63
  task.update_attribute(:last_triggered, Time.now)
64
64
  task.update_attribute(:last_triggered_error, nil)
65
65
  rescue => e
66
66
  puts
67
- $LOG.error(e)
67
+ $LOG.error("Error while executing task #{self.task.name.inspect}! The error was: #{e} (see Taskr log for debugging details)")
68
68
  $LOG.debug(e.backtrace.join($/))
69
69
  details = e.message
70
70
  details += "\n\n#{$LAST_ERROR_BODY}" if $LAST_ERROR_BODY # dumb way of reading Restr errors... Restr needs to be fixed
@@ -95,7 +95,7 @@ module Taskr
95
95
 
96
96
  def trigger(trigger_args = {})
97
97
  begin
98
- $LOG.info("Executing task #{self.task.name}")
98
+ $LOG.info("Executing task #{self.task.name.inspect}")
99
99
  actions.each do |a|
100
100
  a.execute
101
101
  LogEntry.info(a, "Action #{a} executed.")
@@ -104,7 +104,7 @@ module Taskr
104
104
  task.update_attribute(:last_triggered, Time.now)
105
105
  task.update_attribute(:last_triggered_error, nil)
106
106
  rescue => e
107
- $LOG.error(e)
107
+ $LOG.error("Error while executing task #{self.task.name.inspect}! The error was: #{e} (see Taskr log for debugging details)")
108
108
  $LOG.debug("#{e.backtrace}")
109
109
  task.update_attribute(:last_triggered, Time.now)
110
110
  task.update_attribute(:last_triggered_error, {:type => e.class.to_s, :details => "#{e.message}"})
@@ -268,8 +268,12 @@ module Taskr
268
268
  parameters['params'] = params2
269
269
  end
270
270
 
271
- Restr.logger = LogEntry.logger_for_action(task_action)
272
- Restr.do(parameters['method'], parameters['url'], (parameters['params'] unless parameters['params'].blank?), auth)
271
+ options = {:logger => LogEntry.logger_for_action(task_action)}
272
+ options.merge!(auth) if auth
273
+ Restr.do(parameters['method'],
274
+ parameters['url'],
275
+ (parameters['params'] unless parameters['params'].blank?),
276
+ options)
273
277
  end
274
278
  end
275
279
 
@@ -296,19 +300,21 @@ module Taskr
296
300
  end
297
301
 
298
302
  class Taskr4rails < Base
299
- self.parameters = ['url', 'auth', 'ruby_code']#, 'shell_command']
303
+ self.parameters = ['url', 'auth', 'ruby_code', 'dont_wait']#, 'shell_command']
300
304
  self.description = "Executes code on a remote Rails server via the taskr4rails plugin."
301
305
 
302
306
  def execute
303
307
  data = {
308
+ :task_name => task.name,
309
+ :task_id => task.id,
304
310
  :auth => parameters['auth'],
311
+ :dont_wait => parameters['dont_wait'],
305
312
  :ruby_code => parameters['ruby_code']#,
306
313
  #:shell_command => parameters['shell_command']
307
314
  }
308
-
309
-
310
- Restr.logger = LogEntry.logger_for_action(task_action)
311
- Restr.post(parameters['url'], data)
315
+
316
+ options = {:logger => LogEntry.logger_for_action(task_action)}
317
+ Restr.post(parameters['url'], data, options)
312
318
  end
313
319
  end
314
320
  end
@@ -66,9 +66,117 @@ module Taskr::Controllers
66
66
  render :view_task
67
67
  end
68
68
 
69
+ def edit(task_id)
70
+ @task = Task.find(task_id, :include => [:task_actions])
71
+ @actions = Taskr::Actions.list
72
+ render :edit_task
73
+ end
74
+
75
+ def update(task_id)
76
+ $LOG.debug "Update Input params: #{@input.inspect}"
77
+ @task = Task.find(task_id, :include => [:task_actions])
78
+ params = normalize_input(@input)
79
+ @task.attributes= {
80
+ :name => params[:name],
81
+ :schedule_method => params[:schedule_method],
82
+ :schedule_when => params[:schedule_when],
83
+ :memo => params[:memo]
84
+ }
85
+
86
+ @task.task_actions.each do |action|
87
+ $LOG.debug("Updating parameters for #{action.inspect}")
88
+ action_params = params[:action].delete("action_id_#{action.id}")
89
+ $LOG.debug("Using values #{action_params.inspect}")
90
+ next unless action_params
91
+ action_params.each do |param_name, value|
92
+ $LOG.debug("Looking up \"#{param_name}\". Setting value to \"#{value}\"")
93
+ action.parameters.find_by_name(param_name).update_attribute(:value, value)
94
+ end
95
+ end
96
+
97
+ # Create new actions/action_parameters for the remaining params
98
+ unless params[:action].empty?
99
+ params[:action].map do |num, params|
100
+ $LOG.debug "Looping remaining action_parameters: #{params.inspect} (num: #{num})"
101
+ action_class = get_action_class(params[:action_class_name])
102
+ action = TaskAction.new(:order => params[:order] || (@task.task_actions.maximum(:order)+1) || num, :action_class_name => action_class.to_s)
103
+
104
+ action_class.parameters.each do |p|
105
+ value = params[p]
106
+ value = nil if value.blank?
107
+ action.action_parameters << TaskActionParameter.new(:name => p, :value => value)
108
+ end
109
+ @task.task_actions << action
110
+ end
111
+ end
112
+
113
+ unless @task.valid?
114
+ @status = 500
115
+ @actions = Taskr::Actions.list
116
+ else
117
+ @task.save!
118
+ @status = 200
119
+ end
120
+
121
+ @task.reload # Ensure any updates to the record goes in
122
+
123
+ Taskr.scheduler.unschedule(@task.scheduler_job_id)
124
+ @task.schedule! Taskr.scheduler
125
+
126
+ $LOG.info "Task \"#{@task.name}\" (ID: #{@task.id}) updated sucessfully."
127
+ redirect R(@task)
128
+ end
129
+
130
+ def normalize_input(hsh)
131
+ hsh[:task] || hsh["0"] || hsh
132
+ end
133
+
134
+ def normalize_actions_params(input_params)
135
+ $LOG.debug "normalize_actions_params Normalizing: #{input_params.inspect}"
136
+ # some gymnastics here to provide compatibility for the way various
137
+ # REST client libraries submit data
138
+ actions_data = input_params[:actions] || input_params[:action]
139
+
140
+ raise ArgumentError, "Missing action(s) parameter." if actions_data.blank?
141
+ actions = case actions_data
142
+ when Array
143
+ $LOG.debug "normalize_actions_params Plain Array. Returning as-is."
144
+ actions_data
145
+ when Hash
146
+ $LOG.debug "normalize_actions_params Some weird Hash. Injecting."
147
+ actions_data.inject([]) do |acc,(i,a)|
148
+ $LOG.debug "normalize_actions_params acc: #{acc.inspect} index: #{i.inspect} array: #{a.inspect}."
149
+ acc << a
150
+ acc
151
+ end
152
+ else
153
+ $LOG.debug "normalize_actions_params Not a weird hash.\n\tactions_data[:action]: #{actions_data[:action].inspect}\n\tactions_data[:actions]: #{actions_data[:actions].inspect}\n\tactions_data: #{actions_data.inspect}\n\n"
154
+ actions_data[:action] || actions_data[:actions] || actions_data
155
+ end
156
+ actions = [actions] unless actions.kind_of? Array
157
+ $LOG.debug "normalize_actions_params DONE. Returning: #{actions.inspect}"
158
+ actions
159
+ end
160
+
161
+ def get_action_class(class_name)
162
+ action_class_name = "Taskr::Actions::#{class_name}" unless class_name =~ /^Taskr::Actions::/
163
+
164
+ begin
165
+ action_class = action_class_name.constantize
166
+ unless action_class.include? Rufus::Schedulable
167
+ raise ArgumentError,
168
+ "#{a[:action_class_name].inspect} cannot be used as an action because it does not include the Rufus::Schedulable module."
169
+ end
170
+ rescue NameError
171
+ raise ArgumentError,
172
+ "#{a[:action_class_name].inspect} is not defined (i.e. there is no such action class)."
173
+ end
174
+ action_class
175
+ end
176
+
69
177
  # Create and schedule a new task.
70
178
  def create
71
- puts @input.inspect
179
+ $LOG.debug @input.inspect
72
180
  begin
73
181
  # the "0" is for compatibility with PHP's Zend_Rest_Client
74
182
  task_data = @input[:task] || @input["0"] || @input
@@ -125,7 +233,7 @@ module Taskr::Controllers
125
233
  end
126
234
 
127
235
  action = TaskAction.new(:order => a[:order] || i, :action_class_name => action_class_name)
128
-
236
+ $LOG.debug "Action should be initialized and ready for creation: #{action.inspect}"
129
237
 
130
238
  action_class.parameters.each do |p|
131
239
  value = a[p]
@@ -147,7 +255,7 @@ module Taskr::Controllers
147
255
 
148
256
  @task.schedule! Taskr.scheduler
149
257
 
150
- if @task.save
258
+ if @task.save!
151
259
  location = "/tasks/#{@task.id}?format=#{@format}"
152
260
  $LOG.debug "#{@task} saved successfuly. Setting Location header to #{location.inspect}."
153
261
  @headers['Location'] = location
@@ -174,7 +282,7 @@ module Taskr::Controllers
174
282
  # ok to catch exception silently. it should have gotten logged by the action
175
283
  end
176
284
 
177
- render :view_task
285
+ redirect R(@task)
178
286
  end
179
287
 
180
288
  # Unschedule and delete an existing task.
@@ -197,12 +305,31 @@ module Taskr::Controllers
197
305
  _error("Task #{id} was not destroyed.", 500)
198
306
  end
199
307
  end
308
+
309
+ # Reload a task
310
+ def reload(id)
311
+ @task = Task.find(id)
312
+ $LOG.debug "Re-scheduling task #{@task}..."
313
+ if @task.scheduler_job_id
314
+ Taskr.scheduler.unschedule(@task.scheduler_job_id)
315
+ $LOG.debug "\t...unscheduled task #{@task}..."
316
+ end
317
+ @task.schedule! Taskr.scheduler
318
+ $LOG.debug "\t...scheduled task #{@task}...\n"
319
+ redirect R(@task)
320
+ end
200
321
  end
201
322
 
202
323
  class LogEntries < REST 'log_entries'
203
324
  def list
325
+ @since = @input[:since]
326
+
327
+ @level = ['DEBUG', 'INFO', 'WARN', 'ERROR']
328
+ @level.index(@input[:level]).times {@level.shift} if @input[:level]
329
+
204
330
  @log_entries = LogEntry.find(:all,
205
- :conditions => ['task_id = ?', @input[:task_id]],
331
+ :conditions => ['task_id = ? AND IF(?,timestamp > ?,1) AND level IN (?)',
332
+ @input[:task_id], !@since.blank?, @since, @level],
206
333
  :order => 'timestamp DESC, id DESC')
207
334
 
208
335
  render :log_entries_list
@@ -15,34 +15,39 @@
15
15
 
16
16
  $: << File.dirname(File.expand_path(__FILE__))
17
17
 
18
- # Try to load local versions of Picnic and Reststop if possible...
19
- $: << File.dirname(File.expand_path(__FILE__))+"/../../../picnic/lib"
20
- $: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/picnic/lib"
21
- $: << File.dirname(File.expand_path(__FILE__))+"/../../../reststop/lib"
22
- $: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/reststop/lib"
23
-
24
- # active_resource needs newer versions of active_support, but this conflicts
25
- # with active_record, so we need a newer version of that as well (yes, it's a mess)
26
- #$: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/activeresource/lib"
27
- #$: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/activesupport/lib"
28
- #$: << File.dirname(File.expand_path(__FILE__))+"/../../vendor/activerecord/lib"
18
+ # Try to load local version of Picnic if possible (for development purposes)
19
+ alt_picnic_paths = [
20
+ File.dirname(File.expand_path(__FILE__))+"/../../../picnic/lib",
21
+ File.dirname(File.expand_path(__FILE__))+"/../../vendor/picnic/lib"
22
+ ]
23
+ # Try to load local version of Reststop if possible (for development purposes)
24
+ alt_reststop_paths = [
25
+ File.dirname(File.expand_path(__FILE__))+"/../../../reststop/lib",
26
+ File.dirname(File.expand_path(__FILE__))+"/../../vendor/reststop/lib"
27
+ ]
29
28
 
30
29
  require 'rubygems'
31
30
 
32
- require 'active_support'
33
- #require 'active_resource'
34
- require 'active_record'
35
-
36
-
37
31
  # make things backwards-compatible for rubygems < 0.9.0
38
- unless Object.method_defined? :gem
32
+ if Object.method_defined?(:require_gem)
39
33
  alias gem require_gem
40
34
  end
41
35
 
36
+ if alt_picnic_paths.any?{|path| File.exists? "#{path}/picnic.rb" }
37
+ alt_picnic_paths.each{|path| $: << path}
38
+ end
42
39
  require 'picnic'
43
- require 'camping/db'
40
+
41
+ if alt_reststop_paths.any?{|path| File.exists? "#{path}/reststop.rb" }
42
+ alt_reststop_paths.each{|path| $: << path}
43
+ end
44
44
 
45
45
  require 'reststop'
46
46
 
47
- gem 'rufus-scheduler', '~> 1.0.7'
47
+ require 'active_record'
48
+ require 'active_support'
49
+
50
+ require 'camping/db'
51
+
52
+ gem 'rufus-scheduler', '>=1.0.7'
48
53
  require 'rufus/scheduler'
@@ -39,7 +39,7 @@ module Taskr::Models
39
39
  validates_presence_of :task_actions
40
40
  validates_associated :task_actions
41
41
 
42
- def schedule!(scheduler)
42
+ def schedule!(scheduler = Taskr.scheduler)
43
43
  case schedule_method
44
44
  when 'cron'
45
45
  method = :schedule
@@ -62,11 +62,20 @@ module Taskr::Models
62
62
 
63
63
  $LOG.debug "Scheduling task #{name.inspect}: #{self.inspect}"
64
64
 
65
- if task_actions.length > 0
66
- action = prepare_action
65
+ if self.new_record? # Need to distinguish between the edit/create cases. "Edit" needs to reload the task_actions or nothing works; "Create" needs NOT to relaod the actions, or the validations kick in and nothing works. FIXME!!!!!
66
+ if task_actions.length > 0
67
+ action = prepare_action
68
+ else
69
+ $LOG.warn "Task #{name.inspect} has no actions and as a result will not be scheduled!"
70
+ return false
71
+ end
67
72
  else
68
- $LOG.warn "Task #{name.inspect} has no actions and as a result will not be scheduled!"
69
- return false
73
+ if task_actions(true).length > 0
74
+ action = prepare_action
75
+ else
76
+ $LOG.warn "Task #{name.inspect} has no actions and as a result will not be scheduled!"
77
+ return false
78
+ end
70
79
  end
71
80
 
72
81
  job_id = scheduler.send(method, t || schedule_when, :schedulable => action)
@@ -149,6 +158,7 @@ module Taskr::Models
149
158
  :class_name => 'TaskActionParameter',
150
159
  :foreign_key => :task_action_id,
151
160
  :dependent => :destroy
161
+ alias_method :parameters, :action_parameters
152
162
 
153
163
  has_many :log_entries
154
164
  has_one :last_log_entry,
@@ -170,6 +180,10 @@ module Taskr::Models
170
180
  self[:action_class_name].constantize
171
181
  end
172
182
 
183
+ def description
184
+ action_class.description
185
+ end
186
+
173
187
  def to_xml(options = {})
174
188
  options[:indent] ||= 2
175
189
  xml = options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
@@ -186,7 +200,7 @@ module Taskr::Models
186
200
  end
187
201
 
188
202
  def to_s
189
- "#{self.class.name.demodulize}(#{task_action})"
203
+ "#{self.class.name.demodulize}(#{action_class})"
190
204
  end
191
205
  end
192
206
 
@@ -202,7 +216,7 @@ module Taskr::Models
202
216
  xml.tag!('id', {:type => 'integer'}, id)
203
217
  xml.tag!('name', name)
204
218
  xml.tag!('value') do
205
- xml.cdata!(value)
219
+ xml.cdata!(value.to_s)
206
220
  end
207
221
  end
208
222
  end
@@ -247,6 +261,11 @@ module Taskr::Models
247
261
  :data => data
248
262
  )
249
263
  end
264
+
265
+ # Send SNMP traps through net-snmp if Taskr is configured to send them.
266
+ if Taskr::Conf[:snmp] && Taskr::Conf[:snmp][:send_traps]
267
+ send_snmp_trap(level, task, action, data)
268
+ end
250
269
  end
251
270
 
252
271
  # Produces a Logger-like class that will create log entries for the given
@@ -261,6 +280,57 @@ module Taskr::Models
261
280
  log(level, action, data)
262
281
  end
263
282
  end
283
+
284
+ def send_snmp_trap(level, task, action, data)
285
+ snmp_community = Taskr::Conf[:snmp][:community]
286
+ enterprise_oid = Taskr::Conf[:snmp][:enterprise_oid] || '1.3.6.1.4.1.55555.7007'
287
+ to_host = Taskr::Conf[:snmp][:to_host]
288
+ snmp_persistent_dir = Taskr::Conf[:snmp][:snmp_persistent_dir] || '/tmp'
289
+ my_host = ENV['HOSTNAME'] || `hostname`.strip || 'taskr'
290
+
291
+
292
+ task_oid = '1.3.6.1.4.1.55555.7007.1'
293
+ task_typ = 's'
294
+ task_val = task.to_s.gsub(/"/, '\"')
295
+
296
+
297
+ # see http://www.oid-info.com/get/1.3.6.1.4.1.9.5.1.14.4.1.2
298
+ level_oid = '1.3.6.1.4.1.9.5.1.14.4.1.2'
299
+ level_typ = 'i'
300
+ case level
301
+ when 'DEBUG' then level_val = 8
302
+ when 'INFO' then level_val = 7
303
+ when 'WARN' then level_val = 5
304
+ when 'ERROR' then level_val = 4
305
+ end
306
+
307
+ # see http://www.oid-info.com/get/1.3.6.1.4.1.9.9.41.1.2.3.1.4
308
+ sevr_oid = '1.3.6.1.4.1.9.9.41.1.2.3.1.4'
309
+ sevr_typ = 's'
310
+ sevr_val = level
311
+
312
+ # TODO: make msg format configurable
313
+ msg = ("Taskr #{level} on task #{task}[#{action}]: #{data}").gsub(/"/, '\"')
314
+
315
+ # see http://www.oid-info.com/get/1.3.6.1.4.1.9.9.41.1.2.3.1.5
316
+ msg_oid = '1.3.6.1.4.1.9.9.41.1.2.3.1.5'
317
+ msg_typ = 's'
318
+ msg_val = msg
319
+
320
+ cmd = %{snmptrap -v 1 -c #{snmp_community} #{to_host} #{enterprise_oid} #{my_host} 6 #{level_val} '' \
321
+ #{level_oid} #{level_typ} "#{level_val}" \
322
+ #{sevr_oid} #{sevr_typ} "#{sevr_val}" \
323
+ #{task_oid} #{task_typ} "#{task_val}" \
324
+ #{msg_oid} #{msg_typ} "#{msg_val}"}
325
+
326
+ # Band-aid fix for bug in Net-SNMP.
327
+ # See http://sourceforge.net/tracker/index.php?func=detail&aid=1588455&group_id=12694&atid=112694
328
+ ENV['SNMP_PERSISTENT_DIR'] ||= snmp_persistent_dir
329
+
330
+ $LOG.debug "SENDING SNMP TRAP: #{cmd}"
331
+
332
+ `#{cmd}`
333
+ end
264
334
  end
265
335
 
266
336
  # Exposes a Logger-like interface for logging entries for some particular
@@ -1,7 +1,7 @@
1
1
  module Taskr #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 3
4
+ MINOR = 4
5
5
  TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
@@ -24,11 +24,11 @@ module Taskr::Views
24
24
  CONTENT_TYPE = 'text/xml'
25
25
 
26
26
  def tasks_list
27
- @tasks.to_xml(:root => 'tasks')#, :include => [:task_actions])
27
+ @tasks.to_xml(:root => 'tasks', :include => [:task_actions])
28
28
  end
29
29
 
30
30
  def view_task
31
- @task.to_xml(:root => 'task')#, :include => [:task_actions])
31
+ @task.to_xml(:root => 'task', :include => [:task_actions])
32
32
  end
33
33
  end
34
34
 
@@ -52,6 +52,7 @@ module Taskr::Views
52
52
  th "Job ID"
53
53
  th "Created On"
54
54
  th "Created By"
55
+ th ""
55
56
  end
56
57
  end
57
58
  tbody do
@@ -73,6 +74,7 @@ module Taskr::Views
73
74
  td(:class => "job-id") {t.scheduler_job_id}
74
75
  td t.created_on
75
76
  td t.created_by
77
+ td {a(:href => R(t, 'edit')) {"Edit"}}
76
78
  end
77
79
  end
78
80
  end
@@ -83,6 +85,96 @@ module Taskr::Views
83
85
  end
84
86
  end
85
87
 
88
+ def edit_task
89
+ html_scaffold do
90
+ script(:type => 'text/javascript') do
91
+ %{
92
+ function show_action_parameters(num) {
93
+ new Ajax.Updater('parameters_'+num, '#{R(Actions)}', {
94
+ method: 'get',
95
+ parameters: {
96
+ id: $F('action_class_name_'+num),
97
+ action: 'parameters_form',
98
+ num: num
99
+ }
100
+ });
101
+ }
102
+ }
103
+ end
104
+
105
+ a(:href => R(Tasks, :list)) {"Back to Task List"}
106
+
107
+ form :method => 'put', :action => R(@task, 'update') do
108
+ h1 "Edit Task \"#{@task.name}\""
109
+
110
+ p do
111
+ label 'name'
112
+ br
113
+ input :type => 'text', :name => 'name', :value => @task.name, :size => 40
114
+ end
115
+
116
+ p do
117
+ label 'schedule'
118
+ br
119
+ select(:name => 'schedule_method') do
120
+ ['every','at','in','cron'].each do |method|
121
+ if @task.schedule_method == method
122
+ option(:value => method, :selected => 'selected') {method}
123
+ else
124
+ option(:value => method) {method}
125
+ end
126
+ end
127
+ end
128
+ input :type => 'text', :name => 'schedule_when', :value => @task.schedule_when, :size => 30
129
+ end
130
+
131
+ p do
132
+ label 'description/memo'
133
+ br
134
+ textarea(:name => 'memo', :cols => '60', :rows => '4'){@task.memo}
135
+ end
136
+
137
+ p do
138
+ label "Actions:"
139
+ @task.task_actions.each do |action|
140
+ div {action_parameters_form(action)}
141
+ end
142
+ # div do
143
+ # if @task.task_actions.length > 1
144
+ # ol(:style => 'padding-left: 20px') do
145
+ # @task.task_actions.each do |ta|
146
+ # html_task_action_li(ta)
147
+ # end
148
+ # end
149
+ # else
150
+ # html_task_action_li(@task.task_actions.first)
151
+ # end
152
+ # end
153
+ end
154
+
155
+
156
+ p do
157
+ a(:id => 'add_action', :href => '#'){'Add another action'}
158
+ end
159
+
160
+ script(:type => 'text/javascript') do
161
+ %{
162
+ Event.observe('add_action', 'click', function() {
163
+ new Ajax.Updater('add_action', '#{R(Actions, :new)}', {
164
+ method: 'get',
165
+ parameters: { num: $$('select.action_class_name').size() },
166
+ insertion: Insertion.Before
167
+ });
168
+ return false;
169
+ })
170
+ }
171
+ end
172
+
173
+ button(:type => 'submit') {"Save"}
174
+ end
175
+ end
176
+ end
177
+
86
178
  def new_task
87
179
  html_scaffold do
88
180
  script(:type => 'text/javascript') do
@@ -158,6 +250,9 @@ module Taskr::Views
158
250
  form(:method => 'put', :style => 'display: inline', :action => R(@task, 'run')) do
159
251
  button(:type => 'submit', :value => 'run') {"Run Now!"}
160
252
  end
253
+ form(:method => 'put', :style => 'display: inline', :action => R(@task, 'reload')) do
254
+ button(:type => 'submit', :value => 'reload') {"Reload!"}
255
+ end
161
256
  br
162
257
  a(:href => R(Tasks, :list)) {"Back to Task List"}
163
258
 
@@ -222,7 +317,40 @@ module Taskr::Views
222
317
  end
223
318
  end
224
319
 
225
- iframe(:src => R(LogEntries, :list, :task_id => @task.id), :style => 'width: 100%; margin-top: 20px;')
320
+ script %{
321
+ function clickbold(el) {
322
+ $$('#logfilter a').each(function(a){a.style.fontWeight = 'normal'})
323
+ el.style.fontWeight = 'bold'
324
+ }
325
+ }
326
+
327
+ p(:style => "margin-top: 20px; border-top: 1px dotted black; padding-top: 10px", :id => 'logfilter') do
328
+ strong "Show: "
329
+ a(:href => R(LogEntries, :list, :task_id => @task.id, :since => (Time.now - 1.day).to_formatted_s(:db)),
330
+ :target => 'log', :onclick => "clickbold(this)", :style => 'font-weight: bold') {"Last 24 Hours"}
331
+ text "|"
332
+ a(:href => R(LogEntries, :list, :task_id => @task.id, :since => (Time.now - 2.days).to_formatted_s(:db)),
333
+ :target => 'log', :onclick => "clickbold(this)") {"48 Hours"}
334
+ text "|"
335
+ a(:href => R(LogEntries, :list, :task_id => @task.id),
336
+ :target => 'log', :onclick => "clickbold(this)") {"All"}
337
+ br
338
+
339
+ strong "Level: "
340
+ a(:href => R(LogEntries, :list, :task_id => @task.id, :level => 'DEBUG'),
341
+ :target => 'log', :onclick => "clickbold(this)") {"DEBUG"}
342
+ text "|"
343
+ a(:href => R(LogEntries, :list, :task_id => @task.id, :level => 'INFO'),
344
+ :target => 'log', :onclick => "clickbold(this)") {"INFO"}
345
+ text "|"
346
+ a(:href => R(LogEntries, :list, :task_id => @task.id, :level => 'WARN'),
347
+ :target => 'log', :onclick => "clickbold(this)") {"WARN"}
348
+ text "|"
349
+ a(:href => R(LogEntries, :list, :task_id => @task.id, :level => 'ERROR'),
350
+ :target => 'log', :onclick => "clickbold(this)") {"ERROR"}
351
+ end
352
+ iframe(:src => R(LogEntries, :list, :task_id => @task.id, :since => (Time.now - 1.day).to_formatted_s(:db)),
353
+ :style => 'width: 100%; height: 300px', :name => 'log')
226
354
  end
227
355
  end
228
356
 
@@ -248,16 +376,20 @@ module Taskr::Views
248
376
  end
249
377
  end
250
378
 
251
- def action_parameters_form
379
+ def action_parameters_form(action = @action)
252
380
  @num ||= 0
253
381
 
254
- p {em @action.description}
382
+ p {em action.description}
255
383
 
256
- @action.parameters.each do |param|
384
+ action.parameters.each do |param|
257
385
  p do
258
386
  label param
259
387
  br
260
- input :type => 'text', :name => "action[#{@num}][#{param}]", :size => 50
388
+ unless action.is_a?(Taskr::Models::TaskAction) # Setting up a new task
389
+ input :type => 'text', :name => "action[#{@num}][#{param}]", :size => 50
390
+ else # Editing an existing task
391
+ input :type => 'text', :name => "action[action_id_#{action.id}][#{param.name}]", :size => 50, :value => param.value
392
+ end
261
393
  end
262
394
  end
263
395
  end
@@ -286,6 +418,12 @@ module Taskr::Views
286
418
 
287
419
  def log_entries_list
288
420
  h2 "Log"
421
+
422
+ p(:style => 'margin: 0px') do
423
+ em(:style => "font-weight: normal; font-size: 9pt"){"Entries since #{@since}<br />"} unless @since.blank?
424
+ em(:style => "font-weight: normal; font-size: 9pt"){"With levels #{@level.join(", ")}<br />"} unless @level.blank?
425
+ end
426
+
289
427
  table do
290
428
  @log_entries.each do |entry|
291
429
  case entry.level.downcase.intern
@@ -37,22 +37,40 @@ class Taskr4railsController < ActionController::Base
37
37
 
38
38
  io = StringIO.new
39
39
  prev_stdout, prev_stderr = $stdout, $stderr
40
- output = ""
41
40
  $stdout = io
42
41
  $stderr = io
42
+ err = false # start off assuming there's no error
43
43
  begin
44
- eval(params[:ruby_code]) if params[:ruby_code]
45
- #output << (`cd #{RAILS_ROOT}; #{params[:shell_command]}` || "") if params[:shell_command]
46
- err = false
44
+ if !params[:dont_wait].blank? && params[:dont_wait] != 0 && params[:dont_wait] != '0'
45
+ puts "Task #{params[:task_name].inspect} will be forked to its own process because the 'dont_wait' parameter was set to true."
46
+
47
+ # Monkey-patch Mongrel to not remove its pid file in the child
48
+ # See: http://geekblog.vodpod.com/?p=26
49
+ require 'mongrel'
50
+ Mongrel::Configurator.class_eval("def remove_pid_file; puts 'child no-op'; end")
51
+
52
+ pid = fork do
53
+ RAILS_DEFAULT_LOGGER.debug("*** Taskr4Rails -- Executing task #{params[:task_name].inspect} with Ruby code: #{params[:ruby_code]}")
54
+ eval(params[:ruby_code])
55
+ end
56
+
57
+ RAILS_DEFAULT_LOGGER.debug("*** Taskr4Rails -- Task #{params[:task_name].inspect} is being forked into its own thread.")
58
+
59
+ Process.detach(pid)
60
+ else
61
+ RAILS_DEFAULT_LOGGER.debug("*** Taskr4Rails -- Executing task #{params[:task_name].inspect} with Ruby code: #{params[:ruby_code]}")
62
+ RAILS_DEFAULT_LOGGER.debug("*** Taskr4Rails -- Waiting for task #{params[:task_name].inspect} to complete.")
63
+ eval(params[:ruby_code])
64
+ end
47
65
  rescue => e
48
- output << "#{e.class}: #{e}\n\nBACKTRACE:\n#{e.backtrace.join("\n")}"
66
+ puts "#{e.class}: #{e}\n\nBACKTRACE:\n#{e.backtrace.join("\n")}"
49
67
  err = true
50
68
  end
51
69
  $stdout = prev_stdout
52
70
  $stderr = prev_stderr
53
- output = io.read(nil)
71
+ output = io.string
54
72
 
55
73
  render :text => output, :status => (err ? 500 : 200)
56
74
  end
57
75
 
58
- end
76
+ end
metadata CHANGED
@@ -1,28 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taskr
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Zukowski
8
+ - David Palm
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
12
 
12
- date: 2008-06-19 00:00:00 -04:00
13
+ date: 2008-12-23 00:00:00 -05:00
13
14
  default_executable:
14
15
  dependencies:
15
16
  - !ruby/object:Gem::Dependency
16
17
  name: picnic
18
+ type: :runtime
17
19
  version_requirement:
18
20
  version_requirements: !ruby/object:Gem::Requirement
19
21
  requirements:
20
22
  - - ~>
21
23
  - !ruby/object:Gem::Version
22
- version: 0.6.4
24
+ version: 0.7.0
23
25
  version:
24
26
  - !ruby/object:Gem::Dependency
25
27
  name: reststop
28
+ type: :runtime
26
29
  version_requirement:
27
30
  version_requirements: !ruby/object:Gem::Requirement
28
31
  requirements:
@@ -32,15 +35,17 @@ dependencies:
32
35
  version:
33
36
  - !ruby/object:Gem::Dependency
34
37
  name: restr
38
+ type: :runtime
35
39
  version_requirement:
36
40
  version_requirements: !ruby/object:Gem::Requirement
37
41
  requirements:
38
42
  - - ~>
39
43
  - !ruby/object:Gem::Version
40
- version: 0.4.0
44
+ version: 0.5.0
41
45
  version:
42
46
  - !ruby/object:Gem::Dependency
43
47
  name: rufus-scheduler
48
+ type: :runtime
44
49
  version_requirement:
45
50
  version_requirements: !ruby/object:Gem::Requirement
46
51
  requirements:
@@ -48,8 +53,20 @@ dependencies:
48
53
  - !ruby/object:Gem::Version
49
54
  version: 1.0.7
50
55
  version:
56
+ - !ruby/object:Gem::Dependency
57
+ name: hoe
58
+ type: :development
59
+ version_requirement:
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: 1.8.2
65
+ version:
51
66
  description: cron-like scheduler service with a RESTful interface
52
- email: matt at roughest dot net
67
+ email:
68
+ - matt at roughest dot net
69
+ - dvd plm on googles free email service
53
70
  executables:
54
71
  - taskr
55
72
  - taskr-ctl
@@ -120,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
120
137
  requirements: []
121
138
 
122
139
  rubyforge_project: taskr
123
- rubygems_version: 1.0.1
140
+ rubygems_version: 1.3.1
124
141
  signing_key:
125
142
  specification_version: 2
126
143
  summary: cron-like scheduler service with a RESTful interface