taskr 0.3.0 → 0.4.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/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
|