taskr 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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