foreman-tasks 0.7.2 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
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: