foreman-tasks 4.1.5 → 5.2.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 +4 -4
- data/.github/workflows/ruby_tests.yml +0 -1
- data/.rubocop.yml +0 -4
- data/.rubocop_todo.yml +0 -2
- data/README.md +8 -6
- data/app/assets/javascripts/foreman_tasks/trigger_form.js +7 -0
- data/app/controllers/foreman_tasks/api/tasks_controller.rb +2 -2
- data/app/controllers/foreman_tasks/tasks_controller.rb +2 -2
- data/app/graphql/mutations/recurring_logics/cancel.rb +27 -0
- data/app/graphql/types/recurring_logic.rb +21 -0
- data/app/graphql/types/task.rb +25 -0
- data/app/graphql/types/triggering.rb +16 -0
- data/app/helpers/foreman_tasks/foreman_tasks_helper.rb +4 -1
- data/app/lib/actions/helpers/with_continuous_output.rb +1 -1
- data/app/lib/actions/proxy_action.rb +2 -12
- data/app/lib/actions/trigger_proxy_batch.rb +79 -0
- data/app/models/foreman_tasks/recurring_logic.rb +10 -0
- data/app/models/foreman_tasks/remote_task.rb +3 -19
- data/app/models/foreman_tasks/task.rb +29 -0
- data/app/models/foreman_tasks/triggering.rb +14 -4
- data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -1
- data/app/views/foreman_tasks/layouts/react.html.erb +0 -1
- data/app/views/foreman_tasks/recurring_logics/index.html.erb +4 -2
- data/app/views/foreman_tasks/task_groups/recurring_logic_task_groups/_recurring_logic_task_group.html.erb +8 -0
- data/db/migrate/20210720115251_add_purpose_to_recurring_logic.rb +6 -0
- data/extra/foreman-tasks-cleanup.sh +127 -0
- data/extra/foreman-tasks-export.sh +121 -0
- data/foreman-tasks.gemspec +1 -4
- data/lib/foreman_tasks/continuous_output.rb +50 -0
- data/lib/foreman_tasks/engine.rb +8 -15
- data/lib/foreman_tasks/tasks/export_tasks.rake +29 -8
- data/lib/foreman_tasks/version.rb +1 -1
- data/lib/foreman_tasks.rb +2 -5
- data/locale/fr/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/ja/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/zh_CN/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/package.json +7 -9
- data/test/controllers/api/tasks_controller_test.rb +19 -1
- data/test/controllers/tasks_controller_test.rb +19 -0
- data/test/factories/recurring_logic_factory.rb +7 -1
- data/test/graphql/mutations/recurring_logics/cancel_mutation_test.rb +66 -0
- data/test/graphql/queries/recurring_logic_test.rb +28 -0
- data/test/graphql/queries/recurring_logics_query_test.rb +30 -0
- data/test/graphql/queries/task_query_test.rb +33 -0
- data/test/graphql/queries/tasks_query_test.rb +31 -0
- data/test/support/dummy_proxy_action.rb +6 -0
- data/test/unit/actions/proxy_action_test.rb +11 -11
- data/test/unit/actions/trigger_proxy_batch_test.rb +59 -0
- data/test/unit/remote_task_test.rb +0 -8
- data/test/unit/task_test.rb +39 -8
- data/test/unit/triggering_test.rb +22 -0
- metadata +23 -26
- data/test/core/unit/dispatcher_test.rb +0 -43
- data/test/core/unit/runner_test.rb +0 -116
- data/test/core/unit/task_launcher_test.rb +0 -56
- data/test/foreman_tasks_core_test_helper.rb +0 -4
- data/test/unit/otp_manager_test.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa2189368cc093a6ca613ca1b91add99bc3689b2cb22cd0c04a3718821b5f27f
|
4
|
+
data.tar.gz: f7e82294bbbb76fe6576b6c2de9e55c16342962f711a8e5c9070787be304a87c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f20e5fba4dbc2401a4fed1f5eb68961ccc7095071fc309316ad14392b9209141e96aa2372eb7bb5db0e7f829ef08727ff2e232e53de86baa8385183ddb722252
|
7
|
+
data.tar.gz: 534ac74d1c8010ac25330562a6bdb713beacc413d2c681ee72f865727741cfb17e04d9dda2c0ea8ab7bde2f7f2e08f54fa4266e25686ca9d1f38c5309b24b94c
|
data/.rubocop.yml
CHANGED
data/.rubocop_todo.yml
CHANGED
@@ -11,7 +11,6 @@
|
|
11
11
|
# Include: **/*.gemspec
|
12
12
|
Gemspec/RequiredRubyVersion:
|
13
13
|
Exclude:
|
14
|
-
- 'foreman-tasks-core.gemspec'
|
15
14
|
- 'foreman-tasks.gemspec'
|
16
15
|
|
17
16
|
# Offense count: 1
|
@@ -37,7 +36,6 @@ Naming/MemoizedInstanceVariableName:
|
|
37
36
|
Exclude:
|
38
37
|
- 'app/controllers/foreman_tasks/recurring_logics_controller.rb'
|
39
38
|
- 'app/lib/actions/recurring_action.rb'
|
40
|
-
- 'lib/foreman_tasks_core/otp_manager.rb'
|
41
39
|
|
42
40
|
# Offense count: 11
|
43
41
|
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
data/README.md
CHANGED
@@ -6,7 +6,8 @@ happening/happened in your Foreman instance. A framework for asynchronous tasks
|
|
6
6
|
|
7
7
|
* Website: [TheForeman.org](http://theforeman.org)
|
8
8
|
* ServerFault tag: [Foreman](http://serverfault.com/questions/tagged/foreman)
|
9
|
-
* Issues: [
|
9
|
+
* Issues: [Foreman-tasks Redmine](http://projects.theforeman.org/projects/foreman-tasks)
|
10
|
+
* Manual: [Foreman-tasks Manual](https://www.theforeman.org/plugins/foreman_tasks/0.8/index.html)
|
10
11
|
* Wiki: [Foreman wiki](http://projects.theforeman.org/projects/foreman/wiki/About)
|
11
12
|
* Community and support: #theforeman for general support, #theforeman-dev for development chat in [Freenode](irc.freenode.net)
|
12
13
|
* Mailing lists:
|
@@ -25,6 +26,7 @@ happening/happened in your Foreman instance. A framework for asynchronous tasks
|
|
25
26
|
| >= 1.22 | ~> 0.15.0 |
|
26
27
|
| >= 2.0 | ~> 1.0.0 |
|
27
28
|
| >= 2.1 | ~> 2.0.0 |
|
29
|
+
| >= 2.6 | ~> 5.2.0 |
|
28
30
|
|
29
31
|
Installation
|
30
32
|
------------
|
@@ -154,9 +156,9 @@ rails root directory. See `-h` for more details and options
|
|
154
156
|
Tasks cleanup
|
155
157
|
-------------
|
156
158
|
|
157
|
-
Although
|
158
|
-
tasks can
|
159
|
-
|
159
|
+
Although the history of tasks has an auditing value, some kinds of
|
160
|
+
tasks can rapidly increase. Therefore, there is a mechanism for
|
161
|
+
cleaning up the tasks using a rake command. When running without
|
160
162
|
any arguments, the tasks are deleted based on the default parameters
|
161
163
|
defined in the code.
|
162
164
|
|
@@ -179,7 +181,7 @@ override the default configuration inside the configuration
|
|
179
181
|
```
|
180
182
|
:foreman-tasks:
|
181
183
|
:cleanup:
|
182
|
-
# the period after
|
184
|
+
# the period after which to delete all the tasks (by default, all tasks are not deleted after some period)
|
183
185
|
:after: 365d
|
184
186
|
# per action settings to override the default defined in the actions (cleanup_after method)
|
185
187
|
:actions:
|
@@ -194,7 +196,7 @@ to specify the search criteria for the cleanup manually:
|
|
194
196
|
* `TASK_SEARCH`: scoped search filter (example: 'label =
|
195
197
|
"Actions::Foreman::Host::ImportFacts"')
|
196
198
|
* `AFTER`: delete tasks created after `AFTER` period. Expected format
|
197
|
-
is a number followed by the time unit (`s`, `h`, `m`, `y`), such as
|
199
|
+
is a number followed by the time unit (`s`, `h`, `d`, `m`, `y`), such as
|
198
200
|
`10d` for 10 days (applicable only when the `TASK_SEARCH` option is
|
199
201
|
specified)
|
200
202
|
* `STATES`: comma separated list of task states to touch with the
|
@@ -8,6 +8,7 @@ function trigger_form_selector_binds(form_name, form_object_name) {
|
|
8
8
|
form.find('fieldset.trigger_mode_form#trigger_mode_' + type).hide();
|
9
9
|
});
|
10
10
|
form.find('fieldset.trigger_mode_form#trigger_mode_' + $(this).val()).show();
|
11
|
+
disable_hidden_start_field($(this).val(), form)
|
11
12
|
});
|
12
13
|
|
13
14
|
input_type_selector.on('change', function () {
|
@@ -39,3 +40,9 @@ function trigger_form_selector_binds(form_name, form_object_name) {
|
|
39
40
|
};
|
40
41
|
});
|
41
42
|
};
|
43
|
+
|
44
|
+
function disable_hidden_start_field(clicked, form) {
|
45
|
+
["future", "recurring"].forEach(function(type) {
|
46
|
+
form.find('.trigger_mode_form#trigger_mode_' + type + ' #triggering_start_at_raw').prop('disabled', type !== clicked);
|
47
|
+
});
|
48
|
+
};
|
@@ -320,7 +320,7 @@ module ForemanTasks
|
|
320
320
|
end
|
321
321
|
|
322
322
|
def find_task
|
323
|
-
@task = resource_scope.find(params[:id])
|
323
|
+
@task = resource_scope.with_duration.find(params[:id])
|
324
324
|
end
|
325
325
|
|
326
326
|
def resource_scope(_options = {})
|
@@ -330,7 +330,7 @@ module ForemanTasks
|
|
330
330
|
end
|
331
331
|
|
332
332
|
def resource_scope_for_index(*args)
|
333
|
-
super.
|
333
|
+
super.with_duration.distinct
|
334
334
|
end
|
335
335
|
|
336
336
|
def controller_permission
|
@@ -99,8 +99,8 @@ module ForemanTasks
|
|
99
99
|
private
|
100
100
|
|
101
101
|
def respond_with_tasks(scope)
|
102
|
-
@tasks = filter(scope, paginate: false)
|
103
|
-
csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'User'])
|
102
|
+
@tasks = filter(scope, paginate: false).with_duration
|
103
|
+
csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :duration, :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'Duration', 'User'])
|
104
104
|
end
|
105
105
|
|
106
106
|
def controller_permission
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Mutations
|
2
|
+
module RecurringLogics
|
3
|
+
class Cancel < BaseMutation
|
4
|
+
graphql_name 'CancelRecurringLogic'
|
5
|
+
description 'Cancels recurring logic and all its active tasks'
|
6
|
+
resource_class ::ForemanTasks::RecurringLogic
|
7
|
+
|
8
|
+
argument :id, ID, required: true
|
9
|
+
|
10
|
+
field :errors, [Types::AttributeError], null: false
|
11
|
+
field :recurring_logic, Types::RecurringLogic, null: true
|
12
|
+
|
13
|
+
def resolve(id:)
|
14
|
+
recurring_logic = load_object_by(id: id)
|
15
|
+
authorize!(recurring_logic, :edit)
|
16
|
+
task_errors = []
|
17
|
+
begin
|
18
|
+
recurring_logic.cancel
|
19
|
+
rescue => e
|
20
|
+
task_errors = [{ path: ['tasks'], message: "There has been an error when canceling one of the tasks: #{e}" }]
|
21
|
+
end
|
22
|
+
errors = recurring_logic.errors.any? ? map_errors_to_path(recurring_logic) : []
|
23
|
+
{ recurring_logic: recurring_logic, errors: (errors + task_errors) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Types
|
2
|
+
class RecurringLogic < Types::BaseObject
|
3
|
+
description 'A Recurring Logic'
|
4
|
+
model_class ::ForemanTasks::RecurringLogic
|
5
|
+
|
6
|
+
include ::Types::Concerns::MetaField
|
7
|
+
|
8
|
+
global_id_field :id
|
9
|
+
field :cron_line, String
|
10
|
+
field :end_time, GraphQL::Types::ISO8601DateTime
|
11
|
+
field :max_iteration, Integer
|
12
|
+
field :iteration, Integer
|
13
|
+
field :state, String
|
14
|
+
field :purpose, String
|
15
|
+
belongs_to :triggering, Types::Triggering
|
16
|
+
|
17
|
+
def self.graphql_definition
|
18
|
+
super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::RecurringLogic') }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Types
|
2
|
+
class Task < Types::BaseObject
|
3
|
+
description 'A Task'
|
4
|
+
model_class ::ForemanTasks::Task
|
5
|
+
|
6
|
+
global_id_field :id
|
7
|
+
field :type, String
|
8
|
+
field :label, String
|
9
|
+
field :started_at, GraphQL::Types::ISO8601DateTime
|
10
|
+
field :ended_at, GraphQL::Types::ISO8601DateTime
|
11
|
+
field :state, String
|
12
|
+
field :result, String
|
13
|
+
field :external_id, String
|
14
|
+
field :parent_task_id, String
|
15
|
+
field :start_at, GraphQL::Types::ISO8601DateTime
|
16
|
+
field :start_before, GraphQL::Types::ISO8601DateTime
|
17
|
+
field :action, String
|
18
|
+
field :user_id, Integer
|
19
|
+
field :state_updated_at, GraphQL::Types::ISO8601DateTime
|
20
|
+
|
21
|
+
def self.graphql_definition
|
22
|
+
super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::Task') }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Types
|
2
|
+
class Triggering < Types::BaseObject
|
3
|
+
description 'A Task Triggering'
|
4
|
+
model_class ::ForemanTasks::Triggering
|
5
|
+
|
6
|
+
global_id_field :id
|
7
|
+
field :mode, String
|
8
|
+
field :start_at, GraphQL::Types::ISO8601DateTime
|
9
|
+
field :start_before, GraphQL::Types::ISO8601DateTime
|
10
|
+
field :recurring_logic, Types::RecurringLogic
|
11
|
+
|
12
|
+
def self.graphql_definition
|
13
|
+
super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::Triggering') }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -141,7 +141,10 @@ module ForemanTasks
|
|
141
141
|
weekly_fieldset(f, triggering),
|
142
142
|
time_picker_fieldset(f, triggering),
|
143
143
|
]
|
144
|
-
|
144
|
+
tags << text_f(f, :start_at_raw, :label => _('Start at'), :placeholder => 'YYYY-mm-dd HH:MM')
|
145
|
+
tags << text_f(f, :purpose,
|
146
|
+
:label => _('Purpose'),
|
147
|
+
:label_help => N_('A special label for tracking a recurring job. There can be only one active job with a given purpose at a time.'))
|
145
148
|
content_tag(:fieldset, nil, :id => 'trigger_mode_recurring', :class => "trigger_mode_form #{'hidden' unless triggering.recurring?}") do
|
146
149
|
tags.join.html_safe
|
147
150
|
end
|
@@ -8,7 +8,7 @@ module Actions
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def continuous_output
|
11
|
-
continuous_output = ::
|
11
|
+
continuous_output = ::ForemanTasks::ContinuousOutput.new
|
12
12
|
continuous_output_providers.each do |continous_output_provider|
|
13
13
|
continous_output_provider.fill_continuous_output(continuous_output)
|
14
14
|
end
|
@@ -29,10 +29,10 @@ module Actions
|
|
29
29
|
default_connection_options.each do |key, value|
|
30
30
|
options[:connection_options][key] = value unless options[:connection_options].key?(key)
|
31
31
|
end
|
32
|
-
plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s
|
32
|
+
plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s))
|
33
33
|
# Just saving the RemoteTask is enough when using batch triggering
|
34
34
|
# It will be picked up by the ProxyBatchTriggering middleware
|
35
|
-
if input[:use_batch_triggering] &&
|
35
|
+
if input[:use_batch_triggering] && input.dig(:connection_options, :proxy_batch_triggering)
|
36
36
|
prepare_remote_task.save!
|
37
37
|
end
|
38
38
|
end
|
@@ -193,11 +193,6 @@ module Actions
|
|
193
193
|
:proxy_batch_triggering => Setting['foreman_tasks_proxy_batch_trigger'] || false }
|
194
194
|
end
|
195
195
|
|
196
|
-
def with_batch_triggering?(proxy_version)
|
197
|
-
((proxy_version[:major] == 1 && proxy_version[:minor] > 20) || proxy_version[:major] > 1) &&
|
198
|
-
input.fetch(:connection_options, {}).fetch(:proxy_batch_triggering, false)
|
199
|
-
end
|
200
|
-
|
201
196
|
def clean_remote_task(*_args)
|
202
197
|
remote_task.destroy! if remote_task
|
203
198
|
end
|
@@ -222,11 +217,6 @@ module Actions
|
|
222
217
|
.try(:fetch, 'output', {}) || {}
|
223
218
|
end
|
224
219
|
|
225
|
-
def proxy_version(proxy)
|
226
|
-
match = proxy.statuses[:version].version['version'].match(/(\d+)\.(\d+)\.(\d+)/)
|
227
|
-
{ :major => match[1].to_i, :minor => match[2].to_i, :patch => match[3].to_i }
|
228
|
-
end
|
229
|
-
|
230
220
|
def failed_proxy_tasks
|
231
221
|
metadata[:failed_proxy_tasks] ||= []
|
232
222
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Actions
|
2
|
+
# This action plans proxy tasks in batches.
|
3
|
+
# It needs to be manually notified about the next batch being available by sending a TriggerNextBatch event.
|
4
|
+
#
|
5
|
+
# The ProxyAction needs to be planned with `:use_batch_triggering => true` to activate the feature
|
6
|
+
class TriggerProxyBatch < Base
|
7
|
+
TriggerNextBatch = Algebrick.type do
|
8
|
+
fields! batches: Integer
|
9
|
+
end
|
10
|
+
TriggerLastBatch = Algebrick.atom
|
11
|
+
|
12
|
+
def run(event = nil)
|
13
|
+
case event
|
14
|
+
when nil
|
15
|
+
if output[:planned_count]
|
16
|
+
check_finish
|
17
|
+
else
|
18
|
+
init_counts and suspend
|
19
|
+
end
|
20
|
+
when TriggerNextBatch
|
21
|
+
trigger_remote_tasks_batches(event.batches) and suspend
|
22
|
+
when TriggerLastBatch
|
23
|
+
trigger_remote_tasks_batch and on_finish
|
24
|
+
when ::Dynflow::Action::Skip
|
25
|
+
# do nothing
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def trigger_remote_tasks_batches(amount = 1)
|
30
|
+
amount.times { trigger_remote_tasks_batch }
|
31
|
+
end
|
32
|
+
|
33
|
+
def trigger_remote_tasks_batch
|
34
|
+
# Find the tasks in batches, order them by proxy_url so we get all tasks
|
35
|
+
# to a certain proxy "close to each other"
|
36
|
+
batch = remote_tasks.pending.order(:proxy_url, :id).first(batch_size)
|
37
|
+
# Group the tasks by operation, in theory there should be only one operation
|
38
|
+
batch.group_by(&:operation).each do |operation, group|
|
39
|
+
ForemanTasks::RemoteTask.batch_trigger(operation, group)
|
40
|
+
end
|
41
|
+
output[:planned_count] += batch.size
|
42
|
+
rescue => e
|
43
|
+
action_logger.warn "Could not trigger task on the smart proxy: #{e.message}"
|
44
|
+
batch.each { |remote_task| remote_task.update_from_batch_trigger({}) }
|
45
|
+
output[:failed_count] += batch.size
|
46
|
+
end
|
47
|
+
|
48
|
+
def init_counts
|
49
|
+
output[:planned_count] = 0
|
50
|
+
output[:failed_count] = 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def check_finish
|
54
|
+
if output[:planned_count] + output[:failed_count] + batch_size >= input[:total_count]
|
55
|
+
trigger_remote_tasks_batch and on_finish
|
56
|
+
else
|
57
|
+
suspend
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def done?
|
62
|
+
output[:planned_count] + output[:failed_count] >= input[:total_count]
|
63
|
+
end
|
64
|
+
|
65
|
+
def remote_tasks
|
66
|
+
task.remote_sub_tasks
|
67
|
+
end
|
68
|
+
|
69
|
+
def on_finish
|
70
|
+
# nothing for now
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def batch_size
|
76
|
+
input[:batch_size] || Setting['foreman_tasks_proxy_batch_size']
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -4,6 +4,8 @@ module ForemanTasks
|
|
4
4
|
class RecurringLogic < ApplicationRecord
|
5
5
|
include Authorizable
|
6
6
|
|
7
|
+
graphql_type '::Types::RecurringLogic'
|
8
|
+
|
7
9
|
belongs_to :task_group
|
8
10
|
belongs_to :triggering
|
9
11
|
|
@@ -15,6 +17,9 @@ module ForemanTasks
|
|
15
17
|
scoped_search :on => :iteration, :complete_value => false
|
16
18
|
scoped_search :on => :cron_line, :complete_value => true
|
17
19
|
scoped_search :on => :state, :complete_value => true
|
20
|
+
scoped_search :on => :purpose, :complete_value => true
|
21
|
+
|
22
|
+
validate :valid_purpose
|
18
23
|
|
19
24
|
before_create do
|
20
25
|
task_group.save
|
@@ -167,6 +172,7 @@ module ForemanTasks
|
|
167
172
|
::ForemanTasks::RecurringLogic.new_from_cronline(cronline).tap do |manager|
|
168
173
|
manager.end_time = triggering.end_time if triggering.end_time_limited.present?
|
169
174
|
manager.max_iteration = triggering.max_iteration if triggering.max_iteration.present?
|
175
|
+
manager.purpose = triggering.purpose if triggering.purpose.present?
|
170
176
|
manager.triggering = triggering
|
171
177
|
end
|
172
178
|
end
|
@@ -187,5 +193,9 @@ module ForemanTasks
|
|
187
193
|
end
|
188
194
|
hash.select { |key, _| allowed_keys.include? key }
|
189
195
|
end
|
196
|
+
|
197
|
+
def valid_purpose?
|
198
|
+
!(purpose.present? && self.class.where(:purpose => purpose, :state => %w[active disabled]).any?)
|
199
|
+
end
|
190
200
|
end
|
191
201
|
end
|
@@ -16,7 +16,7 @@ module ForemanTasks
|
|
16
16
|
# Triggers a task on the proxy "the old way"
|
17
17
|
def trigger(proxy_action_name, input)
|
18
18
|
response = begin
|
19
|
-
proxy.
|
19
|
+
proxy.launch_tasks('single', :action_class => proxy_action_name, :action_input => input)
|
20
20
|
rescue RestClient::Exception => e
|
21
21
|
logger.warn "Could not trigger task on the smart proxy: #{e.message}"
|
22
22
|
{}
|
@@ -31,28 +31,12 @@ module ForemanTasks
|
|
31
31
|
acc.merge(remote_task.execution_plan_id => { :action_input => remote_task.proxy_input,
|
32
32
|
:action_class => remote_task.proxy_action_name })
|
33
33
|
end
|
34
|
-
|
34
|
+
results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
|
35
|
+
remote_tasks.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
|
35
36
|
end
|
36
37
|
remote_tasks
|
37
38
|
end
|
38
39
|
|
39
|
-
# Attempt to trigger the tasks using the new API and fall back to the old one
|
40
|
-
# if it fails
|
41
|
-
def self.safe_batch_trigger(operation, remote_tasks, input_hash)
|
42
|
-
results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
|
43
|
-
remote_tasks.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
|
44
|
-
rescue RestClient::NotFound
|
45
|
-
fallback_batch_trigger remote_tasks, input_hash
|
46
|
-
end
|
47
|
-
|
48
|
-
# Trigger the tasks one-by-one using the old API
|
49
|
-
def self.fallback_batch_trigger(remote_tasks, input_hash)
|
50
|
-
remote_tasks.each do |remote_task|
|
51
|
-
task_data = input_hash[remote_task.execution_plan_id]
|
52
|
-
remote_task.trigger(task_data[:action_class], task_data[:action_input])
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
40
|
def update_from_batch_trigger(data)
|
57
41
|
if data['result'] == 'success'
|
58
42
|
self.remote_task_id = data['task_id']
|
@@ -5,6 +5,8 @@ module ForemanTasks
|
|
5
5
|
include Authorizable
|
6
6
|
extend Search
|
7
7
|
|
8
|
+
graphql_type '::Types::Task'
|
9
|
+
|
8
10
|
def check_permissions_after_save
|
9
11
|
# there's no create_tasks permission, tasks are created as a result of internal actions, in such case we
|
10
12
|
# don't do authorization, that should have been performed on wrapping action level
|
@@ -76,6 +78,7 @@ module ForemanTasks
|
|
76
78
|
:"foreman_tasks_links.resource_type" => resource.class.name)
|
77
79
|
end)
|
78
80
|
scope :for_action_types, (->(action_types) { where('foreman_tasks_tasks.label IN (?)', Array(action_types)) })
|
81
|
+
scope :with_duration, -> { select("foreman_tasks_tasks.*, coalesce(ended_at, current_timestamp) - coalesce(coalesce(started_at, ended_at), current_timestamp) as duration") }
|
79
82
|
|
80
83
|
apipie :class, "A class representing #{model_name.human} object" do
|
81
84
|
name 'Task'
|
@@ -255,6 +258,32 @@ module ForemanTasks
|
|
255
258
|
main_action.continuous_output.raw_outputs
|
256
259
|
end
|
257
260
|
|
261
|
+
def self.latest_tasks_by_resource_ids(label, resource_type, resource_ids)
|
262
|
+
tasks = arel_table
|
263
|
+
links = ForemanTasks::Link.arel_table
|
264
|
+
started_at = tasks[:started_at]
|
265
|
+
resource_id = links[:resource_id]
|
266
|
+
|
267
|
+
base_combined_table = tasks
|
268
|
+
.join(links).on(tasks[:id].eq(links[:task_id]))
|
269
|
+
.where(tasks[:label].eq(label)
|
270
|
+
.and(links[:resource_type].eq(resource_type))
|
271
|
+
.and(links[:resource_id].in(resource_ids)))
|
272
|
+
|
273
|
+
grouped = base_combined_table.project(
|
274
|
+
started_at.maximum.as('started_at_max'),
|
275
|
+
resource_id
|
276
|
+
).group(resource_id).order(resource_id).as('grouped')
|
277
|
+
|
278
|
+
max_per_resource_id = tasks
|
279
|
+
.join(links).on(tasks[:id].eq(links[:task_id]))
|
280
|
+
.join(grouped).on(grouped[:started_at_max].eq(started_at).and(grouped[:resource_id].eq(resource_id)))
|
281
|
+
.distinct
|
282
|
+
.project(tasks[Arel.star], grouped[:resource_id])
|
283
|
+
|
284
|
+
find_by_sql(max_per_resource_id.to_sql).index_by(&:resource_id)
|
285
|
+
end
|
286
|
+
|
258
287
|
protected
|
259
288
|
|
260
289
|
def generate_id
|
@@ -2,9 +2,11 @@ module ForemanTasks
|
|
2
2
|
class Triggering < ApplicationRecord
|
3
3
|
PARAMS = [:start_at_raw, :start_before_raw, :max_iteration, :input_type,
|
4
4
|
:cronline, :days, :days_of_week, :time, :end_time_limited,
|
5
|
-
:end_time].freeze
|
5
|
+
:end_time, :purpose].freeze
|
6
6
|
attr_accessor(*PARAMS)
|
7
7
|
|
8
|
+
graphql_type '::Types::Triggering'
|
9
|
+
|
8
10
|
before_save do
|
9
11
|
if future?
|
10
12
|
parse_start_at!
|
@@ -28,7 +30,7 @@ module ForemanTasks
|
|
28
30
|
validates :input_type, :if => :recurring?,
|
29
31
|
:inclusion => { :in => ALLOWED_INPUT_TYPES,
|
30
32
|
:message => _('%{value} is not allowed input type') }
|
31
|
-
validates :start_at_raw, format: { :with => TIME_REGEXP, :if =>
|
33
|
+
validates :start_at_raw, format: { :with => TIME_REGEXP, :if => ->(triggering) { triggering.future? || (triggering.recurring? && triggering.start_at_raw) },
|
32
34
|
:message => _('%{value} is wrong format') }
|
33
35
|
validates :start_before_raw, format: { :with => TIME_REGEXP, :if => :future?,
|
34
36
|
:message => _('%{value} is wrong format'), :allow_blank => true }
|
@@ -68,7 +70,7 @@ module ForemanTasks
|
|
68
70
|
delay_options,
|
69
71
|
*args
|
70
72
|
when :recurring
|
71
|
-
recurring_logic.
|
73
|
+
recurring_logic.start_after(action, delay_options[:start_at] || Time.zone.now, *args)
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
@@ -92,7 +94,13 @@ module ForemanTasks
|
|
92
94
|
end
|
93
95
|
|
94
96
|
def parse_start_at!
|
95
|
-
self.start_at ||= Time.zone.parse(start_at_raw)
|
97
|
+
self.start_at ||= Time.zone.parse(start_at_raw) if start_at_raw.present?
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_start_at
|
101
|
+
self.start_at ||= Time.zone.parse(start_at_raw) if start_at_raw.present?
|
102
|
+
rescue ArgumentError
|
103
|
+
errors.add(:start_at, _('is not a valid format'))
|
96
104
|
end
|
97
105
|
|
98
106
|
def parse_start_before!
|
@@ -102,7 +110,9 @@ module ForemanTasks
|
|
102
110
|
private
|
103
111
|
|
104
112
|
def can_start_recurring
|
113
|
+
parse_start_at
|
105
114
|
errors.add(:input_type, _('No task could be started')) unless recurring_logic.valid?
|
115
|
+
errors.add(:purpose, _('Active or disabled recurring logic with purpose %s already exists') % recurring_logic.purpose) unless recurring_logic.valid_purpose?
|
106
116
|
errors.add(:cronline, _('%s is not valid format of cron line') % cronline) unless recurring_logic.valid_cronline?
|
107
117
|
end
|
108
118
|
|
@@ -3,6 +3,6 @@ object @task if @task
|
|
3
3
|
extends 'api/v2/layouts/permissions'
|
4
4
|
|
5
5
|
attributes :id, :label, :pending, :action
|
6
|
-
attributes :username, :started_at, :ended_at, :state, :result, :progress
|
6
|
+
attributes :username, :started_at, :ended_at, :duration, :state, :result, :progress
|
7
7
|
attributes :input, :output, :humanized, :cli_example, :start_at
|
8
8
|
node(:available_actions) { |t| { cancellable: t.execution_plan&.cancellable?, resumable: t.resumable? } }
|
@@ -6,7 +6,6 @@
|
|
6
6
|
<% end %>
|
7
7
|
|
8
8
|
<% content_for(:content) do %>
|
9
|
-
<%= notifications %>
|
10
9
|
<div id="organization-id" data-id="<%= Organization.current.id if Organization.current %>" ></div>
|
11
10
|
<div id="user-id" data-id="<%= User.current.id if User.current %>" ></div>
|
12
11
|
<%= react_component('ForemanTasks') %>
|
@@ -8,9 +8,9 @@
|
|
8
8
|
<% if authorized_for(:permission => :edit_recurring_logics, :auth_object => @recurring_logics) %>
|
9
9
|
<% title_actions link_to(_('Clear Cancelled'),
|
10
10
|
clear_cancelled_foreman_tasks_recurring_logics_path,
|
11
|
-
class: ['btn', 'btn-sm', 'btn-danger'],
|
11
|
+
class: ['btn', 'btn-sm', 'btn-danger'],
|
12
12
|
:'data-toggle' => "modal",
|
13
|
-
:'data-target' => "#clear_modal")
|
13
|
+
:'data-target' => "#clear_modal")
|
14
14
|
%>
|
15
15
|
|
16
16
|
<div class="modal fade" id="clear_modal" tabindex="-1" role="dialog" aria-labelledby="Deploy" aria-hidden="true">
|
@@ -47,6 +47,7 @@
|
|
47
47
|
<th><%= N_("Iteration limit") %></th>
|
48
48
|
<th><%= N_("Repeat until") %></th>
|
49
49
|
<th><%= N_("State") %></th>
|
50
|
+
<th><%= N_("Purpose") %></th>
|
50
51
|
<th/>
|
51
52
|
</thead>
|
52
53
|
<% @recurring_logics.each do |recurring_logic| %>
|
@@ -61,6 +62,7 @@
|
|
61
62
|
<td><%= format_recurring_logic_limit recurring_logic.max_iteration %></td>
|
62
63
|
<td><%= format_recurring_logic_limit recurring_logic.end_time.try(:in_time_zone) %></td>
|
63
64
|
<td><%= recurring_logic_state(recurring_logic) %></td>
|
65
|
+
<td><%= recurring_logic.purpose %></td>
|
64
66
|
<td><%= recurring_logic_action_buttons(recurring_logic) %></td>
|
65
67
|
</tr>
|
66
68
|
<% end %>
|
@@ -36,4 +36,12 @@
|
|
36
36
|
<th><%= N_("State") %></th>
|
37
37
|
<td><%= recurring_logic_state(recurring_logic) %></td>
|
38
38
|
</tr>
|
39
|
+
<tr>
|
40
|
+
<th><%= N_("Purpose") %></th>
|
41
|
+
<td><%= recurring_logic.purpose %></td>
|
42
|
+
</tr>
|
43
|
+
<tr>
|
44
|
+
<th><%= N_("Task count") %></th>
|
45
|
+
<td><%= link_to(task_group.tasks.count, foreman_tasks_tasks_url(:search => "task_group.id = #{task_group.id}")) %></td>
|
46
|
+
</tr>
|
39
47
|
</table>
|