taskr 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG.txt +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
|