taskr 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +1 -27
- data/History.txt +41 -0
- data/Rakefile +7 -6
- data/config.example.yml +92 -16
- data/lib/taskr.rb +2 -2
- data/lib/taskr/actions.rb +77 -16
- data/lib/taskr/controllers.rb +54 -7
- data/lib/taskr/environment.rb +2 -2
- data/lib/taskr/models.rb +151 -24
- data/lib/taskr/version.rb +2 -2
- data/lib/taskr/views.rb +44 -9
- metadata +20 -11
data/CHANGELOG.txt
CHANGED
@@ -1,27 +1 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
* Updated for compatibility with picnic 0.6.1.
|
4
|
-
|
5
|
-
=== 0.2.0 :: 2008-01-03
|
6
|
-
|
7
|
-
* Added Taskr4rails action and accompanying Rails plugin for
|
8
|
-
executing code on a remote Rails server. See
|
9
|
-
http://code.google.com/p/ruby-taskr/wiki/Taskr4rails for
|
10
|
-
details.
|
11
|
-
* The Howlr action now takes an optional content_type parameter
|
12
|
-
to allow for sending out HTML-formatted messages.
|
13
|
-
* Better error reporting for Restr-based actions. The remote server's
|
14
|
-
response body is now included in Taskr's error report and the
|
15
|
-
remote output is printed to the log more cleanly.
|
16
|
-
* Change to REST API: the 'action(s)' parameter is now a bit looser.
|
17
|
-
You can use 'action' or 'actions' interchangeably, whether you're
|
18
|
-
scheduling multiple actions or just one.
|
19
|
-
* Fixed some routing problems in the HTML views.
|
20
|
-
* The ActiveResource and PHP examples have been updated with
|
21
|
-
some more illustrative code and better explanations.
|
22
|
-
* Misc bugs fixed while writing extensive documentation. See
|
23
|
-
wiki pages at http://code.google.com/p/ruby-taskr/w/list.
|
24
|
-
|
25
|
-
=== 0.1.0 :: 2007-12-21
|
26
|
-
|
27
|
-
* First public release.
|
1
|
+
See History.txt
|
data/History.txt
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
=== 0.3.0 :: 2008-06-19
|
2
|
+
|
3
|
+
* Added "Run Now" function allowing a task to be immediately triggered.
|
4
|
+
* The Rest action can now take the 'params' parameter as a CGI string
|
5
|
+
(e.g. foo=bar&blah=hello&taskr=awesome). The string does not have
|
6
|
+
to be properly URI-encoded, since the algorithm for parsing it simply
|
7
|
+
splits it based on & and = symbols.
|
8
|
+
* Changed Scheduler gem from the old OpenWFE to the new Rufus.
|
9
|
+
* Task executions are now logged to the new log_entries table. These
|
10
|
+
log entries are viewable under the task detail page.
|
11
|
+
* Exceptions raised when a task is run manually are now handled gracefully
|
12
|
+
(or rather, silently, since they should be logged by the action).
|
13
|
+
* Blank parameter values are now correctly stored as NULL rather than 0.
|
14
|
+
|
15
|
+
=== 0.2.1 :: 2008-02-28
|
16
|
+
|
17
|
+
* Updated for compatibility with picnic 0.6.1.
|
18
|
+
|
19
|
+
=== 0.2.0 :: 2008-01-03
|
20
|
+
|
21
|
+
* Added Taskr4rails action and accompanying Rails plugin for
|
22
|
+
executing code on a remote Rails server. See
|
23
|
+
http://code.google.com/p/ruby-taskr/wiki/Taskr4rails for
|
24
|
+
details.
|
25
|
+
* The Howlr action now takes an optional content_type parameter
|
26
|
+
to allow for sending out HTML-formatted messages.
|
27
|
+
* Better error reporting for Restr-based actions. The remote server's
|
28
|
+
response body is now included in Taskr's error report and the
|
29
|
+
remote output is printed to the log more cleanly.
|
30
|
+
* Change to REST API: the 'action(s)' parameter is now a bit looser.
|
31
|
+
You can use 'action' or 'actions' interchangeably, whether you're
|
32
|
+
scheduling multiple actions or just one.
|
33
|
+
* Fixed some routing problems in the HTML views.
|
34
|
+
* The ActiveResource and PHP examples have been updated with
|
35
|
+
some more illustrative code and better explanations.
|
36
|
+
* Misc bugs fixed while writing extensive documentation. See
|
37
|
+
wiki pages at http://code.google.com/p/ruby-taskr/w/list.
|
38
|
+
|
39
|
+
=== 0.1.0 :: 2007-12-21
|
40
|
+
|
41
|
+
* First public release.
|
data/Rakefile
CHANGED
@@ -11,9 +11,9 @@ require 'hoe'
|
|
11
11
|
include FileUtils
|
12
12
|
require File.join(File.dirname(__FILE__), 'lib', 'taskr', 'version')
|
13
13
|
|
14
|
-
AUTHOR = "
|
15
|
-
EMAIL = "
|
16
|
-
DESCRIPTION = "
|
14
|
+
AUTHOR = "Matt Zukowski" # can also be an array of Authors
|
15
|
+
EMAIL = "matt at roughest dot net"
|
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
|
19
19
|
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
@@ -54,9 +54,10 @@ 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.
|
58
|
-
['reststop', '~>0.
|
59
|
-
'
|
57
|
+
['picnic', '~> 0.6.4'],
|
58
|
+
['reststop', '~> 0.3.0'],
|
59
|
+
['restr', '~> 0.4.0'],
|
60
|
+
['rufus-scheduler', '~> 1.0.7']
|
60
61
|
]
|
61
62
|
p.spec_extras = {:executables => ['taskr', 'taskr-ctl']}
|
62
63
|
end
|
data/config.example.yml
CHANGED
@@ -4,10 +4,10 @@
|
|
4
4
|
# !!! Be sure to use spaces instead of tabs for indentation, as YAML is very
|
5
5
|
# !!! sensitive to white-space inconsistencies!
|
6
6
|
|
7
|
-
##### HTTP SERVER
|
7
|
+
##### HTTP SERVER ##############################################################
|
8
8
|
|
9
|
-
# Under what HTTP environment are you running the Fluxr server? The following
|
10
|
-
# are currently supported:
|
9
|
+
# Under what HTTP environment are you running the Fluxr server? The following
|
10
|
+
# methods are currently supported:
|
11
11
|
#
|
12
12
|
# webrick -- simple stand-alone HTTP server; this is the default method
|
13
13
|
# mongrel -- fast stand-alone HTTP server; much faster than webrick, but
|
@@ -34,16 +34,16 @@ port: 7007
|
|
34
34
|
#server: mongrel
|
35
35
|
#port: 7007
|
36
36
|
|
37
|
-
# It is possible to run mongrel over SSL, but you will need to use a reverse
|
38
|
-
# (try Pound or Apache).
|
37
|
+
# It is possible to run mongrel over SSL, but you will need to use a reverse
|
38
|
+
# proxy (try Pound or Apache).
|
39
39
|
|
40
40
|
|
41
|
-
##### DATABASE
|
41
|
+
##### DATABASE #################################################################
|
42
42
|
|
43
43
|
# Taskr needs a database to persist its state between restarts.
|
44
44
|
#
|
45
|
-
# By default, we use MySQL, since it is widely used and does not require any
|
46
|
-
# ruby libraries besides ActiveRecord.
|
45
|
+
# By default, we use MySQL, since it is widely used and does not require any
|
46
|
+
# additional ruby libraries besides ActiveRecord.
|
47
47
|
#
|
48
48
|
# With MySQL, your config would be something like the following:
|
49
49
|
# (be sure to create the taskr database in MySQL beforehand,
|
@@ -57,8 +57,8 @@ database:
|
|
57
57
|
host: localhost
|
58
58
|
|
59
59
|
|
60
|
-
# Instead of MySQL you can use SQLite3, PostgreSQL, MSSQL, or anything else
|
61
|
-
# by ActiveRecord.
|
60
|
+
# Instead of MySQL you can use SQLite3, PostgreSQL, MSSQL, or anything else
|
61
|
+
# supported by ActiveRecord.
|
62
62
|
#
|
63
63
|
# If you do not have a database server available, you can try using the SQLite3
|
64
64
|
# back-end. SQLite3 does not require it's own server. Instead all data is stored
|
@@ -70,20 +70,39 @@ database:
|
|
70
70
|
# dbfile: /var/lib/taskr.db
|
71
71
|
|
72
72
|
|
73
|
-
##### AUTHENTICATION
|
73
|
+
##### AUTHENTICATION ###########################################################
|
74
74
|
|
75
|
-
# Taskr supports
|
76
|
-
|
77
|
-
|
75
|
+
# Taskr supports two methods for authentication: Basic HTTP and CAS.
|
76
|
+
|
77
|
+
### Basic HTTP Example
|
78
|
+
# Uset the following if you want Taskr to demand a username and password from
|
79
|
+
# the user using basic HTTP authentication clients. Note that this isn't very
|
80
|
+
# secure unless you run Taskr over HTTPS (see the webrick SSL example above).
|
78
81
|
|
79
82
|
#authentication:
|
83
|
+
# method: basic
|
80
84
|
# username: taskr
|
81
85
|
# password: task!
|
82
86
|
|
87
|
+
### CAS Example
|
88
|
+
# Use the following if you want Taskr to demand a username and password using
|
89
|
+
# Single Sign-On CAS (Central Authentication System). For more information on
|
90
|
+
# CAS see http://code.google.com/p/rubycas-server/.
|
91
|
+
# The cas_base_url setting should be the URL of your CAS server.
|
92
|
+
|
93
|
+
#authenticatoin:
|
94
|
+
# method: cas
|
95
|
+
# cas_base_url: https://login.example.foo/cas
|
96
|
+
|
83
97
|
|
84
|
-
##### LOGGING
|
98
|
+
##### LOGGING ##################################################################
|
85
99
|
|
86
|
-
|
100
|
+
### System Log
|
101
|
+
# This is the general server log where Taskr writes information about web requests.
|
102
|
+
# You'll want to look here if you encounter general problems with Taskr's
|
103
|
+
# operation. Do not confuse this log with the task log, which records information
|
104
|
+
# about the various schedules jobs. The task log is kept in your database and
|
105
|
+
# is completely separate (see below).
|
87
106
|
#
|
88
107
|
# By default, we will try to create a log file named 'taskr.log' in the current
|
89
108
|
# directory (the directory where you're running taskr from). A better place to put
|
@@ -98,3 +117,60 @@ log:
|
|
98
117
|
level: DEBUG
|
99
118
|
# file: /var/log/taskr.log
|
100
119
|
# level: INFO
|
120
|
+
|
121
|
+
### Task Log
|
122
|
+
# This is where Taskr records detailed information about your scheduled jobs (tasks).
|
123
|
+
# Every time a task is triggered, information about the execution is recorded
|
124
|
+
# in this log (i.e. whether the task executed successfuly, its output, etc.)
|
125
|
+
# The Task Log is stored in the Taskr database under the log_entries table.
|
126
|
+
#
|
127
|
+
# Here you can configure the logging level. The "INFO" level is generally a good
|
128
|
+
# choice for production systems, although you may want to keep this at "DEBUG"
|
129
|
+
# if you want to keep a close eye on your tasks -- this is a good idea
|
130
|
+
# especially when setting up a new Taskr system.
|
131
|
+
#
|
132
|
+
# Note that in order to prevent log data from accumulating indefinitely, you
|
133
|
+
# should set up a "RotateTaskLog" action in your Taskr server. This will
|
134
|
+
# delete old logs as per the parameters you provide (do this via the
|
135
|
+
# normal Taskr web interface as you would to set up any other task -- just
|
136
|
+
# select "RotateTaskLog" for the action).
|
137
|
+
|
138
|
+
task_log:
|
139
|
+
# ERROR, WARN, INFO, or DEBUG
|
140
|
+
level: DEBUG
|
141
|
+
|
142
|
+
|
143
|
+
##### MISC #####################################################################
|
144
|
+
|
145
|
+
### Custom Task Definitions
|
146
|
+
# You can define your own task action behaviour by specifying an external ruby
|
147
|
+
# filename. On startup, Taskr will load any action definitions from this file,
|
148
|
+
# making them available when scheduling new tasks (you should see your custom
|
149
|
+
# task classes listed in the Actions pulldown on the new task page).
|
150
|
+
#
|
151
|
+
# Custom task definitions must:
|
152
|
+
# - be defined under the Taskr::Actions module
|
153
|
+
# - extend the Taskr::Actions::Base class
|
154
|
+
# - implement a 'execute' method (this is what will be executed when your
|
155
|
+
# action is triggered)
|
156
|
+
# - define a 'parameters' class attribute which should be an Array listing
|
157
|
+
# the names of the parameters that your action takes
|
158
|
+
# - define a 'description' class attribute which should provide a brief
|
159
|
+
# description for what your action does (this will show up in the web UI)
|
160
|
+
#
|
161
|
+
# Here is an example of a trivial custom action:
|
162
|
+
#
|
163
|
+
# module Taskr
|
164
|
+
# module Actions
|
165
|
+
# class MyCustomAction < Taskr::Actions::Base
|
166
|
+
# self.parameters = ['alpha', 'beta']
|
167
|
+
# self.description = "Multiplies the given parameters and prints the result to stdout."
|
168
|
+
#
|
169
|
+
# def execute
|
170
|
+
# puts parameters['alpha'].to_i * parameters['beta'].to_i
|
171
|
+
# end
|
172
|
+
# end
|
173
|
+
# end
|
174
|
+
# end
|
175
|
+
|
176
|
+
#external_actions: /path/to/file.rb
|
data/lib/taskr.rb
CHANGED
@@ -76,9 +76,9 @@ def Taskr.create
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def Taskr.prestart
|
79
|
-
$LOG.info "Starting
|
79
|
+
$LOG.info "Starting Rufus Scheduler..."
|
80
80
|
|
81
|
-
Taskr.scheduler =
|
81
|
+
Taskr.scheduler = Rufus::Scheduler.new
|
82
82
|
Taskr.scheduler.start
|
83
83
|
|
84
84
|
$LOG.debug "Scheduler is: #{Taskr.scheduler.inspect}"
|
data/lib/taskr/actions.rb
CHANGED
@@ -13,7 +13,7 @@
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
14
14
|
# along with Taskr. If not, see <http://www.gnu.org/licenses/>.
|
15
15
|
|
16
|
-
require '
|
16
|
+
require 'rufus/scheduler'
|
17
17
|
|
18
18
|
#require 'active_resource'
|
19
19
|
require 'restr'
|
@@ -39,13 +39,14 @@ module Taskr
|
|
39
39
|
# The base class for all Actions.
|
40
40
|
# Extend this to define your own custom Action.
|
41
41
|
class Base
|
42
|
-
include
|
42
|
+
include Rufus::Schedulable
|
43
43
|
|
44
44
|
class_inheritable_accessor :parameters
|
45
45
|
class_inheritable_accessor :description
|
46
46
|
|
47
47
|
attr_accessor :parameters
|
48
48
|
attr_accessor :task
|
49
|
+
attr_accessor :task_action
|
49
50
|
|
50
51
|
def initialize(parameters)
|
51
52
|
self.parameters = HashWithIndifferentAccess.new(parameters)
|
@@ -72,6 +73,10 @@ module Taskr
|
|
72
73
|
raise e
|
73
74
|
end
|
74
75
|
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
"#{self.class.name}(#{parameters.inspect})"
|
79
|
+
end
|
75
80
|
end
|
76
81
|
|
77
82
|
# Do not extend this class. It is used internally to schedule multiple
|
@@ -79,7 +84,7 @@ module Taskr
|
|
79
84
|
#
|
80
85
|
# If you want to define your own custom Action, extend Taskr::Actions::Base
|
81
86
|
class Multi
|
82
|
-
include
|
87
|
+
include Rufus::Schedulable
|
83
88
|
|
84
89
|
attr_accessor :actions
|
85
90
|
attr_accessor :task
|
@@ -90,22 +95,39 @@ module Taskr
|
|
90
95
|
|
91
96
|
def trigger(trigger_args = {})
|
92
97
|
begin
|
93
|
-
$LOG.info("Executing task #{self.name}")
|
98
|
+
$LOG.info("Executing task #{self.task.name}")
|
94
99
|
actions.each do |a|
|
95
100
|
a.execute
|
101
|
+
LogEntry.info(a, "Action #{a} executed.")
|
96
102
|
end
|
97
103
|
# TODO: maybe we should store last_triggered time on a per-action basis?
|
98
104
|
task.update_attribute(:last_triggered, Time.now)
|
105
|
+
task.update_attribute(:last_triggered_error, nil)
|
99
106
|
rescue => e
|
100
107
|
$LOG.error(e)
|
101
|
-
$LOG.debug("#{e.
|
108
|
+
$LOG.debug("#{e.backtrace}")
|
102
109
|
task.update_attribute(:last_triggered, Time.now)
|
103
|
-
task.update_attribute(:last_triggered_error, e)
|
110
|
+
task.update_attribute(:last_triggered_error, {:type => e.class.to_s, :details => "#{e.message}"})
|
111
|
+
# FIXME: Maybe actions should be responseible for logging their errors, otherwise we double-log the same error.
|
112
|
+
LogEntry.error(task, "Task #{task} raised an exception: \n#{e.class}: #{e.message}\n#{e.backtrace}")
|
104
113
|
raise e
|
105
114
|
end
|
106
115
|
end
|
107
116
|
end
|
108
117
|
|
118
|
+
class RotateTaskLog < Base
|
119
|
+
self.parameters = ['delete_old_log_entries_after_days']
|
120
|
+
self.description = "Deletes old task log entries from this Taskr server's database."
|
121
|
+
|
122
|
+
def execute
|
123
|
+
num = parameters['delete_old_log_entries_after_days'].to_i
|
124
|
+
|
125
|
+
cond = ['timestamp < ?', Time.now - num.days]
|
126
|
+
LogEntry.delete_all(cond)
|
127
|
+
LogEntry.debug(task_action, "Deleted log entries with conditions: #{cond.inspect}")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
109
131
|
class Shell < Base
|
110
132
|
self.parameters = ['command', 'as_user']
|
111
133
|
self.description = "Execute a shell command (be careful!)"
|
@@ -118,14 +140,29 @@ module Taskr
|
|
118
140
|
user = nil
|
119
141
|
end
|
120
142
|
|
121
|
-
|
122
|
-
|
123
|
-
else
|
124
|
-
`#{cmd}`
|
143
|
+
unless user.blank?
|
144
|
+
cmd = "sudo -u #{user} #{cmd}"
|
125
145
|
end
|
126
146
|
|
147
|
+
outio = StringIO.new
|
148
|
+
errio = StringIO.new
|
149
|
+
old_stdout, $stdout = $stdout, outio
|
150
|
+
old_stderr, $stderr = $stderr, errio
|
151
|
+
|
152
|
+
out = `#{cmd}`
|
153
|
+
|
154
|
+
err = errio.string
|
155
|
+
out = outio.string
|
156
|
+
LogEntry.debug(task_action, out) unless out.blank?
|
157
|
+
LogEntry.error(task_action, err) unless err.blank?
|
158
|
+
|
159
|
+
$stdout = old_stdout
|
160
|
+
$stderr = old_stderr
|
161
|
+
|
127
162
|
unless $?.success?
|
128
|
-
|
163
|
+
msg = "Shell command #{cmd.inspect} failed (returned code #{$?}): #{out}"
|
164
|
+
LogEntry.error(task_action, msg)
|
165
|
+
raise msg
|
129
166
|
end
|
130
167
|
end
|
131
168
|
end
|
@@ -135,11 +172,26 @@ module Taskr
|
|
135
172
|
self.description = "Execute some Ruby code."
|
136
173
|
|
137
174
|
def execute
|
175
|
+
outio = StringIO.new
|
176
|
+
errio = StringIO.new
|
177
|
+
old_stdout, $stdout = $stdout, outio
|
178
|
+
old_stderr, $stderr = $stderr, errio
|
179
|
+
|
138
180
|
code = parameters['code']
|
139
|
-
eval
|
181
|
+
eval(code)
|
182
|
+
|
183
|
+
err = errio.string
|
184
|
+
out = outio.string
|
185
|
+
LogEntry.debug(task_action, out) unless out.blank?
|
186
|
+
LogEntry.error(task_action, err) unless err.blank?
|
187
|
+
|
188
|
+
$stdout = old_stdout
|
189
|
+
$stderr = old_stderr
|
140
190
|
end
|
141
191
|
end
|
142
|
-
|
192
|
+
|
193
|
+
|
194
|
+
# This is too complicated... we use Restr instead.
|
143
195
|
# class ActiveResource < Base
|
144
196
|
# self.parameters = ['site', 'resource', 'action', 'parameters']
|
145
197
|
# self.description = "Perform a REST call on a remote service using ActiveResource."
|
@@ -207,8 +259,17 @@ module Taskr
|
|
207
259
|
auth['password'] = parameters['password'] if parameters['password']
|
208
260
|
end
|
209
261
|
|
210
|
-
|
211
|
-
|
262
|
+
if parameters['params'].kind_of? String
|
263
|
+
params2 = {}
|
264
|
+
parameters['params'].split('&').collect do |p|
|
265
|
+
key, value = p.split('=')
|
266
|
+
params2[key] = value
|
267
|
+
end
|
268
|
+
parameters['params'] = params2
|
269
|
+
end
|
270
|
+
|
271
|
+
Restr.logger = LogEntry.logger_for_action(task_action)
|
272
|
+
Restr.do(parameters['method'], parameters['url'], (parameters['params'] unless parameters['params'].blank?), auth)
|
212
273
|
end
|
213
274
|
end
|
214
275
|
|
@@ -246,7 +307,7 @@ module Taskr
|
|
246
307
|
}
|
247
308
|
|
248
309
|
|
249
|
-
Restr.logger =
|
310
|
+
Restr.logger = LogEntry.logger_for_action(task_action)
|
250
311
|
Restr.post(parameters['url'], data)
|
251
312
|
end
|
252
313
|
end
|
data/lib/taskr/controllers.rb
CHANGED
@@ -45,25 +45,30 @@ module Taskr::Controllers
|
|
45
45
|
class Tasks < REST 'tasks'
|
46
46
|
include Taskr::Models
|
47
47
|
|
48
|
+
# List of tasks.
|
48
49
|
def list
|
49
50
|
@tasks = Task.find(:all, :include => [:task_actions])
|
50
51
|
|
51
52
|
render :tasks_list
|
52
53
|
end
|
53
54
|
|
55
|
+
# Input for a new task.
|
54
56
|
def new
|
55
57
|
@actions = Taskr::Actions.list
|
56
58
|
|
57
59
|
render :new_task
|
58
60
|
end
|
59
61
|
|
62
|
+
# Retrieve details for an existing task.
|
60
63
|
def read(id)
|
61
64
|
@task = Task.find(id, :include => [:task_actions])
|
62
65
|
|
63
66
|
render :view_task
|
64
67
|
end
|
65
68
|
|
69
|
+
# Create and schedule a new task.
|
66
70
|
def create
|
71
|
+
puts @input.inspect
|
67
72
|
begin
|
68
73
|
# the "0" is for compatibility with PHP's Zend_Rest_Client
|
69
74
|
task_data = @input[:task] || @input["0"] || @input
|
@@ -72,12 +77,14 @@ module Taskr::Controllers
|
|
72
77
|
created_by = @env['REMOTE_HOST']
|
73
78
|
schedule_method = task_data[:schedule_method]
|
74
79
|
schedule_when = task_data[:schedule_when]
|
80
|
+
memo = task_data[:memo]
|
75
81
|
|
76
82
|
@task = Task.new(
|
77
83
|
:name => name,
|
78
84
|
:created_by => created_by,
|
79
85
|
:schedule_method => schedule_method,
|
80
|
-
:schedule_when => schedule_when
|
86
|
+
:schedule_when => schedule_when,
|
87
|
+
:memo => memo
|
81
88
|
)
|
82
89
|
|
83
90
|
# some gymnastics here to provide compatibility for the way various
|
@@ -98,19 +105,19 @@ module Taskr::Controllers
|
|
98
105
|
end
|
99
106
|
|
100
107
|
actions = [actions] unless actions.kind_of? Array
|
101
|
-
puts actions.inspect
|
108
|
+
#puts actions.inspect
|
102
109
|
|
103
110
|
i = 0
|
104
111
|
actions.each do |a|
|
105
|
-
puts a.inspect
|
112
|
+
#puts a.inspect
|
106
113
|
action_class_name = a[:action_class_name]
|
107
114
|
action_class_name = "Taskr::Actions::#{action_class_name}" unless action_class_name =~ /^Taskr::Actions::/
|
108
115
|
|
109
116
|
begin
|
110
117
|
action_class = action_class_name.constantize
|
111
|
-
unless action_class.include?
|
118
|
+
unless action_class.include? Rufus::Schedulable
|
112
119
|
raise ArgumentError,
|
113
|
-
"#{a[:action_class_name].inspect} cannot be used as an action because it does not include the
|
120
|
+
"#{a[:action_class_name].inspect} cannot be used as an action because it does not include the Rufus::Schedulable module."
|
114
121
|
end
|
115
122
|
rescue NameError
|
116
123
|
raise ArgumentError,
|
@@ -119,8 +126,11 @@ module Taskr::Controllers
|
|
119
126
|
|
120
127
|
action = TaskAction.new(:order => a[:order] || i, :action_class_name => action_class_name)
|
121
128
|
|
129
|
+
|
122
130
|
action_class.parameters.each do |p|
|
123
|
-
|
131
|
+
value = a[p]
|
132
|
+
value = nil if value.blank?
|
133
|
+
action.action_parameters << TaskActionParameter.new(:name => p, :value => value)
|
124
134
|
end
|
125
135
|
|
126
136
|
@task.task_actions << action
|
@@ -150,7 +160,24 @@ module Taskr::Controllers
|
|
150
160
|
raise e
|
151
161
|
end
|
152
162
|
end
|
163
|
+
|
164
|
+
def run(id)
|
165
|
+
@task = Task.find(id, :include => [:task_actions])
|
166
|
+
|
167
|
+
action = @task.prepare_action
|
168
|
+
|
169
|
+
LogEntry.info(@task, "Manually executing task #{@task}.")
|
170
|
+
|
171
|
+
begin
|
172
|
+
action.trigger
|
173
|
+
rescue
|
174
|
+
# ok to catch exception silently. it should have gotten logged by the action
|
175
|
+
end
|
176
|
+
|
177
|
+
render :view_task
|
178
|
+
end
|
153
179
|
|
180
|
+
# Unschedule and delete an existing task.
|
154
181
|
def destroy(id)
|
155
182
|
@task = Task.find(id)
|
156
183
|
if @task.scheduler_job_id
|
@@ -158,7 +185,27 @@ module Taskr::Controllers
|
|
158
185
|
Taskr.scheduler.unschedule(@task.scheduler_job_id)
|
159
186
|
end
|
160
187
|
@task.destroy
|
161
|
-
|
188
|
+
|
189
|
+
if @task.frozen?
|
190
|
+
@status = 200
|
191
|
+
if @format == :XML
|
192
|
+
""
|
193
|
+
else
|
194
|
+
return redirect(R(Tasks, :list))
|
195
|
+
end
|
196
|
+
else
|
197
|
+
_error("Task #{id} was not destroyed.", 500)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
class LogEntries < REST 'log_entries'
|
203
|
+
def list
|
204
|
+
@log_entries = LogEntry.find(:all,
|
205
|
+
:conditions => ['task_id = ?', @input[:task_id]],
|
206
|
+
:order => 'timestamp DESC, id DESC')
|
207
|
+
|
208
|
+
render :log_entries_list
|
162
209
|
end
|
163
210
|
end
|
164
211
|
end
|
data/lib/taskr/environment.rb
CHANGED
data/lib/taskr/models.rb
CHANGED
@@ -17,7 +17,7 @@ require 'camping/db'
|
|
17
17
|
require 'openwfe/util/scheduler'
|
18
18
|
require 'date'
|
19
19
|
|
20
|
-
class
|
20
|
+
class Rufus::Scheduler
|
21
21
|
public :duration_to_f
|
22
22
|
end
|
23
23
|
|
@@ -62,26 +62,8 @@ module Taskr::Models
|
|
62
62
|
|
63
63
|
$LOG.debug "Scheduling task #{name.inspect}: #{self.inspect}"
|
64
64
|
|
65
|
-
if task_actions.length
|
66
|
-
|
67
|
-
|
68
|
-
parameters = {}
|
69
|
-
ta.action_parameters.each{|p| parameters[p.name] = p.value}
|
70
|
-
|
71
|
-
action = (ta.action_class.kind_of?(Class) ? ta.action_class : ta.action_class.constantize).new(parameters)
|
72
|
-
action.task = self
|
73
|
-
elsif task_actions.length > 1
|
74
|
-
action = Taskr::Actions::Multi.new
|
75
|
-
task_actions.each do |ta|
|
76
|
-
parameters = {}
|
77
|
-
ta.action_parameters.each{|p| parameters[p.name] = p.value}
|
78
|
-
|
79
|
-
a = (ta.action_class.kind_of?(Class) ? ta.action_class : ta.action_class.constantize).new(parameters)
|
80
|
-
a.task = self
|
81
|
-
|
82
|
-
action.actions << a
|
83
|
-
end
|
84
|
-
action.task = self
|
65
|
+
if task_actions.length > 0
|
66
|
+
action = prepare_action
|
85
67
|
else
|
86
68
|
$LOG.warn "Task #{name.inspect} has no actions and as a result will not be scheduled!"
|
87
69
|
return false
|
@@ -107,6 +89,36 @@ module Taskr::Models
|
|
107
89
|
return job_id
|
108
90
|
end
|
109
91
|
|
92
|
+
def prepare_action
|
93
|
+
if task_actions.length == 1
|
94
|
+
ta = task_actions.first
|
95
|
+
|
96
|
+
parameters = {}
|
97
|
+
ta.action_parameters.each{|p| parameters[p.name] = p.value}
|
98
|
+
|
99
|
+
action = (ta.action_class.kind_of?(Class) ? ta.action_class : ta.action_class.constantize).new(parameters)
|
100
|
+
action.task = self
|
101
|
+
action.task_action = ta
|
102
|
+
elsif task_actions.length > 1
|
103
|
+
action = Taskr::Actions::Multi.new
|
104
|
+
task_actions.each do |ta|
|
105
|
+
parameters = {}
|
106
|
+
ta.action_parameters.each{|p| parameters[p.name] = p.value}
|
107
|
+
|
108
|
+
a = (ta.action_class.kind_of?(Class) ? ta.action_class : ta.action_class.constantize).new(parameters)
|
109
|
+
a.task = self
|
110
|
+
a.task_action = ta
|
111
|
+
|
112
|
+
action.actions << a
|
113
|
+
end
|
114
|
+
action.task = self
|
115
|
+
else
|
116
|
+
raise "Task #{name.inspect} has no actions!"
|
117
|
+
end
|
118
|
+
|
119
|
+
action
|
120
|
+
end
|
121
|
+
|
110
122
|
def next_trigger_time
|
111
123
|
# TODO: need to figure out how to calulate trigger_time for these.. for now return :unknown
|
112
124
|
return :unknown unless schedule_method == 'at' || schedule_method == 'in'
|
@@ -122,10 +134,12 @@ module Taskr::Models
|
|
122
134
|
# treat 0000-00-00 00:00:00 as nil
|
123
135
|
Time.send(Base.default_timezone, *time_array) rescue DateTime.new(*time_array[0..5]) rescue nil
|
124
136
|
end
|
137
|
+
|
125
138
|
|
126
139
|
def to_s
|
127
|
-
"
|
140
|
+
"#{name.inspect}@#{schedule_when}"
|
128
141
|
end
|
142
|
+
|
129
143
|
end
|
130
144
|
|
131
145
|
class TaskAction < Base
|
@@ -136,6 +150,12 @@ module Taskr::Models
|
|
136
150
|
:foreign_key => :task_action_id,
|
137
151
|
:dependent => :destroy
|
138
152
|
|
153
|
+
has_many :log_entries
|
154
|
+
has_one :last_log_entry,
|
155
|
+
:class_name => 'LogEntry',
|
156
|
+
:foreign_key => :task_id,
|
157
|
+
:order => 'timestamp DESC'
|
158
|
+
|
139
159
|
validates_associated :action_parameters
|
140
160
|
|
141
161
|
def action_class=(class_name)
|
@@ -166,7 +186,7 @@ module Taskr::Models
|
|
166
186
|
end
|
167
187
|
|
168
188
|
def to_s
|
169
|
-
"
|
189
|
+
"#{self.class.name.demodulize}(#{task_action})"
|
170
190
|
end
|
171
191
|
end
|
172
192
|
|
@@ -188,7 +208,82 @@ module Taskr::Models
|
|
188
208
|
end
|
189
209
|
|
190
210
|
def to_s
|
191
|
-
"
|
211
|
+
"#{self.class.name.demodulize}(#{name}:#{value})"
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class LogEntry < Base
|
216
|
+
belongs_to :task
|
217
|
+
belongs_to :task_action
|
218
|
+
|
219
|
+
class << self
|
220
|
+
def log(level, action_or_task, data)
|
221
|
+
level = level.upcase
|
222
|
+
if action_or_task.kind_of? TaskAction
|
223
|
+
action = action_or_task
|
224
|
+
task = action.task
|
225
|
+
elsif action_or_task.kind_of? Task
|
226
|
+
action = nil
|
227
|
+
task = action_or_task
|
228
|
+
elsif action_or_task.kind_of? Integer
|
229
|
+
action = TaskAction.find(action_or_task)
|
230
|
+
task = action.task
|
231
|
+
elsif action_or_task.kind_of? Taskr::Actions::Base
|
232
|
+
action = action_or_task.task_action
|
233
|
+
task = action.task
|
234
|
+
else
|
235
|
+
raise ArgumentError, "#{action_or_task.inspect} is not a valid Task or TaskAction!"
|
236
|
+
end
|
237
|
+
|
238
|
+
threshold = Taskr::Conf[:task_log][:level].upcase if Taskr::Conf[:task_log] && Taskr::Conf[:task_log][:level]
|
239
|
+
|
240
|
+
if threshold.blank? ||
|
241
|
+
['DEBUG', 'INFO', 'WARN', 'ERROR'].index(threshold) <= ['DEBUG', 'INFO', 'WARN', 'ERROR'].index(level)
|
242
|
+
LogEntry.create(
|
243
|
+
:level => level,
|
244
|
+
:timestamp => Time.now,
|
245
|
+
:task => task,
|
246
|
+
:task_action => action,
|
247
|
+
:data => data
|
248
|
+
)
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
# Produces a Logger-like class that will create log entries for the given
|
253
|
+
# TaskAction. The returned object exploses behaviour much like a standard
|
254
|
+
# Ruby Logger, so that it can be used in place of a Logger when necessary.
|
255
|
+
def logger_for_action(action)
|
256
|
+
ActionLogger.new(action)
|
257
|
+
end
|
258
|
+
|
259
|
+
['debug', 'info', 'warn', 'error'].each do |level|
|
260
|
+
define_method(level) do |action, data|
|
261
|
+
log(level, action, data)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
# Exposes a Logger-like interface for logging entries for some particular
|
267
|
+
# TaskAction.
|
268
|
+
class ActionLogger
|
269
|
+
def initialize(action)
|
270
|
+
@action = action
|
271
|
+
end
|
272
|
+
|
273
|
+
def method_missing(method, data)
|
274
|
+
LogEntry.send(method, @action, "#{"#{@progname}: " unless @progname.blank?}#{data}")
|
275
|
+
end
|
276
|
+
|
277
|
+
def respond_to?(method)
|
278
|
+
[:debug, :info, :warn, :error].include?(method)
|
279
|
+
end
|
280
|
+
|
281
|
+
def progname
|
282
|
+
action.task.name
|
283
|
+
end
|
284
|
+
|
285
|
+
def progname=(p)
|
286
|
+
end
|
192
287
|
end
|
193
288
|
end
|
194
289
|
|
@@ -235,4 +330,36 @@ module Taskr::Models
|
|
235
330
|
drop_table :taskr_tasks
|
236
331
|
end
|
237
332
|
end
|
333
|
+
|
334
|
+
class AddLoggingTables < V 0.3
|
335
|
+
def self.up
|
336
|
+
$LOG.info("Creating logging tables")
|
337
|
+
|
338
|
+
create_table :taskr_log_entries, :force => true do |t|
|
339
|
+
t.column :task_id, :integer
|
340
|
+
t.column :task_action_id, :integer
|
341
|
+
|
342
|
+
t.column :timestamp, :timestamp, :null => false
|
343
|
+
t.column :level, :string, :null => false
|
344
|
+
t.column :data, :text
|
345
|
+
end
|
346
|
+
|
347
|
+
add_index :taskr_log_entries, :task_id
|
348
|
+
add_index :taskr_log_entries, :task_action_id
|
349
|
+
end
|
350
|
+
|
351
|
+
def self.down
|
352
|
+
drop_table :taskr_log_entries
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
class AddMemoToTasks < V 0.3001
|
357
|
+
def self.up
|
358
|
+
add_column :taskr_tasks, :memo, :text
|
359
|
+
end
|
360
|
+
|
361
|
+
def self.down
|
362
|
+
remove_column :taskr_tasks, :memo
|
363
|
+
end
|
364
|
+
end
|
238
365
|
end
|
data/lib/taskr/version.rb
CHANGED
data/lib/taskr/views.rb
CHANGED
@@ -30,13 +30,6 @@ module Taskr::Views
|
|
30
30
|
def view_task
|
31
31
|
@task.to_xml(:root => 'task')#, :include => [:task_actions])
|
32
32
|
end
|
33
|
-
|
34
|
-
def create_task_result
|
35
|
-
taskr_response_xml(@task.valid? ? 'success' : 'failure') do
|
36
|
-
text @task.to_xml
|
37
|
-
text @task.errors.to_xml unless @task.valid?
|
38
|
-
end
|
39
|
-
end
|
40
33
|
end
|
41
34
|
|
42
35
|
module HTML
|
@@ -128,6 +121,12 @@ module Taskr::Views
|
|
128
121
|
input :type => 'text', :name => 'schedule_when', :size => 30
|
129
122
|
end
|
130
123
|
|
124
|
+
p do
|
125
|
+
label 'description/memo'
|
126
|
+
br
|
127
|
+
textarea(:name => 'memo', :cols => '60', :rows => '4'){""}
|
128
|
+
end
|
129
|
+
|
131
130
|
action_form
|
132
131
|
|
133
132
|
p do
|
@@ -153,8 +152,11 @@ module Taskr::Views
|
|
153
152
|
|
154
153
|
def view_task
|
155
154
|
html_scaffold do
|
156
|
-
form(:method => 'delete', :style => 'display: inline') do
|
157
|
-
button(:type => 'submit', :value => 'delete') {"Delete"}
|
155
|
+
form(:method => 'delete', :style => 'display: inline', :action => R(@task)) do
|
156
|
+
button(:type => 'submit', :value => 'delete', :onclick => 'return confirm("Are you sure you want to unschedule and delete this task?")') {"Delete"}
|
157
|
+
end
|
158
|
+
form(:method => 'put', :style => 'display: inline', :action => R(@task, 'run')) do
|
159
|
+
button(:type => 'submit', :value => 'run') {"Run Now!"}
|
158
160
|
end
|
159
161
|
br
|
160
162
|
a(:href => R(Tasks, :list)) {"Back to Task List"}
|
@@ -169,6 +171,10 @@ module Taskr::Views
|
|
169
171
|
th "Schedule:"
|
170
172
|
td "#{@task.schedule_method} #{@task.schedule_when}"
|
171
173
|
end
|
174
|
+
tr do
|
175
|
+
th "Description/Memo:"
|
176
|
+
td "#{@task.memo}"
|
177
|
+
end
|
172
178
|
tr do
|
173
179
|
th "Job ID:"
|
174
180
|
td @task.scheduler_job_id
|
@@ -215,6 +221,8 @@ module Taskr::Views
|
|
215
221
|
td @task.created_on
|
216
222
|
end
|
217
223
|
end
|
224
|
+
|
225
|
+
iframe(:src => R(LogEntries, :list, :task_id => @task.id), :style => 'width: 100%; margin-top: 20px;')
|
218
226
|
end
|
219
227
|
end
|
220
228
|
|
@@ -276,6 +284,33 @@ module Taskr::Views
|
|
276
284
|
|
277
285
|
end
|
278
286
|
|
287
|
+
def log_entries_list
|
288
|
+
h2 "Log"
|
289
|
+
table do
|
290
|
+
@log_entries.each do |entry|
|
291
|
+
case entry.level.downcase.intern
|
292
|
+
when :error
|
293
|
+
bg_color = '#faa'
|
294
|
+
when :warn
|
295
|
+
bg_color = '#ffa'
|
296
|
+
when :info
|
297
|
+
bg_color = '#aaf'
|
298
|
+
when :debug
|
299
|
+
bg_color = '#eee'
|
300
|
+
else
|
301
|
+
bg_color = '#fff; '+entry.level.inspect
|
302
|
+
end
|
303
|
+
tr do
|
304
|
+
td(:style => "vertical-align: top; font-size: 9pt; white-space: nowrap; background: #{bg_color}") do
|
305
|
+
entry.timestamp
|
306
|
+
end
|
307
|
+
td(:style => "vertical-align: top; font-size: 9pt; background-color: #{bg_color}; font-size: 9pt; font-family: monospace") do
|
308
|
+
entry.data.gsub(/<\/?(html|body)>/, '').gsub(/\n/, "<br />")
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
279
314
|
end
|
280
315
|
|
281
316
|
default_format :HTML
|
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: taskr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Matt Zukowski
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-06-19 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
requirements:
|
20
20
|
- - ~>
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 0.6.
|
22
|
+
version: 0.6.4
|
23
23
|
version:
|
24
24
|
- !ruby/object:Gem::Dependency
|
25
25
|
name: reststop
|
@@ -28,19 +28,28 @@ dependencies:
|
|
28
28
|
requirements:
|
29
29
|
- - ~>
|
30
30
|
- !ruby/object:Gem::Version
|
31
|
-
version: 0.
|
31
|
+
version: 0.3.0
|
32
32
|
version:
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: restr
|
35
35
|
version_requirement:
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.4.0
|
41
|
+
version:
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: rufus-scheduler
|
44
|
+
version_requirement:
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ~>
|
39
48
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
49
|
+
version: 1.0.7
|
41
50
|
version:
|
42
|
-
description:
|
43
|
-
email:
|
51
|
+
description: cron-like scheduler service with a RESTful interface
|
52
|
+
email: matt at roughest dot net
|
44
53
|
executables:
|
45
54
|
- taskr
|
46
55
|
- taskr-ctl
|
@@ -114,6 +123,6 @@ rubyforge_project: taskr
|
|
114
123
|
rubygems_version: 1.0.1
|
115
124
|
signing_key:
|
116
125
|
specification_version: 2
|
117
|
-
summary:
|
126
|
+
summary: cron-like scheduler service with a RESTful interface
|
118
127
|
test_files:
|
119
128
|
- test/taskr_test.rb
|