taskr 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +20 -1
- data/Rakefile +28 -5
- data/bin/taskr +0 -0
- data/bin/taskr-ctl +0 -0
- data/config.example.yml +13 -0
- data/lib/taskr/actions.rb +17 -11
- data/lib/taskr/controllers.rb +132 -5
- data/lib/taskr/environment.rb +24 -19
- data/lib/taskr/models.rb +77 -7
- data/lib/taskr/version.rb +1 -1
- data/lib/taskr/views.rb +145 -7
- data/taskr4rails/lib/taskr4rails_controller.rb +25 -7
- metadata +23 -6
data/History.txt
CHANGED
@@ -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`[
|
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.
|
57
|
+
['picnic', '~> 0.7.0'],
|
58
58
|
['reststop', '~> 0.3.0'],
|
59
|
-
['restr', '~> 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
|
data/bin/taskr-ctl
CHANGED
File without changes
|
data/config.example.yml
CHANGED
@@ -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
|
|
data/lib/taskr/actions.rb
CHANGED
@@ -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
|
-
|
272
|
-
|
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.
|
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
|
data/lib/taskr/controllers.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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 = ?',
|
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
|
data/lib/taskr/environment.rb
CHANGED
@@ -15,34 +15,39 @@
|
|
15
15
|
|
16
16
|
$: << File.dirname(File.expand_path(__FILE__))
|
17
17
|
|
18
|
-
# Try to load local
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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'
|
data/lib/taskr/models.rb
CHANGED
@@ -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.
|
66
|
-
|
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
|
-
|
69
|
-
|
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}(#{
|
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
|
data/lib/taskr/version.rb
CHANGED
data/lib/taskr/views.rb
CHANGED
@@ -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'
|
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'
|
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
|
-
|
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
|
382
|
+
p {em action.description}
|
255
383
|
|
256
|
-
|
384
|
+
action.parameters.each do |param|
|
257
385
|
p do
|
258
386
|
label param
|
259
387
|
br
|
260
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
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.
|
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.
|
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-
|
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.
|
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.
|
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:
|
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.
|
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
|