foreman-tasks 0.7.2 → 0.7.3

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.
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MDE1OGU2NTc0M2VkNTdhYjUwODE4YTM1YTNiMTVmYWQwNTUyNjIxZQ==
4
+ Y2I3YzI4OGYwN2U1YWZmZGQ1MmE2MmVlYTBiNTBlZmUyMDhkMDJkYQ==
5
5
  data.tar.gz: !binary |-
6
- YmU0NzM5ZjNhZTMxODdjZjVkYTFkYTkwYWY5YjhiZDljMjUxNGZkZA==
6
+ NDNlOGVkYjFjODE5ZmQ2NWNiMjYzOTFjMmI3NTQ3N2FjYmYzMmI2Mg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- Yzc2NTgwODNhODY2MTM5MWFjNmU3NzBkODZlZmNhYjZjZTJlZTIxMzVlZjll
10
- MDQ1OTk0ZmE4MTk5ZDRiMmUyMzViNzQxYTdjZDViNmMyNTcyZjQwNmIyODI0
11
- MzRiZjM4ZjA0YWJlNTRkZGY1ZjVjODNiNWJjMmZkYjAxYWU5OWM=
9
+ OTA1NDM0MjUyZDIzNGNmMjA3NTgwNWNkYzVmZDhmODg4ZDk3MGY0YjMzZGIx
10
+ ZjQyMTY4N2Y5NTgwNTQzMTEwNWU4ZmZlYThmNmEzZWVmMWE1ZGVmZDFlMWUz
11
+ N2IyZjkyMzc1YjIzOWZjZjY4M2E1NDc1NmI2NTI4ZTI4Y2RhOWE=
12
12
  data.tar.gz: !binary |-
13
- Y2VjMzU0Y2UxNjg0YjJlNGY1NDMwMWVjMzQ3NGMwNGVmMzI1ZjhkMmM2YWRl
14
- NTU1NGJmZDVhMWFiODkyYjQzMzJlMmExYTI3YmJmNjY4MGU1Yzg2ZGIwNGY1
15
- Njc1NzE3YTRmZmYxOTVjN2IyMGVhYmFiMDkyNmY1ZjM1ZTcyNmM=
13
+ ZDRjZmVlN2I2NDgwMDc4YjVlODA2MjE1ZjY5MzRmZGJmMDlkOGM5OTI0YWMy
14
+ ZTJkNmIzMDk0MDZiYTZmOGEzMzFiZDZmOTdmNGYwNTQzYjg2NzBkNTUwN2Ew
15
+ YzQ2N2RlNWU1ZjdiNWRhOWY5NDE0ZmVjYWMxOTA4NmJkYTcwNjI=
@@ -14,6 +14,9 @@ module ForemanTasks
14
14
  module Api
15
15
  class TasksController < ::Api::V2::BaseController
16
16
 
17
+ include ::Foreman::Controller::SmartProxyAuth
18
+ add_smart_proxy_filters :callback, :features => 'Dynflow'
19
+
17
20
  resource_description do
18
21
  resource_id 'foreman_tasks'
19
22
  api_version 'v2'
@@ -158,6 +161,20 @@ module ForemanTasks
158
161
  }
159
162
  end
160
163
 
164
+ api :POST, '/tasks/callback', N_("Send data to the task from external executor (such as smart_proxy_dynflow)")
165
+ param :callback, Hash do
166
+ param :task_id, :identifier, :desc => N_("UUID of the task")
167
+ param :step_id, String, :desc => N_("The id of the step inside the execution plan to send the event to ")
168
+ end
169
+ param :data, Hash, :desc => N_("Data to be sent to the action")
170
+ def callback
171
+ task = ForemanTasks::Task::DynflowTask.find(params[:callback][:task_id])
172
+ ForemanTasks.dynflow.world.event(task.external_id,
173
+ params[:callback][:step_id].to_i,
174
+ ::Actions::ProxyAction::CallbackData.new(params[:data]))
175
+ render :json => { :message => 'processing' }.to_json
176
+ end
177
+
161
178
  private
162
179
 
163
180
  def search_tasks(search_params)
@@ -26,6 +26,16 @@ module ForemanTasks
26
26
  redirect_to foreman_tasks_task_path(task)
27
27
  end
28
28
 
29
+ def cancel
30
+ task = find_dynflow_task
31
+ if task.cancel
32
+ flash[:notice] = _('Trying to cancel the task')
33
+ else
34
+ flash[:warning] = _('The task is not cancellable at the moment.')
35
+ end
36
+ redirect_to :back
37
+ end
38
+
29
39
  def resume
30
40
  task = find_dynflow_task
31
41
  if task.resumable?
@@ -34,7 +44,7 @@ module ForemanTasks
34
44
  else
35
45
  flash[:warning] = _('The execution has to be resumable.')
36
46
  end
37
- redirect_to foreman_tasks_task_path(task)
47
+ redirect_to :back
38
48
  end
39
49
 
40
50
  def unlock
@@ -46,7 +56,7 @@ module ForemanTasks
46
56
  else
47
57
  flash[:warning] = _('The execution has to be paused.')
48
58
  end
49
- redirect_to foreman_tasks_task_path(task)
59
+ redirect_to :back
50
60
  end
51
61
 
52
62
  def force_unlock
@@ -54,7 +64,7 @@ module ForemanTasks
54
64
  task.state = :stopped
55
65
  task.save!
56
66
  flash[:notice] = _('The task resources were unlocked with force.')
57
- redirect_to foreman_tasks_task_path(task)
67
+ redirect_to :back
58
68
  end
59
69
 
60
70
  # we need do this to make the Foreman helpers working properly
@@ -76,7 +86,7 @@ module ForemanTasks
76
86
  case params[:action]
77
87
  when 'sub_tasks'
78
88
  :view
79
- when 'resume', 'unlock', 'force_unlock', 'cancel_step'
89
+ when 'resume', 'unlock', 'force_unlock', 'cancel_step', 'cancel'
80
90
  :edit
81
91
  else
82
92
  super
@@ -0,0 +1,82 @@
1
+ module Actions
2
+ class ProxyAction < Base
3
+
4
+ include ::Dynflow::Action::Cancellable
5
+
6
+ class CallbackData
7
+ attr_reader :data
8
+
9
+ def initialize(data)
10
+ @data = data
11
+ end
12
+ end
13
+
14
+ def plan(proxy, options)
15
+ plan_self(options.merge(:proxy_url => proxy.url))
16
+ end
17
+
18
+ def run(event = nil)
19
+ case event
20
+ when nil
21
+ if output[:proxy_task_id]
22
+ on_resume
23
+ else
24
+ trigger_proxy_task
25
+ end
26
+ suspend
27
+ when ::Dynflow::Action::Skip
28
+ # do nothing
29
+ when ::Dynflow::Action::Cancellable::Cancel
30
+ cancel_proxy_task
31
+ when CallbackData
32
+ on_data(event.data)
33
+ else
34
+ raise "Unexpected event #{event.inspect}"
35
+ end
36
+ end
37
+
38
+ def trigger_proxy_task
39
+ response = proxy.trigger_task(proxy_action_name,
40
+ input.merge(:callback => { :task_id => task.id,
41
+ :step_id => run_step_id }))
42
+ output[:proxy_task_id] = response["task_id"]
43
+ end
44
+
45
+ def cancel_proxy_task
46
+ if output[:cancel_sent]
47
+ error! _("Cancel enforced: the task might be still running on the proxy")
48
+ else
49
+ proxy.cancel_task(output[:proxy_task_id])
50
+ output[:cancel_sent] = true
51
+ suspend
52
+ end
53
+ end
54
+
55
+ def on_resume
56
+ # TODO: add logic to load the data from the external action
57
+ suspend
58
+ end
59
+
60
+ # @override to put custom logic on event handling
61
+ def on_data(data)
62
+ output[:proxy_output] = data
63
+ end
64
+
65
+ # @override String name of an action to be triggered on server
66
+ def proxy_action_name
67
+ raise NotImplemented
68
+ end
69
+
70
+ def proxy
71
+ ProxyAPI::ForemanDynflow::DynflowProxy.new(:url => input[:proxy_url])
72
+ end
73
+
74
+ def proxy_output
75
+ output[:proxy_output]
76
+ end
77
+
78
+ def proxy_output=(output)
79
+ output[:proxy_output] = output
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,31 @@
1
+ module ProxyAPI
2
+ module ForemanDynflow
3
+ class DynflowProxy
4
+ PREFIX = 'dynflow'
5
+
6
+ class Task < ProxyAPI::Resource
7
+ def initialize(args)
8
+ @url = "#{args[:url]}/#{PREFIX}/tasks"
9
+ super args
10
+ @connect_params[:headers] ||= {}
11
+ @connect_params[:headers]['content-type'] = 'application/json'
12
+ end
13
+ end
14
+
15
+ def initialize(args)
16
+ @args = args
17
+ end
18
+
19
+ # Initiate the command
20
+ def trigger_task(action_name, action_input)
21
+ payload = MultiJson.dump(:action_name => action_name, :action_input => action_input)
22
+ MultiJson.load(Task.new(@args).send(:post, payload))
23
+ end
24
+
25
+ # Initiate the command
26
+ def cancel_task(proxy_task_id)
27
+ MultiJson.load(Task.new(@args).send(:post, "", "#{ proxy_task_id }/cancel"))
28
+ end
29
+ end
30
+ end
31
+ end
@@ -22,6 +22,14 @@ module ForemanTasks
22
22
  return changes
23
23
  end
24
24
 
25
+ def cancellable?
26
+ execution_plan.cancellable?
27
+ end
28
+
29
+ def cancel
30
+ execution_plan.cancel.any?
31
+ end
32
+
25
33
  def resumable?
26
34
  execution_plan.state == :paused
27
35
  end
@@ -9,6 +9,10 @@
9
9
  resume_foreman_tasks_task_path(@task),
10
10
  class: ['btn', 'btn-sm', 'btn-primary', ('disabled' unless @task.resumable?)].compact,
11
11
  method: :post) %>
12
+ <%= link_to(_('Cancel'),
13
+ cancel_foreman_tasks_task_path(@task),
14
+ class: ['btn', 'btn-sm', 'btn-primary', ('disabled' unless @task.cancellable?)].compact,
15
+ method: :post) %>
12
16
  <% if Setting['dynflow_allow_dangerous_actions'] %>
13
17
  <%= link_to(_('Unlock'),
14
18
  '',
@@ -71,7 +71,7 @@
71
71
  taskProgressReloader.stop();
72
72
  });
73
73
 
74
- <% if @task.state == 'running' %>
74
+ <% if @task.state != 'stopped' %>
75
75
  taskProgressReloader.start();
76
76
  <% else %>
77
77
  taskProgressReloader.stop();
@@ -6,6 +6,7 @@ Foreman::Application.routes.draw do
6
6
  end
7
7
  member do
8
8
  get :sub_tasks
9
+ post :cancel
9
10
  post :resume
10
11
  post :unlock
11
12
  post :force_unlock
@@ -19,6 +20,7 @@ Foreman::Application.routes.draw do
19
20
  post :bulk_search
20
21
  post :bulk_resume
21
22
  get :summary
23
+ post :callback
22
24
  end
23
25
  end
24
26
  end
@@ -0,0 +1,2 @@
1
+ f = Feature.find_or_create_by_name('Dynflow')
2
+ raise "Unable to create proxy feature: #{format_errors f}" if f.nil? || f.errors.any?
@@ -18,7 +18,7 @@ module ForemanTasks
18
18
  security_block :foreman_tasks do |map|
19
19
  permission :view_foreman_tasks, {:'foreman_tasks/tasks' => [:auto_complete_search, :sub_tasks, :index, :show],
20
20
  :'foreman_tasks/api/tasks' => [:bulk_search, :show, :index, :summary] }, :resource_type => ForemanTasks::Task.name
21
- permission :edit_foreman_tasks, {:'foreman_tasks/tasks' => [:resume, :unlock, :force_unlock, :cancel_step],
21
+ permission :edit_foreman_tasks, {:'foreman_tasks/tasks' => [:resume, :unlock, :force_unlock, :cancel_step, :cancel],
22
22
  :'foreman_tasks/api/tasks' => [:bulk_resume]}, :resource_type => ForemanTasks::Task.name
23
23
  end
24
24
 
@@ -51,6 +51,12 @@ module ForemanTasks
51
51
  ForemanTasks.dynflow.config.eager_load_paths.concat(%W[#{ForemanTasks::Engine.root}/app/lib/actions])
52
52
  end
53
53
 
54
+ initializer "foreman_tasks.test_exceptions" do |app|
55
+ if defined? ActiveSupport::TestCase
56
+ require 'foreman_tasks/test_extensions'
57
+ end
58
+ end
59
+
54
60
  initializer "foreman_tasks.load_app_instance_data" do |app|
55
61
  app.config.paths['db/migrate'] += ForemanTasks::Engine.paths['db/migrate'].existent
56
62
  end
@@ -0,0 +1,14 @@
1
+ module ForemanTasks
2
+ module TestExtensions
3
+ module AccessPermissionsTestExtension
4
+ def setup
5
+ super
6
+ if defined?(AccessPermissionsTest) && self.class == AccessPermissionsTest
7
+ skip 'used by proxy only' if __name__.include?('foreman_tasks/api/tasks/callback')
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
13
+
14
+ ActiveSupport::TestCase.send(:include, ForemanTasks::TestExtensions::AccessPermissionsTestExtension)
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = "0.7.2"
2
+ VERSION = "0.7.3"
3
3
  end
@@ -9,15 +9,43 @@ module ForemanTasks
9
9
  User.current = User.where(:login => 'apiadmin').first
10
10
  @request.env['HTTP_ACCEPT'] = 'application/json'
11
11
  @request.env['CONTENT_TYPE'] = 'application/json'
12
- @task = FactoryGirl.create(:dynflow_task, :user_create_task)
13
12
  end
14
13
 
15
- it 'searches for task on GET' do
16
- get(:show, :id => @task.id)
17
- assert_response :success
18
- assert_template 'api/tasks/show'
14
+ describe 'GET /api/tasks/show' do
15
+ it 'searches for task' do
16
+ task = FactoryGirl.create(:dynflow_task, :user_create_task)
17
+ get(:show, :id => task.id)
18
+ assert_response :success
19
+ assert_template 'api/tasks/show'
20
+ end
19
21
  end
20
22
 
23
+ describe 'POST /tasks/callback' do
24
+ self.use_transactional_fixtures = false
25
+
26
+ it 'passes the data to the corresponding action' do
27
+ Support::DummyProxyAction.reset
28
+
29
+ triggered = ForemanTasks.trigger(Support::DummyProxyAction, Support::DummyProxyAction.proxy, 'foo' => 'bar')
30
+ Support::DummyProxyAction.proxy.task_triggered.wait(5)
31
+
32
+ task = ForemanTasks::Task.find_by_external_id(triggered.id)
33
+ task.state.must_equal 'running'
34
+ task.result.must_equal 'pending'
35
+
36
+ callback = Support::DummyProxyAction.proxy.log[:trigger_task].first[1][:callback]
37
+ post(:callback, 'callback' => callback, 'data' => {'result' => 'success'})
38
+ triggered.finished.wait(5)
39
+
40
+ task.reload
41
+ task.state.must_equal 'stopped'
42
+ task.result.must_equal 'success'
43
+ task.main_action.output.must_equal({ "proxy_task_id" => "123",
44
+ "proxy_output" => { "result" => "success" }})
45
+ end
46
+ end
21
47
  end
22
48
  end
23
49
  end
50
+
51
+
@@ -1,5 +1,8 @@
1
1
  require 'test_helper'
2
2
  require_relative './support/dummy_dynflow_action'
3
+ require_relative './support/dummy_proxy_action'
4
+
5
+ require 'dynflow/testing'
3
6
 
4
7
  FactoryGirl.definition_file_paths = ["#{ForemanTasks::Engine.root}/test/factories"]
5
8
  FactoryGirl.find_definitions
@@ -0,0 +1,49 @@
1
+ module Support
2
+ class DummyProxyAction < Actions::ProxyAction
3
+
4
+ class DummyProxy
5
+ attr_reader :log, :task_triggered
6
+
7
+ def initialize
8
+ @log = Hash.new { |h, k| h[k] = [] }
9
+ @task_triggered = Concurrent.future
10
+ end
11
+
12
+ def trigger_task(*args)
13
+ @log[:trigger_task] << args
14
+ @task_triggered.success(true)
15
+ {"task_id" => "123"}
16
+ end
17
+
18
+ def cancel_task(*args)
19
+ @log[:cancel_task] << args
20
+ end
21
+
22
+ def url
23
+ 'proxy.example.com'
24
+ end
25
+ end
26
+
27
+ def proxy
28
+ self.class.proxy
29
+ end
30
+
31
+ def proxy_action_name
32
+ 'Proxy::DummyAction'
33
+ end
34
+
35
+ def task
36
+ super
37
+ rescue ActiveRecord::RecordNotFound
38
+ ForemanTasks::Task::DynflowTask.new.tap { |task| task.id = '123' }
39
+ end
40
+
41
+ def self.proxy
42
+ @proxy
43
+ end
44
+
45
+ def self.reset
46
+ @proxy = DummyProxy.new
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,67 @@
1
+ require "foreman_tasks_test_helper"
2
+
3
+ module ForemanTasks
4
+ class ProxyActionTest < ActiveSupport::TestCase
5
+
6
+ describe Actions::ProxyAction do
7
+ include ::Dynflow::Testing
8
+
9
+ before do
10
+ Support::DummyProxyAction.reset
11
+ @action = create_and_plan_action(Support::DummyProxyAction, Support::DummyProxyAction.proxy, 'foo' => 'bar')
12
+ @action = run_action(@action)
13
+ end
14
+
15
+ describe 'first run' do
16
+ it 'triggers the corresponding action on the proxy' do
17
+ proxy_call = Support::DummyProxyAction.proxy.log[:trigger_task].first
18
+ proxy_call.must_equal(["Proxy::DummyAction",
19
+ { "foo" => "bar",
20
+ "proxy_url" => "proxy.example.com",
21
+ "callback" => { "task_id" => "123", "step_id" => @action.run_step_id }}])
22
+ end
23
+ end
24
+
25
+ describe 'resumed run' do
26
+ it "doesn't trigger the corresponding action again on the proxy" do
27
+ action = run_action(@action)
28
+
29
+ action.state.must_equal :suspended
30
+
31
+ Support::DummyProxyAction.proxy.log[:trigger_task].size.must_equal 1
32
+ end
33
+ end
34
+
35
+ it 'supports skipping' do
36
+ action = run_action(@action, ::Dynflow::Action::Skip)
37
+ action.state.must_equal :success
38
+ end
39
+
40
+ describe 'cancel' do
41
+ it 'sends the cancel event to the proxy when the cancel event is sent for the first time' do
42
+ action = run_action(@action, ::Dynflow::Action::Cancellable::Cancel)
43
+ Support::DummyProxyAction.proxy.log[:cancel_task].first.must_equal ['123']
44
+ action.state.must_equal :suspended
45
+ end
46
+
47
+ it 'cancels the action immediatelly when cancel event is sent for the second time' do
48
+ action = run_action(@action, ::Dynflow::Action::Cancellable::Cancel)
49
+ error = begin
50
+ run_action(action, ::Dynflow::Action::Cancellable::Cancel)
51
+ rescue => e
52
+ e
53
+ end
54
+
55
+ Support::DummyProxyAction.proxy.log[:cancel_task].size.must_equal 1
56
+ error.message.must_match 'Cancel enforced'
57
+ end
58
+ end
59
+
60
+ it 'saves the data comming from the proxy to the output and finishes' do
61
+ action = run_action(@action, ::Actions::ProxyAction::CallbackData.new('result' => 'success'))
62
+ action.output[:proxy_output].must_equal({'result' => 'success'})
63
+ end
64
+ end
65
+
66
+ end
67
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman-tasks
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.2
4
+ version: 0.7.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Nečas
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-31 00:00:00.000000000 Z
11
+ date: 2015-08-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dynflow
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ~>
18
18
  - !ruby/object:Gem::Version
19
- version: 0.8.1
19
+ version: 0.8.3
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
- version: 0.8.1
26
+ version: 0.8.3
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: sequel
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -104,6 +104,8 @@ files:
104
104
  - app/lib/actions/helpers/humanizer.rb
105
105
  - app/lib/actions/helpers/lock.rb
106
106
  - app/lib/actions/middleware/keep_current_user.rb
107
+ - app/lib/actions/proxy_action.rb
108
+ - app/lib/proxy_api/foreman_dynflow/dynflow_proxy.rb
107
109
  - app/models/foreman_tasks/concerns/action_subject.rb
108
110
  - app/models/foreman_tasks/concerns/action_triggering.rb
109
111
  - app/models/foreman_tasks/concerns/architecture_action_subject.rb
@@ -133,6 +135,7 @@ files:
133
135
  - db/migrate/20140324104010_remove_foreman_tasks_progress.rb
134
136
  - db/migrate/20140813215942_add_parent_task_id.rb
135
137
  - db/seeds.d/20-foreman_tasks_permissions.rb
138
+ - db/seeds.d/60-dynflow_proxy_feature.rb
136
139
  - deploy/foreman-tasks.init
137
140
  - deploy/foreman-tasks.service
138
141
  - deploy/foreman-tasks.sysconfig
@@ -150,6 +153,7 @@ files:
150
153
  - lib/foreman_tasks/tasks/cleanup.rake
151
154
  - lib/foreman_tasks/tasks/dynflow.rake
152
155
  - lib/foreman_tasks/tasks/export_tasks.rake
156
+ - lib/foreman_tasks/test_extensions.rb
153
157
  - lib/foreman_tasks/triggers.rb
154
158
  - lib/foreman_tasks/version.rb
155
159
  - lib/tasks/gettext.rake
@@ -158,7 +162,9 @@ files:
158
162
  - test/foreman_tasks_test_helper.rb
159
163
  - test/helpers/foreman_tasks/tasks_helper_test.rb
160
164
  - test/support/dummy_dynflow_action.rb
165
+ - test/support/dummy_proxy_action.rb
161
166
  - test/unit/actions/action_with_sub_plans_test.rb
167
+ - test/unit/actions/proxy_action_test.rb
162
168
  - test/unit/cleaner_test.rb
163
169
  - test/unit/dynflow_console_authorizer_test.rb
164
170
  - test/unit/task_test.rb
@@ -188,11 +194,13 @@ summary: Foreman plugin for showing tasks information for resoruces and users
188
194
  test_files:
189
195
  - test/helpers/foreman_tasks/tasks_helper_test.rb
190
196
  - test/support/dummy_dynflow_action.rb
197
+ - test/support/dummy_proxy_action.rb
191
198
  - test/foreman_tasks_test_helper.rb
192
199
  - test/controllers/api/tasks_controller_test.rb
193
200
  - test/factories/task_factory.rb
194
201
  - test/unit/cleaner_test.rb
195
202
  - test/unit/dynflow_console_authorizer_test.rb
203
+ - test/unit/actions/proxy_action_test.rb
196
204
  - test/unit/actions/action_with_sub_plans_test.rb
197
205
  - test/unit/task_test.rb
198
206
  has_rdoc: