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.
@@ -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 = PARTS_ORDER
38
+ parts = self.class.default_parts
29
39
  end
30
40
  included_parts(parts, @input).map do |part|
31
- [part, humanize_resource(part, @input[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(type, data, other_data)
40
- humanized_type = _(type)
41
- humanized_value = data[:name] || data[:label] || data[:id]
42
- { text: "#{humanized_type} '#{humanized_value}'",
43
- link: link_to_resource(type, data, other_data) }
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
- # TODO: this probably is not the best place for this logic, find
47
- # out where to put this
48
- def link_to_resource(type, data, other_data)
49
- case type
50
- when :product
51
- "#/products/#{data[:id]}/info" if data[:id]
52
- when :repository
53
- if other_data[:product] && other_data[:product][:id] && data[:id]
54
- "#/products/#{other_data[:product][:id]}/repositories/#{data[:id]}"
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
- when :system
57
- if data[:uuid]
58
- "#/systems/#{data[:uuid]}/info"
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
- when :organization
61
- if data[:label]
62
- "/organizations/#{data[:id]}/edit"
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 "Executing dynflow action inside a transaction is not a good idea"
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 update_progress
26
- self.progress = execution_plan.progress
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: main_action.respond_to?(:humanized_name) && main_action.humanized_name,
43
- input: main_action.respond_to?(:humanized_input) && main_action.humanized_input,
44
- output: main_action.respond_to?(:humanized_output) && main_action.humanized_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
@@ -0,0 +1,5 @@
1
+ class RemoveForemanTasksProgress < ActiveRecord::Migration
2
+ def change
3
+ remove_column :foreman_tasks_tasks, :progress
4
+ end
5
+ 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 = Rails.logger
32
- self.dynflow_logger = Rails.logger
33
- self.pool_size = 5
34
- self.remote = Rails.env.production?
35
- self.remote_socket_path = File.join(Rails.root, "tmp", "sockets", "dynflow_socket")
36
- self.transaction_adapter = ::Dynflow::TransactionAdapters::ActiveRecord.new
37
- self.eager_load_paths = []
38
- self.lazy_initialization = !Rails.env.production?
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? && @remote
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
- task.update_from_dynflow(data, true)
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -1,16 +1,21 @@
1
- namespace :gettext do
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
- File.write storage_file,
7
- "# Autogenerated!\n" +
8
- Actions::EntryAction.
9
- all_action_names.
10
- uniq.
11
- map { |n| %[_("#{n}")] }.
12
- join("\n")
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.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 00:00:00.000000000 Z
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.5.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.5.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