taskr 0.2.1 → 0.3.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/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
|