foreman-tasks 0.4.0 → 0.5.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/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
|