foreman-tasks 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/app/controllers/foreman_tasks/api/tasks_controller.rb +8 -0
- data/app/lib/actions/helpers/humanizer.rb +164 -34
- data/app/models/foreman_tasks/concerns/action_triggering.rb +18 -5
- data/app/models/foreman_tasks/lock.rb +1 -1
- data/app/models/foreman_tasks/task.rb +11 -0
- data/app/models/foreman_tasks/task/dynflow_task.rb +18 -6
- data/db/migrate/20140324104010_remove_foreman_tasks_progress.rb +5 -0
- data/lib/foreman_tasks/dynflow/configuration.rb +21 -9
- data/lib/foreman_tasks/dynflow/persistence.rb +3 -1
- data/lib/foreman_tasks/engine.rb +9 -2
- data/lib/foreman_tasks/version.rb +1 -1
- data/lib/tasks/gettext.rake +17 -12
- metadata +5 -4
@@ -14,6 +14,12 @@ module ForemanTasks
|
|
14
14
|
module Api
|
15
15
|
class TasksController < ::Api::V2::BaseController
|
16
16
|
|
17
|
+
resource_description do
|
18
|
+
resource_id 'foreman_tasks'
|
19
|
+
api_version 'v2'
|
20
|
+
api_base_url "/foreman_tasks/api"
|
21
|
+
end
|
22
|
+
|
17
23
|
# Foreman right now doesn't have mechanism to
|
18
24
|
# cause general BadRequest handling, resuing the Apipie::ParamError
|
19
25
|
# for now http://projects.theforeman.org/issues/3957
|
@@ -22,6 +28,8 @@ module ForemanTasks
|
|
22
28
|
|
23
29
|
before_filter :find_task, :only => [:show]
|
24
30
|
|
31
|
+
api :GET, "/tasks/:id", "Show task details"
|
32
|
+
param :id, :identifier, desc: "UUID of the task"
|
25
33
|
def show
|
26
34
|
end
|
27
35
|
|
@@ -3,18 +3,6 @@ module Actions
|
|
3
3
|
|
4
4
|
class Humanizer
|
5
5
|
|
6
|
-
PARTS_ORDER = [:user,
|
7
|
-
:repository,
|
8
|
-
:product,
|
9
|
-
:system,
|
10
|
-
:organization]
|
11
|
-
# Just to get the trings into pot file
|
12
|
-
PARTS_TRANSLATIONS = [N_('user'),
|
13
|
-
N_('repository'),
|
14
|
-
N_('product'),
|
15
|
-
N_('system'),
|
16
|
-
N_('organization')]
|
17
|
-
|
18
6
|
def initialize(action)
|
19
7
|
@action = action
|
20
8
|
@input = action.respond_to?(:task_input) ? action.task_input : action.input
|
@@ -23,12 +11,34 @@ module Actions
|
|
23
11
|
@output ||= {}
|
24
12
|
end
|
25
13
|
|
14
|
+
def self.resource_classes_order
|
15
|
+
@resource_classes_order ||= []
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.resource(name)
|
19
|
+
resource_classes_order.map(&:new).find { |resource| resource.name == name }
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.default_parts
|
23
|
+
self.resource_classes_order.map { |klass| klass.new.name }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Registers the resource class to the humanizer. Usually, this
|
27
|
+
# happens when the resource class is defined. The order of resources
|
28
|
+
# in the humanized input is determined by the registration order.
|
29
|
+
# The `register_resource` can be run more times for the same class,
|
30
|
+
# effectively moving the resource to the end of the humanized form.
|
31
|
+
def self.register_resource(resource_class)
|
32
|
+
self.resource_classes_order.delete_if { |klass| klass.name == resource_class.name }
|
33
|
+
self.resource_classes_order << resource_class
|
34
|
+
end
|
35
|
+
|
26
36
|
def input(*parts)
|
27
37
|
if parts.empty?
|
28
|
-
parts =
|
38
|
+
parts = self.class.default_parts
|
29
39
|
end
|
30
40
|
included_parts(parts, @input).map do |part|
|
31
|
-
[part, humanize_resource(part, @input
|
41
|
+
[part, humanize_resource(part, @input)]
|
32
42
|
end
|
33
43
|
end
|
34
44
|
|
@@ -36,30 +46,150 @@ module Actions
|
|
36
46
|
parts.select { |part| data.has_key?(part) }
|
37
47
|
end
|
38
48
|
|
39
|
-
def humanize_resource(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
49
|
+
def humanize_resource(name, data)
|
50
|
+
if resource = self.class.resource(name)
|
51
|
+
{ text: "#{resource.humanized_name} '#{resource.humanized_value(data)}'",
|
52
|
+
link: resource.link(data) }
|
53
|
+
end
|
44
54
|
end
|
45
55
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
56
|
+
class Resource
|
57
|
+
def name
|
58
|
+
raise NotImplementedError
|
59
|
+
end
|
60
|
+
|
61
|
+
def humanized_name
|
62
|
+
name
|
63
|
+
end
|
64
|
+
|
65
|
+
def link(data)
|
66
|
+
end
|
67
|
+
|
68
|
+
def humanized_value(data)
|
69
|
+
fetch_data(data, name, :name) ||
|
70
|
+
fetch_data(data, name, :label) ||
|
71
|
+
fetch_data(data, name, :id)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.inherited(klass)
|
75
|
+
Humanizer.register_resource(klass)
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def fetch_data(data, *subkeys)
|
81
|
+
if subkeys.empty?
|
82
|
+
return data
|
83
|
+
else
|
84
|
+
head, *tail = subkeys
|
85
|
+
if data.is_a?(Hash) && data.has_key?(head)
|
86
|
+
return fetch_data(data[head], *tail)
|
87
|
+
else
|
88
|
+
return nil
|
89
|
+
end
|
55
90
|
end
|
56
|
-
|
57
|
-
|
58
|
-
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class UserResource < Resource
|
95
|
+
def name
|
96
|
+
:user
|
97
|
+
end
|
98
|
+
|
99
|
+
def humanized_name
|
100
|
+
_('user')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# TODO: remove after getting the definitions into Katello
|
105
|
+
class RepositoryResource < Resource
|
106
|
+
def name
|
107
|
+
:repository
|
108
|
+
end
|
109
|
+
|
110
|
+
def humanized_name
|
111
|
+
_('repository')
|
112
|
+
end
|
113
|
+
|
114
|
+
def link(data)
|
115
|
+
product_id = fetch_data(data, :product, :id)
|
116
|
+
repo_id = fetch_data(data, :repo, :id)
|
117
|
+
if product_id && repo_id
|
118
|
+
"#/products/#{product_id}/repositories/#{repo_id}"
|
59
119
|
end
|
60
|
-
|
61
|
-
|
62
|
-
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class ContentViewVersionResource < Resource
|
124
|
+
def name
|
125
|
+
:content_view_version
|
126
|
+
end
|
127
|
+
|
128
|
+
def humanized_name
|
129
|
+
_('content view version')
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class ContentViewResource < Resource
|
134
|
+
def name
|
135
|
+
:content_view
|
136
|
+
end
|
137
|
+
|
138
|
+
def humanized_name
|
139
|
+
_('content view')
|
140
|
+
end
|
141
|
+
|
142
|
+
def link(data)
|
143
|
+
if content_view_id = fetch_data(data, :content_view, :id)
|
144
|
+
"#/content_views/#{content_view_id}/versions"
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
class ProductResource < Resource
|
150
|
+
def name
|
151
|
+
:product
|
152
|
+
end
|
153
|
+
|
154
|
+
def humanized_name
|
155
|
+
_('product')
|
156
|
+
end
|
157
|
+
|
158
|
+
def link(data)
|
159
|
+
if product_id = fetch_data(data, :product, :id)
|
160
|
+
"#/products/#{product_id}/info"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
class SystemResource < Resource
|
166
|
+
def name
|
167
|
+
:system
|
168
|
+
end
|
169
|
+
|
170
|
+
def humanized_name
|
171
|
+
_('system')
|
172
|
+
end
|
173
|
+
|
174
|
+
def link(data)
|
175
|
+
if system_uuid = fetch_data(data, :system, :uuid)
|
176
|
+
"#/systems/#{system_uuid}/info"
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
class OrganizationResource < Resource
|
182
|
+
def name
|
183
|
+
:organization
|
184
|
+
end
|
185
|
+
|
186
|
+
def humanized_name
|
187
|
+
_('organization')
|
188
|
+
end
|
189
|
+
|
190
|
+
def link(data)
|
191
|
+
if org_id = fetch_data(data, :organization, :id)
|
192
|
+
"/organizations/#{org_id}/edit"
|
63
193
|
end
|
64
194
|
end
|
65
195
|
end
|
@@ -22,18 +22,26 @@ module ForemanTasks
|
|
22
22
|
def destroy_action
|
23
23
|
end
|
24
24
|
|
25
|
-
def save(*)
|
26
|
-
dynflow_task_wrap(:save) { super }
|
25
|
+
def save(*args)
|
26
|
+
dynflow_task_wrap(:save) { super(*args) }
|
27
27
|
end
|
28
28
|
|
29
|
-
def save!(*)
|
30
|
-
dynflow_task_wrap(:save) { super }
|
29
|
+
def save!(*args)
|
30
|
+
dynflow_task_wrap(:save) { super(*args) }
|
31
31
|
end
|
32
32
|
|
33
33
|
def destroy
|
34
34
|
dynflow_task_wrap(:destroy) { super }
|
35
35
|
end
|
36
36
|
|
37
|
+
def update_attributes(*args)
|
38
|
+
dynflow_task_wrap(:save) { super(*args) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_attributes!(*args)
|
42
|
+
dynflow_task_wrap(:save) { super(*args) }
|
43
|
+
end
|
44
|
+
|
37
45
|
protected
|
38
46
|
|
39
47
|
def sync_action_flag_reset!
|
@@ -83,6 +91,9 @@ module ForemanTasks
|
|
83
91
|
# we would start the execution phase inside this transaction which would lead
|
84
92
|
# to unexpected results.
|
85
93
|
def dynflow_task_wrap(method)
|
94
|
+
return yield if @_dynflow_task_wrapped
|
95
|
+
@_dynflow_task_wrapped = true
|
96
|
+
|
86
97
|
action = case method
|
87
98
|
when :save
|
88
99
|
self.new_record? ? create_action : update_action
|
@@ -100,13 +111,15 @@ module ForemanTasks
|
|
100
111
|
else
|
101
112
|
yield
|
102
113
|
end
|
114
|
+
ensure
|
115
|
+
@_dynflow_task_wrapped = false
|
103
116
|
end
|
104
117
|
|
105
118
|
# we don't want to start executing the task calling to external services
|
106
119
|
# when inside some other transaction. Might lead to unexpected results
|
107
120
|
def ensure_not_in_transaction!
|
108
121
|
if self.class.connection.open_transactions > 0
|
109
|
-
raise
|
122
|
+
raise 'Executing dynflow action inside a transaction is not a good idea'
|
110
123
|
end
|
111
124
|
end
|
112
125
|
|
@@ -13,7 +13,7 @@ module ForemanTasks
|
|
13
13
|
class LockConflict < StandardError
|
14
14
|
attr_reader :required_lock, :conflicting_locks
|
15
15
|
def initialize(required_lock, conflicting_locks)
|
16
|
-
super()
|
16
|
+
super("required lock: #{required_lock} conflicts wiht #{conflicting_locks.inspect}")
|
17
17
|
@required_lock = required_lock
|
18
18
|
@conflicting_locks = conflicting_locks
|
19
19
|
end
|
@@ -81,6 +81,17 @@ module ForemanTasks
|
|
81
81
|
return {:conditions => condition, :joins => joins }
|
82
82
|
end
|
83
83
|
|
84
|
+
def progress
|
85
|
+
case self.state.to_s
|
86
|
+
when "running", "paused"
|
87
|
+
0.5
|
88
|
+
when "stopped"
|
89
|
+
1
|
90
|
+
else
|
91
|
+
0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
84
95
|
protected
|
85
96
|
|
86
97
|
def generate_id
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module ForemanTasks
|
2
2
|
class Task::DynflowTask < ForemanTasks::Task
|
3
3
|
|
4
|
+
include Algebrick::TypeCheck
|
5
|
+
|
4
6
|
scope :for_action, ->(action_class) { where(label: action_class.name) }
|
5
7
|
|
6
8
|
def update_from_dynflow(data, planned)
|
@@ -17,13 +19,12 @@ module ForemanTasks
|
|
17
19
|
unless self.label
|
18
20
|
self.label = main_action.class.name
|
19
21
|
end
|
20
|
-
update_progress
|
21
22
|
end
|
22
23
|
self.save!
|
23
24
|
end
|
24
25
|
|
25
|
-
def
|
26
|
-
|
26
|
+
def progress
|
27
|
+
execution_plan.progress
|
27
28
|
end
|
28
29
|
|
29
30
|
def execution_plan
|
@@ -39,9 +40,9 @@ module ForemanTasks
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def humanized
|
42
|
-
{ action:
|
43
|
-
input:
|
44
|
-
output:
|
43
|
+
{ action: get_humanized(:humanized_name),
|
44
|
+
input: get_humanized(:humanized_input),
|
45
|
+
output: get_humanized(:humanized_output) }
|
45
46
|
end
|
46
47
|
|
47
48
|
def cli_example
|
@@ -54,5 +55,16 @@ module ForemanTasks
|
|
54
55
|
return @main_action if @main_action
|
55
56
|
execution_plan.root_plan_step.action execution_plan
|
56
57
|
end
|
58
|
+
|
59
|
+
def get_humanized(method)
|
60
|
+
Match! method, :humanized_name, :humanized_input, :humanized_output
|
61
|
+
if main_action.respond_to? method
|
62
|
+
begin
|
63
|
+
main_action.send method
|
64
|
+
rescue => error
|
65
|
+
"#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
57
69
|
end
|
58
70
|
end
|
@@ -27,15 +27,19 @@ module ForemanTasks
|
|
27
27
|
|
28
28
|
attr_accessor :lazy_initialization
|
29
29
|
|
30
|
+
# what rake tasks should run their own executor, not depending on the external one
|
31
|
+
attr_accessor :rake_tasks_with_executor
|
32
|
+
|
30
33
|
def initialize
|
31
|
-
self.action_logger
|
32
|
-
self.dynflow_logger
|
33
|
-
self.pool_size
|
34
|
-
self.remote
|
35
|
-
self.remote_socket_path
|
36
|
-
self.transaction_adapter
|
37
|
-
self.eager_load_paths
|
38
|
-
self.lazy_initialization
|
34
|
+
self.action_logger = Rails.logger
|
35
|
+
self.dynflow_logger = Rails.logger
|
36
|
+
self.pool_size = 5
|
37
|
+
self.remote = Rails.env.production?
|
38
|
+
self.remote_socket_path = File.join(Rails.root, "tmp", "sockets", "dynflow_socket")
|
39
|
+
self.transaction_adapter = ::Dynflow::TransactionAdapters::ActiveRecord.new
|
40
|
+
self.eager_load_paths = []
|
41
|
+
self.lazy_initialization = !Rails.env.production?
|
42
|
+
self.rake_tasks_with_executor = %w[db:migrate db:seed]
|
39
43
|
end
|
40
44
|
|
41
45
|
def initialize_world(world_class = ::Dynflow::World)
|
@@ -45,7 +49,15 @@ module ForemanTasks
|
|
45
49
|
# No matter what config.remote says, when the process is marked as executor,
|
46
50
|
# it can't be remote
|
47
51
|
def remote?
|
48
|
-
!ForemanTasks.dynflow.executor? &&
|
52
|
+
!ForemanTasks.dynflow.executor? &&
|
53
|
+
!rake_task_with_executor? &&
|
54
|
+
@remote
|
55
|
+
end
|
56
|
+
|
57
|
+
def rake_task_with_executor?
|
58
|
+
Rake.application.top_level_tasks.any? do |rake_task|
|
59
|
+
rake_tasks_with_executor.include?(rake_task)
|
60
|
+
end
|
49
61
|
end
|
50
62
|
|
51
63
|
protected
|
@@ -27,7 +27,9 @@ module ForemanTasks
|
|
27
27
|
Lock.owner!(::User.current, task.id) if ::User.current
|
28
28
|
elsif data[:state] != :planning
|
29
29
|
if task = ::ForemanTasks::Task::DynflowTask.find_by_external_id(execution_plan_id)
|
30
|
-
|
30
|
+
unless task.state.to_s == data[:state].to_s
|
31
|
+
task.update_from_dynflow(data, true)
|
32
|
+
end
|
31
33
|
end
|
32
34
|
end
|
33
35
|
end
|
data/lib/foreman_tasks/engine.rb
CHANGED
@@ -18,6 +18,15 @@ module ForemanTasks
|
|
18
18
|
ActiveRecord::SchemaDumper.ignore_tables << /^dynflow_.*$/
|
19
19
|
end
|
20
20
|
|
21
|
+
initializer "foreman_tasks.apipie" do
|
22
|
+
# this condition is here for compatibility reason to work with Foreman 1.4.x
|
23
|
+
if Apipie.configuration.api_controllers_matcher.is_a?(Array) &&
|
24
|
+
Apipie.configuration.respond_to?(:checksum_path)
|
25
|
+
Apipie.configuration.api_controllers_matcher << "#{ForemanTasks::Engine.root}/app/controllers/foreman_tasks/api/*.rb"
|
26
|
+
Apipie.configuration.checksum_path += ['/foreman_tasks/api/']
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
21
30
|
initializer "foreman_tasks.register_paths" do |app|
|
22
31
|
ForemanTasks.dynflow.config.eager_load_paths.concat(%W[#{ForemanTasks::Engine.root}/app/lib/actions])
|
23
32
|
end
|
@@ -59,8 +68,6 @@ module ForemanTasks
|
|
59
68
|
end
|
60
69
|
|
61
70
|
rake_tasks do
|
62
|
-
# enforce local executor in rake tasks
|
63
|
-
ForemanTasks.dynflow.executor!
|
64
71
|
load File.expand_path('../tasks/dynflow.rake', __FILE__)
|
65
72
|
end
|
66
73
|
end
|
data/lib/tasks/gettext.rake
CHANGED
@@ -1,16 +1,21 @@
|
|
1
|
-
|
2
|
-
task :store_action_names => :environment do
|
3
|
-
storage_file = "#{locale_path}/action_names.rb"
|
4
|
-
puts "writing action translations to: #{storage_file}"
|
1
|
+
gettext_find_task = Rake::Task['gettext:find'] rescue nil
|
5
2
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
3
|
+
if gettext_find_task
|
4
|
+
namespace :gettext do
|
5
|
+
task :store_action_names => :environment do
|
6
|
+
storage_file = "#{locale_path}/action_names.rb"
|
7
|
+
puts "writing action translations to: #{storage_file}"
|
8
|
+
|
9
|
+
File.write storage_file,
|
10
|
+
"# Autogenerated!\n" +
|
11
|
+
Actions::EntryAction.
|
12
|
+
all_action_names.
|
13
|
+
uniq.
|
14
|
+
map { |n| %[_("#{n}")] }.
|
15
|
+
join("\n")
|
16
|
+
end
|
13
17
|
end
|
18
|
+
|
19
|
+
gettext_find_task.enhance ['gettext:store_action_names']
|
14
20
|
end
|
15
21
|
|
16
|
-
Rake::Task['gettext:find'].enhance ['gettext:store_action_names']
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman-tasks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-03-
|
12
|
+
date: 2014-03-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
requirements:
|
51
51
|
- - ! '>='
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.
|
53
|
+
version: 0.6.0
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -58,7 +58,7 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - ! '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 0.
|
61
|
+
version: 0.6.0
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: sequel
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -153,6 +153,7 @@ files:
|
|
153
153
|
- config/routes.rb
|
154
154
|
- db/migrate/20131209122644_create_foreman_tasks_locks.rb
|
155
155
|
- db/migrate/20131205204140_create_foreman_tasks.rb
|
156
|
+
- db/migrate/20140324104010_remove_foreman_tasks_progress.rb
|
156
157
|
- lib/tasks/gettext.rake
|
157
158
|
- lib/foreman_tasks/tasks/dynflow.rake
|
158
159
|
- lib/foreman_tasks/engine.rb
|