taskr 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ body {
2
+ font-size: 9pt;
3
+ font-family: Verdana;
4
+ }
5
+
6
+ table {
7
+ border-collapse: collapse;
8
+ }
9
+
10
+ th, td {
11
+ font-size: 9pt;
12
+ padding-left: 2pt;
13
+ padding-right: 6pt;
14
+ vertical-align: top;
15
+ }
16
+
17
+ th {
18
+ text-align: right;
19
+ }
20
+
21
+ thead th {
22
+ background: #dde;
23
+ text-align: left;
24
+ font-size: 8pt;
25
+ }
26
+
27
+ td {
28
+
29
+ }
30
+
31
+ label {
32
+ font-weight: bold;
33
+ }
34
+
35
+ tr.expired td {
36
+ color: #aaa;
37
+ }
38
+
39
+ tr.expired td.job-id {
40
+ text-decoration: line-through;
41
+ }
42
+
43
+ tr.error td {
44
+ background-color: #faa;
45
+ }
@@ -0,0 +1,83 @@
1
+ # This file is part of Taskr.
2
+ #
3
+ # Taskr is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # Taskr is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with Taskr. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ $: << File.dirname(File.expand_path(__FILE__))
17
+ require 'taskr/environment'
18
+
19
+ Camping.goes :Taskr
20
+ Taskr.picnic!
21
+
22
+ require 'taskr/controllers'
23
+
24
+ module Taskr
25
+ @@scheduler = nil
26
+ def self.scheduler=(scheduler)
27
+ @@scheduler = scheduler
28
+ end
29
+ def self.scheduler
30
+ @@scheduler
31
+ end
32
+ end
33
+
34
+ require 'taskr/actions'
35
+ require 'taskr/models'
36
+ require 'taskr/helpers'
37
+ require 'taskr/views'
38
+ require 'taskr/controllers'
39
+
40
+ module Taskr
41
+ include Taskr::Models
42
+ end
43
+
44
+ include Taskr::Models
45
+
46
+ def Taskr.create
47
+ $LOG.info "Initializing Taskr..."
48
+ Taskr::Models::Base.establish_connection(Taskr::Conf.database)
49
+ Taskr::Models.create_schema
50
+
51
+ if self::Conf[:external_actions]
52
+ if self::Conf[:external_actions].kind_of? Array
53
+ external_actions = self::Conf[:external_actions]
54
+ else
55
+ external_actions = [self::Conf[:external_actions]]
56
+ end
57
+ external_actions.each do |f|
58
+ $LOG.info "Loading additional action definitions from #{self::Conf[:external_actions]}..."
59
+ require f
60
+ end
61
+ end
62
+ end
63
+
64
+ def Taskr.prestart
65
+ $LOG.info "Starting OpenWFE Scheduler..."
66
+
67
+ Taskr.scheduler = OpenWFE::Scheduler.new
68
+ Taskr.scheduler.start
69
+
70
+ $LOG.debug "Scheduler is: #{Taskr.scheduler.inspect}"
71
+
72
+ tasks = Taskr::Models::Task.find(:all)
73
+
74
+ $LOG.info "Scheduling #{tasks.length} persisted tasks..."
75
+
76
+ tasks.each do |t|
77
+ t.schedule! Taskr.scheduler
78
+ end
79
+
80
+ Taskr.scheduler.instance_variable_get(:@scheduler_thread).run
81
+ end
82
+
83
+ Taskr.start_picnic
@@ -0,0 +1,230 @@
1
+ # This file is part of Taskr.
2
+ #
3
+ # Taskr is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # Taskr is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with Taskr. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ require 'openwfe/util/scheduler'
17
+
18
+ #require 'active_resource'
19
+ require 'restr'
20
+
21
+
22
+ unless $LOG
23
+ $LOG = Logger.new(STDERR)
24
+ $LOG.level = Logger::ERROR
25
+ end
26
+
27
+ module Taskr
28
+ module Actions
29
+
30
+ def self.list
31
+ actions = []
32
+ Taskr::Actions.constants.each do |m|
33
+ a = Taskr::Actions.const_get(m)
34
+ actions << a if a < Taskr::Actions::Base
35
+ end
36
+ return actions
37
+ end
38
+
39
+ # The base class for all Actions.
40
+ # Extend this to define your own custom Action.
41
+ class Base
42
+ include OpenWFE::Schedulable
43
+
44
+ class_inheritable_accessor :parameters
45
+ class_inheritable_accessor :description
46
+
47
+ attr_accessor :parameters
48
+ attr_accessor :task
49
+
50
+ def initialize(parameters)
51
+ self.parameters = HashWithIndifferentAccess.new(parameters)
52
+ end
53
+
54
+ def execute
55
+ raise NotImplementedError, "Implement me!"
56
+ end
57
+
58
+ def trigger(trigger_args = {})
59
+ begin
60
+ $LOG.info("Executing task #{self.task.name}")
61
+ execute
62
+ task.update_attribute(:last_triggered, Time.now)
63
+ task.update_attribute(:last_triggered_error, nil)
64
+ rescue => e
65
+ $LOG.error(e)
66
+ $LOG.debug(e.backtrace.to_s)
67
+ task.update_attribute(:last_triggered, Time.now)
68
+ task.update_attribute(:last_triggered_error, {:type => e.class.to_s, :message => e.message})
69
+ raise e
70
+ end
71
+ end
72
+ end
73
+
74
+ # Do not extend this class. It is used internally to schedule multiple
75
+ # actions per task.
76
+ #
77
+ # If you want to define your own custom Action, extend Taskr::Actions::Base
78
+ class Multi
79
+ include OpenWFE::Schedulable
80
+
81
+ attr_accessor :actions
82
+ attr_accessor :task
83
+
84
+ def initialize
85
+ self.actions = []
86
+ end
87
+
88
+ def trigger(trigger_args = {})
89
+ begin
90
+ $LOG.info("Executing task #{self.name}")
91
+ actions.each do |a|
92
+ a.execute
93
+ end
94
+ # TODO: maybe we should store last_triggered time on a per-action basis?
95
+ task.update_attribute(:last_triggered, Time.now)
96
+ rescue => e
97
+ $LOG.error(e)
98
+ $LOG.debug("#{e.stacktrace}")
99
+ task.update_attribute(:last_triggered, Time.now)
100
+ task.update_attribute(:last_triggered_error, e)
101
+ raise e
102
+ end
103
+ end
104
+ end
105
+
106
+ class Shell < Base
107
+ self.parameters = ['command', 'as_user']
108
+ self.description = "Execute a shell command (be careful!)"
109
+
110
+ def execute
111
+ if parameters.kind_of? Hash
112
+ user = parameters['as_user']
113
+ cmd = parameters['command']
114
+ else
115
+ user = nil
116
+ end
117
+
118
+ if user
119
+ `sudo -u #{user} #{cmd}`
120
+ else
121
+ `#{cmd}`
122
+ end
123
+
124
+ unless $?.success?
125
+ raise "Shell command failed (#{$?}): #{cmd}"
126
+ end
127
+ end
128
+ end
129
+
130
+ class Ruby < Base
131
+ self.parameters = ['code']
132
+ self.description = "Execute some Ruby code."
133
+
134
+ def execute
135
+ code = parameters['code']
136
+ eval code
137
+ end
138
+ end
139
+
140
+ # class ActiveResource < Base
141
+ # self.parameters = ['site', 'resource', 'action', 'parameters']
142
+ # self.description = "Perform a REST call on a remote service using ActiveResource."
143
+ #
144
+ # def execute
145
+ # $LOG.debug self
146
+ # ::ActiveResource::Base.logger = $LOG
147
+ # ::ActiveResource::Base.logger.progname = (task ? task.to_s : self)
148
+ #
149
+ # eval %{
150
+ # class Proxy < ::ActiveResource::Base
151
+ # self.site = "#{parameters['site']}"
152
+ # self.collection_name = "#{parameters['resource'].pluralize}"
153
+ # end
154
+ # }
155
+ #
156
+ # begin
157
+ # case parameters['action']
158
+ # when 'create'
159
+ # obj = Proxy.new(parameters['parameters'])
160
+ # obj.save
161
+ # when 'update', "'update' action is not implemented"
162
+ # raise NotImplementedError
163
+ # when 'delete'
164
+ # Proxy.delete(parameters['parameters'])
165
+ # when 'find'
166
+ # raise NotImplementedError, "'find' action is not implemented"
167
+ # else
168
+ # raise ArgumentError, "unknown action #{parameters['action'].inspect}"
169
+ # end
170
+ # rescue ::ActiveResource::ServerError => e
171
+ # $LOG.error #{self} ERROR: #{e.methods.inspect}"
172
+ # raise e
173
+ # end
174
+ # end
175
+ # end
176
+ #
177
+ # class Howlr < ActiveResource
178
+ # self.parameters = ['site', 'from', 'recipients', 'subject', 'body']
179
+ # self.description = "Send a message through a Howlr service."
180
+ #
181
+ # def execute
182
+ # parameters['action'] = 'create'
183
+ # parameters['resource'] = 'message'
184
+ # parameters['parameters'] = {
185
+ # 'from' => parameters['from'],
186
+ # 'recipients' => parameters['recipients'],
187
+ # 'subject' => parameters['subject'],
188
+ # 'body' => parameters['body']
189
+ # }
190
+ #
191
+ # super
192
+ # end
193
+ # end
194
+
195
+ class Rest < Base
196
+ self.parameters = ['method', 'url', 'params', 'username', 'password']
197
+ self.description = "Perform a REST call on a remote service."
198
+
199
+ def execute
200
+ auth = nil
201
+ if parameters['username']
202
+ auth = {}
203
+ auth['username'] = parameters['username'] if parameters['username']
204
+ auth['password'] = parameters['password'] if parameters['password']
205
+ end
206
+
207
+ Restr.logger = $LOG
208
+ Restr.do(parameters['method'], parameters['url'], parameters['params'], auth)
209
+ end
210
+ end
211
+
212
+ class Howlr < Rest
213
+ self.parameters = ['url', 'from', 'recipients', 'subject', 'body', 'username', 'password']
214
+ self.description = "Send a message through a Howlr service."
215
+
216
+ def execute
217
+ parameters['method'] = 'post'
218
+ parameters['params'] = {
219
+ 'from' => parameters['from'],
220
+ 'recipients' => parameters['recipients'],
221
+ 'subject' => parameters['subject'],
222
+ 'body' => parameters['body'],
223
+ 'format' => 'XML'
224
+ }
225
+
226
+ super
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,164 @@
1
+ # This file is part of Taskr.
2
+ #
3
+ # Taskr is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # Taskr is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with Taskr. If not, see <http://www.gnu.org/licenses/>.
15
+
16
+ module Taskr::Controllers
17
+
18
+ class ActionTypes < REST 'action_types'
19
+ def list
20
+ @actions = Taskr::Actions.list
21
+
22
+ render :action_list
23
+ end
24
+ end
25
+
26
+ class Actions < REST 'actions'
27
+ def parameters_form(id)
28
+ @num = @input[:num] || 0
29
+ @action = Taskr::Actions.list.find {|a| a.to_s =~ Regexp.new("#{id}$")}
30
+ if @action
31
+ render :action_parameters_form
32
+ else
33
+ @status = 404
34
+ "Action #{id.inspect} not defined"
35
+ end
36
+ end
37
+
38
+ def new
39
+ @num = @input[:num] || 0
40
+ @actions = Taskr::Actions.list
41
+ render :action_form
42
+ end
43
+ end
44
+
45
+ class Tasks < REST 'tasks'
46
+ include Taskr::Models
47
+
48
+ def list
49
+ @tasks = Task.find(:all, :include => [:task_actions])
50
+
51
+ render :tasks_list
52
+ end
53
+
54
+ def new
55
+ @actions = Taskr::Actions.list
56
+
57
+ render :new_task
58
+ end
59
+
60
+ def read(id)
61
+ @task = Task.find(id, :include => [:task_actions])
62
+
63
+ render :view_task
64
+ end
65
+
66
+ def create
67
+ begin
68
+ puts @input.class
69
+ puts @input.to_xml if @input.kind_of?(XmlSimple)
70
+ puts @input.inspect
71
+
72
+ # the "0" is for compatibility with PHP's Zend_Rest_Client
73
+ task_data = @input[:task] || @input["0"] || @input
74
+
75
+ name = task_data[:name]
76
+ created_by = @env['REMOTE_HOST']
77
+ schedule_method = task_data[:schedule_method]
78
+ schedule_when = task_data[:schedule_when]
79
+
80
+ @task = Task.new(
81
+ :name => name,
82
+ :created_by => created_by,
83
+ :schedule_method => schedule_method,
84
+ :schedule_when => schedule_when
85
+ )
86
+
87
+ # some gymnastics here to provide compatibility for the way various
88
+ # REST client libraries submit data
89
+ actions_data = task_data[:actions] || task_data[:action]
90
+
91
+ raise ArgumentError, "Missing action(s) parameter." if actions_data.blank?
92
+
93
+ if actions_data.kind_of?(Array)
94
+ actions = actions_data
95
+ elsif actions_data["0"]
96
+ actions = []
97
+ actions_data.each do |i,a|
98
+ actions << a
99
+ end
100
+ else
101
+ actions = actions_data[:action]
102
+ end
103
+
104
+ actions = [actions] unless actions.kind_of? Array
105
+ puts actions.inspect
106
+
107
+ i = 0
108
+ actions.each do |a|
109
+ puts a.inspect
110
+ action_class_name = a[:action_class_name]
111
+ action_class_name = "Taskr::Actions::#{action_class_name}" unless action_class_name =~ /^Taskr::Actions::/
112
+
113
+ begin
114
+ action_class = action_class_name.constantize
115
+ unless action_class.include? OpenWFE::Schedulable
116
+ raise ArgumentError,
117
+ "#{a[:action_class_name].inspect} cannot be used as an action because it does not include the OpenWFE::Schedulable module."
118
+ end
119
+ rescue NameError
120
+ raise ArgumentError,
121
+ "#{a[:action_class_name].inspect} is not defined (i.e. there is no such action class)."
122
+ end
123
+
124
+ action = TaskAction.new(:order => a[:order] || i, :action_class_name => action_class_name)
125
+
126
+ action_class.parameters.each do |p|
127
+ action.action_parameters << TaskActionParameter.new(:name => p, :value => a[p])
128
+ end
129
+
130
+ @task.task_actions << action
131
+ i += 1
132
+ end
133
+
134
+
135
+ unless @task.valid?
136
+ @status = 500
137
+ return render(:new_task)
138
+ end
139
+
140
+
141
+ @task.schedule! Taskr.scheduler
142
+
143
+ if @task.save
144
+ location = "/tasks/#{@task.id}?format=#{@format}"
145
+ $LOG.debug "#{@task} saved successfuly. Setting Location header to #{location.inspect}."
146
+ @headers['Location'] = location
147
+ end
148
+
149
+ return render(:view_task)
150
+ rescue => e
151
+ puts e.inspect
152
+ puts e.backtrace
153
+ raise e
154
+ end
155
+ end
156
+
157
+ def destroy(id)
158
+ @task = Task.find(id)
159
+ Taskr.scheduler.unschedule(@task.scheduler_job_id) if @task.scheduler_job_id
160
+ @task.destroy
161
+ return redirect('/tasks')
162
+ end
163
+ end
164
+ end