foreman-tasks 0.12.2 → 0.13.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c9314affd9bff690bd88cbd4aa26c65033b10eb
4
- data.tar.gz: 24fc605d6fd7038ed08a4dfbd55ef1c88a81421c
3
+ metadata.gz: 0111beaab4e9971f1039c81e0ccf31ad5cbb79e4
4
+ data.tar.gz: 0077b9efb0e93957f99a37fe4d08469b6f6b62d2
5
5
  SHA512:
6
- metadata.gz: 60bb278cd278df8fef607c162335e70419da9fc52ebca9a6e4de7b5520976638e2c04e17108ad594e1788933ffca2a16fc754044011ecf289ab1ead78642ae17
7
- data.tar.gz: f20a4318e569a2d11ab00c60bf23b21dfb6b579ac0373eb6a0e444325904b56d5ae8fa11a3f782858efdc35b3143f956ef7028feec32fb1691a1862fcf7e3678
6
+ metadata.gz: 0aff56c13e49c3cca9802a8ae63f0f4c024ba69a4b6bb07d9453e236de2571d284895906192c8b52cc2e8ed8300b083e3643d47bf8894fab8fbde55da5bc3364
7
+ data.tar.gz: 55255ff6e02f84e834b2ea8927364985830f13a0b456c64af503af56b0a76f286e1a94e5ec2fef04b3ee4da48fe905836a242b9baae4efd90f54afe04ee03944
@@ -58,11 +58,11 @@ module ForemanTasks
58
58
  end
59
59
 
60
60
  def recurring_logic_next_occurrence(recurring_logic)
61
- if %w[finished cancelled].include? recurring_logic.state
62
- '-'
63
- else
64
- recurring_logic.next_occurrence_time
65
- end
61
+ default = '-'
62
+ return default if %w[finished cancelled].include? recurring_logic.state
63
+
64
+ last_task = recurring_logic.tasks.order(:start_at).last
65
+ last_task ? last_task.start_at : default
66
66
  end
67
67
 
68
68
  def time_f(f, attr, field_options = {}, time_options = {}, html_options = {})
@@ -126,7 +126,7 @@ module Actions
126
126
  product_id = fetch_data(data, :product, :id)
127
127
  repo_id = fetch_data(data, :repo, :id)
128
128
  if product_id && repo_id
129
- "#/products/#{product_id}/repositories/#{repo_id}"
129
+ "/products/#{product_id}/repositories/#{repo_id}"
130
130
  end
131
131
  end
132
132
  end
@@ -152,7 +152,7 @@ module Actions
152
152
 
153
153
  def link(data)
154
154
  if (content_view_id = fetch_data(data, :content_view, :id))
155
- "#/content_views/#{content_view_id}/versions"
155
+ "/content_views/#{content_view_id}/versions"
156
156
  end
157
157
  end
158
158
  end
@@ -168,7 +168,7 @@ module Actions
168
168
 
169
169
  def link(data)
170
170
  if (product_id = fetch_data(data, :product, :id))
171
- "#/products/#{product_id}/info"
171
+ "/products/#{product_id}/"
172
172
  end
173
173
  end
174
174
  end
@@ -22,7 +22,7 @@ module Actions
22
22
  private
23
23
 
24
24
  def with_current_taxonomies
25
- if has_current_taxonomies?
25
+ if current_taxonomies?
26
26
  yield
27
27
  else
28
28
  restore_current_taxonomies { yield }
@@ -43,9 +43,9 @@ module Actions
43
43
  Location.current = nil
44
44
  end
45
45
 
46
- def has_current_taxonomies?
46
+ def current_taxonomies?
47
47
  (Organization.current || action.input[:current_organization_id].nil?) &&
48
- (Location.current || action.input[:current_location_id].nil?)
48
+ (Location.current || action.input[:current_location_id].nil?)
49
49
  end
50
50
  end
51
51
  end
@@ -7,11 +7,6 @@ module Actions
7
7
  # After an action is delayed, it checks whether the delay_options
8
8
  # hash contains an id of a recurring logic. If so, it adds the task
9
9
  # to the recurring logic's task group, otherwise does nothing.
10
- #
11
- # After the action's plan phase the middleware checks if the task
12
- # is associated with a task group of any recurring logic, in which case
13
- # it triggers another repeat using the task group's recurring logic,
14
- # otherwise does nothing.
15
10
  def delay(delay_options, *args)
16
11
  pass(delay_options, *args).tap do
17
12
  if delay_options[:recurring_logic_id]
@@ -20,13 +15,6 @@ module Actions
20
15
  end
21
16
  end
22
17
 
23
- def plan(*args)
24
- pass(*args).tap do
25
- task_group = task.task_groups.find { |tg| tg.is_a? ::ForemanTasks::TaskGroups::RecurringLogicTaskGroup }
26
- task_group.recurring_logic.trigger_repeat(action.class, *args) if task_group
27
- end
28
- end
29
-
30
18
  private
31
19
 
32
20
  def recurring_logic(delay_options)
@@ -0,0 +1,27 @@
1
+ module Actions
2
+ module RecurringAction
3
+ # When included sets the base action to use the RecurringLogic middleware and configures
4
+ # #trigger_repeat to be called when appropriate to trigger the next repeat.
5
+ def self.included(base)
6
+ base.middleware.use Actions::Middleware::RecurringLogic
7
+ base.execution_plan_hooks.use :trigger_repeat, :on => [:planned, :failure]
8
+ end
9
+
10
+ # Hook to be called when a repetition needs to be triggered. This either happens when the plan goes into planned state
11
+ # or when it fails.
12
+ def trigger_repeat(execution_plan)
13
+ if execution_plan.delay_record && recurring_logic_task_group
14
+ args = execution_plan.delay_record.args
15
+ logic = recurring_logic_task_group.recurring_logic
16
+ logic.trigger_repeat_after(task.start_at, self.class, *args)
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def recurring_logic_task_group
23
+ @task_group ||= task.task_groups
24
+ .find { |tg| tg.is_a? ::ForemanTasks::TaskGroups::RecurringLogicTaskGroup }
25
+ end
26
+ end
27
+ end
@@ -33,12 +33,13 @@ module ForemanTasks
33
33
  trigger_repeat(action_class, *args)
34
34
  end
35
35
 
36
- def trigger_repeat(action_class, *args)
37
- if can_continue?
36
+ def trigger_repeat_after(time, action_class, *args)
37
+ return if cancelled?
38
+ if can_continue?(time)
38
39
  self.iteration += 1
39
40
  save!
40
41
  ::ForemanTasks.delay action_class,
41
- generate_delay_options,
42
+ generate_delay_options(time),
42
43
  *args
43
44
  else
44
45
  self.state = 'finished'
@@ -47,6 +48,10 @@ module ForemanTasks
47
48
  end
48
49
  end
49
50
 
51
+ def trigger_repeat(action_class, *args)
52
+ trigger_repeat_after(Time.zone.now, action_class, *args)
53
+ end
54
+
50
55
  def cancel
51
56
  self.state = 'cancelled'
52
57
  save!
@@ -89,6 +94,10 @@ module ForemanTasks
89
94
  state == 'finished'
90
95
  end
91
96
 
97
+ def cancelled?
98
+ state == 'cancelled'
99
+ end
100
+
92
101
  def humanized_state
93
102
  case state
94
103
  when 'active'
@@ -109,10 +109,16 @@ module ForemanTasks
109
109
  state == 'paused'
110
110
  end
111
111
 
112
+ # returns true if the task is *CURRENTLY* waiting to be executed in the future
113
+ def scheduled?
114
+ state == 'scheduled'
115
+ end
116
+
112
117
  def recurring?
113
118
  !recurring_logic_task_group_ids.empty?
114
119
  end
115
120
 
121
+ # returns true if the task was planned to execute in the future
116
122
  def delayed?
117
123
  start_at.to_i != started_at.to_i
118
124
  end
@@ -8,12 +8,12 @@ module ForemanTasks
8
8
  def update_from_dynflow(data)
9
9
  utc_zone = ActiveSupport::TimeZone.new('UTC')
10
10
  self.external_id = data[:id]
11
- self.started_at = utc_zone.parse(data[:started_at]) unless data[:started_at].nil?
12
- self.ended_at = utc_zone.parse(data[:ended_at]) unless data[:ended_at].nil?
13
11
  self.result = map_result(data).to_s
14
12
  self.state = data[:state].to_s
15
- self.start_at = utc_zone.parse(data[:start_at]) if data[:start_at]
16
- self.start_before = utc_zone.parse(data[:start_before]) if data[:start_before]
13
+ self.started_at = string_to_time(utc_zone, data[:started_at]) unless data[:started_at].nil?
14
+ self.ended_at = string_to_time(utc_zone, data[:ended_at]) unless data[:ended_at].nil?
15
+ self.start_at = string_to_time(utc_zone, data[:start_at]) if data[:start_at]
16
+ self.start_before = string_to_time(utc_zone, data[:start_before]) if data[:start_before]
17
17
  self.parent_task_id ||= begin
18
18
  if main_action.caller_execution_plan_id
19
19
  DynflowTask.where(:external_id => main_action.caller_execution_plan_id).first!.id
@@ -153,6 +153,11 @@ module ForemanTasks
153
153
 
154
154
  private
155
155
 
156
+ def string_to_time(zone, time)
157
+ return time if time.is_a?(Time)
158
+ zone.parse(time)
159
+ end
160
+
156
161
  def set_action_field
157
162
  self.action = to_label
158
163
  end
@@ -29,7 +29,7 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
29
29
  s.extra_rdoc_files = Dir['README*', 'LICENSE']
30
30
 
31
31
  s.add_dependency "foreman-tasks-core"
32
- s.add_dependency "dynflow", '~> 0.8.29'
32
+ s.add_dependency "dynflow", '~> 1.0'
33
33
  s.add_dependency "sinatra" # for Dynflow web console
34
34
  s.add_dependency "parse-cron", '~> 0.1.4'
35
35
  s.add_dependency "get_process_mem" # for memory polling
@@ -35,8 +35,8 @@ module ForemanTasks
35
35
  options
36
36
  end
37
37
 
38
- def initialize_persistence
39
- ForemanTasks::Dynflow::Persistence.new(default_sequel_adapter_options)
38
+ def persistence_class
39
+ ForemanTasks::Dynflow::Persistence
40
40
  end
41
41
  end
42
42
  end
@@ -98,7 +98,7 @@ module ForemanTasks
98
98
  end
99
99
 
100
100
  initializer 'foreman_tasks.register_paths' do |_app|
101
- ForemanTasks.dynflow.config.eager_load_paths.concat(%W[#{ForemanTasks::Engine.root}/app/lib/actions])
101
+ ForemanTasks.dynflow.config.eager_load_paths.concat(%W[#{ForemanTasks::Engine.root}/app/lib/actions #{ForemanTasks::Engine.root}/app/lib/hooks])
102
102
  end
103
103
 
104
104
  initializer 'foreman_tasks.test_exceptions' do |_app|
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '0.12.2'.freeze
2
+ VERSION = '0.13.0'.freeze
3
3
  end
@@ -0,0 +1,67 @@
1
+ require 'foreman_tasks_test_helper'
2
+
3
+ module Actions
4
+ module Middleware
5
+ class KeepCurrentTaxonomiesTest < ActiveSupport::TestCase
6
+ include ::Dynflow::Testing
7
+
8
+ class TestAction < Support::DummyDynflowAction
9
+ middleware.use KeepCurrentTaxonomies
10
+
11
+ def run; end
12
+ end
13
+
14
+ before do
15
+ @org = mock('organization')
16
+ @org.stubs(:id).returns(1)
17
+ @loc = mock('location')
18
+ @loc.stubs(:id).returns(2)
19
+ end
20
+
21
+ describe 'plan' do
22
+ test 'with current taxonomies set' do
23
+ Organization.expects(:current).twice.returns(@org)
24
+ Location.expects(:current).twice.returns(@loc)
25
+
26
+ @action = create_and_plan_action(TestAction)
27
+ assert_equal(@org.id, @action.input['current_organization_id'])
28
+ assert_equal(@loc.id, @action.input['current_location_id'])
29
+ end
30
+
31
+ test 'with one current taxonomy set (organization)' do
32
+ Organization.expects(:current).twice.returns(@org)
33
+ Location.expects(:current).twice
34
+
35
+ @action = create_and_plan_action(TestAction)
36
+ assert_equal(@org.id, @action.input['current_organization_id'])
37
+ assert_nil(@action.input['current_location_id'])
38
+ end
39
+ end
40
+
41
+ describe 'run' do
42
+ before do
43
+ Organization.stubs(:current).returns(@org)
44
+ Location.stubs(:current).returns(@loc)
45
+
46
+ @action = create_and_plan_action(TestAction)
47
+
48
+ Organization.stubs(:current)
49
+ Location.stubs(:current)
50
+ end
51
+
52
+ test 'with current taxonomies as input' do
53
+ Organization.unscoped.class.any_instance.expects(:find).with(@org.id).returns(@org)
54
+ Location.unscoped.class.any_instance.expects(:find).with(@loc.id).returns(@loc)
55
+
56
+ Organization.expects(:current=).with(@org)
57
+ Location.expects(:current=).with(@loc)
58
+
59
+ Organization.stubs(:current=).with(nil)
60
+ Location.stubs(:current=).with(nil)
61
+
62
+ @action = run_action(@action)
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,49 @@
1
+ require 'foreman_tasks_test_helper'
2
+
3
+ module Actions
4
+ module Middleware
5
+ class KeepCurrentUserTest < ActiveSupport::TestCase
6
+ include ::Dynflow::Testing
7
+
8
+ class TestAction < Support::DummyDynflowAction
9
+ middleware.use KeepCurrentUser
10
+
11
+ def run; end
12
+ end
13
+
14
+ before do
15
+ @user = mock('user')
16
+ @user.stubs(:id).returns(1)
17
+ end
18
+
19
+ describe 'plan' do
20
+ test 'with current user set' do
21
+ User.expects(:current).twice.returns(@user)
22
+
23
+ @action = create_and_plan_action(TestAction)
24
+ assert_equal(@user.id, @action.input['current_user_id'])
25
+ end
26
+ end
27
+
28
+ describe 'run' do
29
+ before do
30
+ User.stubs(:current).returns(@user)
31
+
32
+ @action = create_and_plan_action(TestAction)
33
+
34
+ User.stubs(:current)
35
+ end
36
+
37
+ test 'with current user as input' do
38
+ User.unscoped.class.any_instance.expects(:find).with(@user.id).returns(@user)
39
+
40
+ User.expects(:current=).with(@user)
41
+
42
+ User.stubs(:current=).with(nil)
43
+
44
+ @action = run_action(@action)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,87 @@
1
+ require 'foreman_tasks_test_helper'
2
+
3
+ module ForemanTasks
4
+ class RecurringActionTest < ActiveSupport::TestCase
5
+ class HookedAction < Actions::EntryAction
6
+ include Actions::RecurringAction
7
+
8
+ def plan(should_fail, _numbers)
9
+ plan_self(:should_fail => should_fail)
10
+ end
11
+
12
+ def run
13
+ raise "A controlled failure" if input[:should_fail]
14
+ end
15
+ end
16
+
17
+ describe Actions::RecurringAction do
18
+ include ::Dynflow::Testing
19
+
20
+ let(:preset) do
21
+ {
22
+ :minutes => 0,
23
+ :hours => 12,
24
+ :days => 1,
25
+ :months => (Time.zone.now.month + 1) % 12
26
+ }
27
+ end
28
+
29
+ let(:recurring_logic) do
30
+ cronline = ForemanTasks::RecurringLogic.assemble_cronline(preset)
31
+ logic = ForemanTasks::RecurringLogic.new_from_cronline(cronline)
32
+ logic.state = 'active'
33
+ logic.save!
34
+ logic
35
+ end
36
+
37
+ let(:args) { [false, [1, 2, 3]] }
38
+
39
+ let(:recurring_task) do
40
+ recurring_logic.start(HookedAction, *args)
41
+ recurring_logic.tasks.first
42
+ end
43
+
44
+ specify 'it triggers the repeat when task is cancelled' do
45
+ recurring_task.must_be :delayed?
46
+ recurring_logic.tasks.count.must_equal 1
47
+ cancelled_events = recurring_task.execution_plan.cancel
48
+ cancelled_events.each(&:wait!)
49
+ recurring_logic.reload
50
+ recurring_logic.tasks.count.must_equal 2
51
+ new_task = recurring_logic.tasks.find { |task| task.id != recurring_task.id }
52
+ new_task.execution_plan.delay_record.args.must_equal args
53
+ new_task.start_at.must_equal(recurring_task.start_at + 1.year)
54
+ end
55
+
56
+ specify 'it triggers the repeat when the task goes into planned state' do
57
+ delay_options = recurring_logic.generate_delay_options
58
+ task = ForemanTasks.delay HookedAction, delay_options, args
59
+ recurring_logic.tasks.count.must_equal 1
60
+
61
+ # Perform planning of the delayed plan
62
+ task.execution_plan.delay_record.plan
63
+
64
+ # Check a repetition was planned
65
+ recurring_logic.tasks.count.must_equal 2
66
+ end
67
+
68
+ specify 'it does not trigger repeat when failing in run' do
69
+ delay_options = recurring_logic.generate_delay_options
70
+ task = ForemanTasks.delay HookedAction, delay_options, true, args.last
71
+ recurring_logic.tasks.count.must_equal 1
72
+
73
+ # Perform the planning (trigger repeat)
74
+ task.execution_plan.delay_record.plan
75
+ recurring_logic.tasks.count.must_equal 2
76
+
77
+ # Let it fail
78
+ task.execution_plan.delay_record.execute.finished.wait
79
+ task.reload
80
+ task.result.must_equal 'error'
81
+
82
+ # Check no new repetitions were planned
83
+ recurring_logic.tasks.count.must_equal 2
84
+ end
85
+ end
86
+ end
87
+ end
@@ -106,6 +106,13 @@ class RecurringLogicsTest < ActiveSupport::TestCase
106
106
  logic.end_time.must_be_close_to(triggering.end_time, 1.second)
107
107
  end
108
108
 
109
+ it 'cannot trigger tasks when cancelled' do
110
+ recurring_logic = ForemanTasks::RecurringLogic.new_from_cronline('* * * * *')
111
+ recurring_logic.state = 'cancelled'
112
+ recurring_logic.expects(:can_continue?).never
113
+ recurring_logic.trigger_repeat('this is not important', 'neither is this')
114
+ end
115
+
109
116
  describe 'validation' do
110
117
  let(:logic) { FactoryBot.build(:recurring_logic) }
111
118
 
@@ -122,7 +122,7 @@ class TasksTest < ActiveSupport::TestCase
122
122
 
123
123
  describe 'recurring task' do
124
124
  let(:logic) { FactoryBot.build(:recurring_logic) }
125
- let(:task) { FactoryBot.create(:some_task) }
125
+ let(:task) { FactoryBot.create(:dynflow_task) }
126
126
 
127
127
  it 'can indicate it is recurring' do
128
128
  refute task.recurring?
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.12.2
4
+ version: 0.13.0
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: 2018-03-23 00:00:00.000000000 Z
11
+ date: 2018-04-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: foreman-tasks-core
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 0.8.29
33
+ version: '1.0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 0.8.29
40
+ version: '1.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: sinatra
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -144,6 +144,7 @@ files:
144
144
  - app/lib/actions/middleware/rails_executor_wrap.rb
145
145
  - app/lib/actions/middleware/recurring_logic.rb
146
146
  - app/lib/actions/proxy_action.rb
147
+ - app/lib/actions/recurring_action.rb
147
148
  - app/lib/actions/serializers/active_record_serializer.rb
148
149
  - app/lib/proxy_api/foreman_dynflow/dynflow_proxy.rb
149
150
  - app/models/foreman_tasks/concerns/action_subject.rb
@@ -244,12 +245,15 @@ files:
244
245
  - test/factories/triggering_factory.rb
245
246
  - test/foreman_tasks_test_helper.rb
246
247
  - test/helpers/foreman_tasks/tasks_helper_test.rb
248
+ - test/lib/actions/middleware/keep_current_taxonomies_test.rb
249
+ - test/lib/actions/middleware/keep_current_user_test.rb
247
250
  - test/support/dummy_dynflow_action.rb
248
251
  - test/support/dummy_proxy_action.rb
249
252
  - test/support/dummy_task_group.rb
250
253
  - test/tasks/generate_task_actions_test.rb
251
254
  - test/unit/actions/action_with_sub_plans_test.rb
252
255
  - test/unit/actions/proxy_action_test.rb
256
+ - test/unit/actions/recurring_action_test.rb
253
257
  - test/unit/cleaner_test.rb
254
258
  - test/unit/config/environment.rb
255
259
  - test/unit/dynflow_console_authorizer_test.rb
@@ -279,7 +283,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
279
283
  version: '0'
280
284
  requirements: []
281
285
  rubyforge_project:
282
- rubygems_version: 2.6.12
286
+ rubygems_version: 2.6.8
283
287
  signing_key:
284
288
  specification_version: 4
285
289
  summary: Foreman plugin for showing tasks information for resoruces and users
@@ -293,12 +297,15 @@ test_files:
293
297
  - test/factories/triggering_factory.rb
294
298
  - test/foreman_tasks_test_helper.rb
295
299
  - test/helpers/foreman_tasks/tasks_helper_test.rb
300
+ - test/lib/actions/middleware/keep_current_taxonomies_test.rb
301
+ - test/lib/actions/middleware/keep_current_user_test.rb
296
302
  - test/support/dummy_dynflow_action.rb
297
303
  - test/support/dummy_proxy_action.rb
298
304
  - test/support/dummy_task_group.rb
299
305
  - test/tasks/generate_task_actions_test.rb
300
306
  - test/unit/actions/action_with_sub_plans_test.rb
301
307
  - test/unit/actions/proxy_action_test.rb
308
+ - test/unit/actions/recurring_action_test.rb
302
309
  - test/unit/cleaner_test.rb
303
310
  - test/unit/config/environment.rb
304
311
  - test/unit/dynflow_console_authorizer_test.rb