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.
@@ -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