taskr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.txt +0 -0
- data/GPLv3-LICENSE.txt +674 -0
- data/History.txt +0 -0
- data/Manifest.txt +25 -0
- data/README.txt +14 -0
- data/Rakefile +58 -0
- data/bin/taskr +35 -0
- data/bin/taskr-ctl +32 -0
- data/config.example.yml +100 -0
- data/examples/active_resource_client_example.rb +36 -0
- data/examples/php_client_example.php +85 -0
- data/lib/public/prototype.js +3271 -0
- data/lib/public/taskr.css +45 -0
- data/lib/taskr.rb +83 -0
- data/lib/taskr/actions.rb +230 -0
- data/lib/taskr/controllers.rb +164 -0
- data/lib/taskr/environment.rb +48 -0
- data/lib/taskr/helpers.rb +79 -0
- data/lib/taskr/models.rb +238 -0
- data/lib/taskr/version.rb +9 -0
- data/lib/taskr/views.rb +276 -0
- data/setup.rb +1585 -0
- data/test.rb +3 -0
- data/test/taskr_test.rb +11 -0
- data/test/test_helper.rb +2 -0
- metadata +102 -0
@@ -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
|
+
}
|
data/lib/taskr.rb
ADDED
@@ -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
|