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.
@@ -1,27 +1 @@
1
- === 0.2.1 :: 2008-02-28
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
@@ -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 = "URBACON\mzukowski" # can also be an array of Authors
15
- EMAIL = "your contact email for bug fixes and info"
16
- DESCRIPTION = "description of gem"
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.1'],
58
- ['reststop', '~>0.2.0'],
59
- 'openwferu-scheduler'
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
@@ -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 methods
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 proxy
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 additional
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 supported
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 basic HTTP authentication. Uncomment this if you want Taskr to demand
76
- # a username and password from clients. Note that this isn't very secure unless
77
- # you run Taskr over HTTPS (see the webrick SSL example above).
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
- # This log is where you'll want to look in case of problems.
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
@@ -76,9 +76,9 @@ def Taskr.create
76
76
  end
77
77
 
78
78
  def Taskr.prestart
79
- $LOG.info "Starting OpenWFE Scheduler..."
79
+ $LOG.info "Starting Rufus Scheduler..."
80
80
 
81
- Taskr.scheduler = OpenWFE::Scheduler.new
81
+ Taskr.scheduler = Rufus::Scheduler.new
82
82
  Taskr.scheduler.start
83
83
 
84
84
  $LOG.debug "Scheduler is: #{Taskr.scheduler.inspect}"
@@ -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 'openwfe/util/scheduler'
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 OpenWFE::Schedulable
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 OpenWFE::Schedulable
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.stacktrace}")
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
- if user
122
- `sudo -u #{user} #{cmd}`
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
- raise "Shell command failed (#{$?}): #{cmd}"
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 code
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
- Restr.logger = $LOG
211
- Restr.do(parameters['method'], parameters['url'], parameters['params'], auth)
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 = $LOG
310
+ Restr.logger = LogEntry.logger_for_action(task_action)
250
311
  Restr.post(parameters['url'], data)
251
312
  end
252
313
  end
@@ -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? OpenWFE::Schedulable
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 OpenWFE::Schedulable module."
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
- action.action_parameters << TaskActionParameter.new(:name => p, :value => a[p])
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
- return redirect(R(Tasks, :list))
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
@@ -44,5 +44,5 @@ require 'camping/db'
44
44
 
45
45
  require 'reststop'
46
46
 
47
- gem 'openwferu-scheduler', '~> 0.9.16'
48
- require 'openwfe/util/scheduler'
47
+ gem 'rufus-scheduler', '~> 1.0.7'
48
+ require 'rufus/scheduler'
@@ -17,7 +17,7 @@ require 'camping/db'
17
17
  require 'openwfe/util/scheduler'
18
18
  require 'date'
19
19
 
20
- class OpenWFE::Scheduler
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 == 1
66
- ta = task_actions.first
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
- "#<#{self.class}:#{self.id}>"
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
- "#<#{self.class}:#{self.id}>"
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
- "#<#{self.class}:#{self.id}>"
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
@@ -1,8 +1,8 @@
1
1
  module Taskr #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
- MINOR = 2
5
- TINY = 1
4
+ MINOR = 3
5
+ TINY = 0
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
- - URBACONmzukowski
7
+ - Matt Zukowski
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-02-28 00:00:00 -05:00
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.1
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.2.0
31
+ version: 0.3.0
32
32
  version:
33
33
  - !ruby/object:Gem::Dependency
34
- name: openwferu-scheduler
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: "0"
49
+ version: 1.0.7
41
50
  version:
42
- description: description of gem
43
- email: your contact email for bug fixes and info
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: description of gem
126
+ summary: cron-like scheduler service with a RESTful interface
118
127
  test_files:
119
128
  - test/taskr_test.rb