foreman-tasks 0.13.0 → 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.rubocop_todo.yml +107 -48
- data/README.md +2 -2
- data/app/controllers/foreman_tasks/concerns/hosts_controller_extension.rb +1 -7
- data/app/lib/actions/helpers/with_continuous_output.rb +6 -1
- data/app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb +73 -0
- data/app/lib/actions/proxy_action.rb +47 -32
- data/app/lib/proxy_api/foreman_dynflow/dynflow_proxy.rb +5 -0
- data/app/models/foreman_tasks/remote_task.rb +12 -0
- data/app/models/foreman_tasks/task.rb +6 -1
- data/app/models/foreman_tasks/task/dynflow_task.rb +54 -10
- data/app/models/setting/foreman_tasks.rb +1 -2
- data/db/migrate/20180207150921_add_remote_tasks.foreman_tasks.rb +16 -0
- data/lib/foreman_tasks/cleaner.rb +5 -0
- data/lib/foreman_tasks/engine.rb +1 -1
- data/lib/foreman_tasks/version.rb +1 -1
- data/test/controllers/api/tasks_controller_test.rb +27 -0
- data/test/unit/actions/proxy_action_test.rb +8 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 464f9df51dc38a0d63e3e573e462f9f57bc5d091f0bdc0090327229d6b8ecffb
|
4
|
+
data.tar.gz: 5d2f0b357ce12025975c5cd62d2d31637d5cbcff20349e3af89edb213e8c553a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df01ce6f12aec5edd07cfe1ba355d86ad130df011be72c559dc100f92e2dabcfae356aa636bc73454393acd52aa02d569fe3addefe6d4940aa2d9ec2289a0a6d
|
7
|
+
data.tar.gz: 2e6da699d36a27a4f5727475e998ee52e07861e0a2eecd8a879a9b17f6b27121a225edb34f66338939c11e9110bb3fd53f0edc090b113fedb983db5bdd8798f6
|
data/.rubocop_todo.yml
CHANGED
@@ -1,74 +1,147 @@
|
|
1
1
|
# This configuration was generated by
|
2
2
|
# `rubocop --auto-gen-config`
|
3
|
-
# on
|
3
|
+
# on 2018-04-02 18:15:37 +0200 using RuboCop version 0.54.0.
|
4
4
|
# The point is for the user to remove these configuration records
|
5
5
|
# one by one as the offenses are removed from the code base.
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
7
7
|
# versions of RuboCop, may require this file to be generated again.
|
8
8
|
|
9
|
-
|
9
|
+
# Offense count: 3
|
10
|
+
# Cop supports --auto-correct.
|
11
|
+
# Configuration parameters: Include, TreatCommentsAsGroupSeparators.
|
12
|
+
# Include: **/*.gemspec
|
13
|
+
Gemspec/OrderedDependencies:
|
10
14
|
Exclude:
|
11
|
-
- '
|
15
|
+
- 'foreman-tasks.gemspec'
|
16
|
+
|
17
|
+
# Offense count: 1
|
18
|
+
# Cop supports --auto-correct.
|
19
|
+
# Configuration parameters: EnforcedStyle.
|
20
|
+
# SupportedStyles: final_newline, final_blank_line
|
21
|
+
Layout/TrailingBlankLines:
|
22
|
+
Exclude:
|
23
|
+
- 'locale/action_names.rb'
|
12
24
|
|
13
|
-
|
25
|
+
# Offense count: 1
|
26
|
+
Lint/EmptyWhen:
|
14
27
|
Exclude:
|
15
|
-
- '
|
28
|
+
- 'app/lib/actions/proxy_action.rb'
|
16
29
|
|
30
|
+
# Offense count: 2
|
17
31
|
Lint/UselessAssignment:
|
18
32
|
Exclude:
|
19
33
|
- 'lib/foreman_tasks/tasks/export_tasks.rake'
|
20
34
|
|
35
|
+
# Offense count: 32
|
21
36
|
Metrics/AbcSize:
|
22
37
|
Max: 41
|
23
38
|
|
24
|
-
#
|
39
|
+
# Offense count: 2
|
40
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
25
41
|
Metrics/BlockLength:
|
26
|
-
Max:
|
42
|
+
Max: 32
|
27
43
|
|
44
|
+
# Offense count: 13
|
28
45
|
# Configuration parameters: CountComments.
|
29
46
|
Metrics/ClassLength:
|
30
47
|
Max: 230
|
31
48
|
|
49
|
+
# Offense count: 9
|
32
50
|
Metrics/CyclomaticComplexity:
|
33
|
-
Max:
|
51
|
+
Max: 9
|
34
52
|
|
53
|
+
# Offense count: 482
|
35
54
|
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
36
55
|
# URISchemes: http, https
|
37
56
|
Metrics/LineLength:
|
38
|
-
Max:
|
57
|
+
Max: 211
|
39
58
|
|
59
|
+
# Offense count: 54
|
40
60
|
# Configuration parameters: CountComments.
|
41
61
|
Metrics/MethodLength:
|
42
|
-
Max:
|
62
|
+
Max: 29
|
43
63
|
|
64
|
+
# Offense count: 2
|
44
65
|
# Configuration parameters: CountComments.
|
45
66
|
Metrics/ModuleLength:
|
46
|
-
Max:
|
67
|
+
Max: 167
|
47
68
|
|
69
|
+
# Offense count: 1
|
48
70
|
# Configuration parameters: CountKeywordArgs.
|
49
71
|
Metrics/ParameterLists:
|
50
72
|
Max: 6
|
51
73
|
|
74
|
+
# Offense count: 4
|
52
75
|
Metrics/PerceivedComplexity:
|
53
|
-
Max:
|
76
|
+
Max: 9
|
77
|
+
|
78
|
+
# Offense count: 3
|
79
|
+
Naming/MemoizedInstanceVariableName:
|
80
|
+
Exclude:
|
81
|
+
- 'app/controllers/foreman_tasks/recurring_logics_controller.rb'
|
82
|
+
- 'app/lib/actions/recurring_action.rb'
|
83
|
+
- 'lib/foreman_tasks_core/otp_manager.rb'
|
84
|
+
|
85
|
+
# Offense count: 1
|
86
|
+
# Configuration parameters: NamePrefix, NamePrefixBlacklist, NameWhitelist, MethodDefinitionMacros.
|
87
|
+
# NamePrefix: is_, has_, have_
|
88
|
+
# NamePrefixBlacklist: is_, has_, have_
|
89
|
+
# NameWhitelist: is_a?
|
90
|
+
# MethodDefinitionMacros: define_method, define_singleton_method
|
91
|
+
Naming/PredicateName:
|
92
|
+
Exclude:
|
93
|
+
- 'spec/**/*'
|
94
|
+
- 'app/models/foreman_tasks/task/status_explicator.rb'
|
95
|
+
|
96
|
+
# Offense count: 12
|
97
|
+
# Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
|
98
|
+
# AllowedNames: io, id, to
|
99
|
+
Naming/UncommunicativeMethodParamName:
|
100
|
+
Exclude:
|
101
|
+
- 'app/helpers/foreman_tasks/foreman_tasks_helper.rb'
|
102
|
+
- 'app/models/foreman_tasks/recurring_logic.rb'
|
54
103
|
|
104
|
+
# Offense count: 3
|
105
|
+
# Cop supports --auto-correct.
|
106
|
+
Rails/ActiveRecordAliases:
|
107
|
+
Exclude:
|
108
|
+
- 'app/models/foreman_tasks/task/dynflow_task.rb'
|
109
|
+
- 'test/factories/task_factory.rb'
|
110
|
+
|
111
|
+
# Offense count: 6
|
55
112
|
# Configuration parameters: Include.
|
56
|
-
# Include:
|
57
|
-
Rails/
|
113
|
+
# Include: db/migrate/*.rb
|
114
|
+
Rails/CreateTableWithTimestamps:
|
115
|
+
Exclude:
|
116
|
+
- 'db/migrate/20131205204140_create_foreman_tasks.rb'
|
117
|
+
- 'db/migrate/20131209122644_create_foreman_tasks_locks.rb'
|
118
|
+
- 'db/migrate/20150907124936_create_recurring_logic.rb'
|
119
|
+
- 'db/migrate/20150907131503_create_task_groups.rb'
|
120
|
+
- 'db/migrate/20151112152108_create_triggerings.rb'
|
121
|
+
|
122
|
+
# Offense count: 1
|
123
|
+
# Cop supports --auto-correct.
|
124
|
+
# Configuration parameters: EnforcedStyle.
|
125
|
+
# SupportedStyles: numeric, symbolic
|
126
|
+
Rails/HttpStatus:
|
58
127
|
Exclude:
|
59
|
-
- '
|
128
|
+
- 'app/controllers/foreman_tasks/concerns/hosts_controller_extension.rb'
|
60
129
|
|
130
|
+
# Offense count: 1
|
61
131
|
# Configuration parameters: Include.
|
62
132
|
# Include: app/**/*.rb, config/**/*.rb, db/**/*.rb, lib/**/*.rb
|
63
133
|
Rails/Output:
|
64
134
|
Exclude:
|
65
135
|
- 'lib/foreman_tasks/cleaner.rb'
|
66
136
|
|
137
|
+
# Offense count: 5
|
67
138
|
Rails/OutputSafety:
|
68
139
|
Exclude:
|
69
140
|
- 'app/helpers/foreman_tasks/foreman_tasks_helper.rb'
|
70
141
|
|
71
|
-
#
|
142
|
+
# Offense count: 11
|
143
|
+
# Cop supports --auto-correct.
|
144
|
+
# Configuration parameters: AutoCorrect, EnforcedStyle.
|
72
145
|
# SupportedStyles: nested, compact
|
73
146
|
Style/ClassAndModuleChildren:
|
74
147
|
Exclude:
|
@@ -83,51 +156,37 @@ Style/ClassAndModuleChildren:
|
|
83
156
|
- 'lib/foreman_tasks/dynflow/persistence.rb'
|
84
157
|
- 'test/controllers/api/recurring_logics_controller_test.rb'
|
85
158
|
- 'test/controllers/api/tasks_controller_test.rb'
|
86
|
-
- 'test/unit/actions/action_with_sub_plans_test.rb'
|
87
159
|
|
160
|
+
# Offense count: 2
|
88
161
|
Style/DoubleNegation:
|
89
162
|
Exclude:
|
90
163
|
- 'app/models/foreman_tasks/lock.rb'
|
91
164
|
- 'app/models/foreman_tasks/recurring_logic.rb'
|
92
165
|
|
93
|
-
#
|
94
|
-
|
166
|
+
# Offense count: 2
|
167
|
+
# Cop supports --auto-correct.
|
168
|
+
Style/Encoding:
|
95
169
|
Exclude:
|
96
|
-
- '
|
97
|
-
- '
|
98
|
-
- 'db/seeds.d/61-foreman_tasks_bookmarks.rb'
|
99
|
-
- 'lib/foreman-tasks.rb'
|
170
|
+
- 'foreman-tasks-core.gemspec'
|
171
|
+
- 'foreman-tasks.gemspec'
|
100
172
|
|
101
|
-
#
|
102
|
-
#
|
103
|
-
Style/
|
173
|
+
# Offense count: 6
|
174
|
+
# Cop supports --auto-correct.
|
175
|
+
Style/ExpandPathArguments:
|
104
176
|
Exclude:
|
105
|
-
- '
|
106
|
-
- '
|
177
|
+
- 'foreman-tasks-core.gemspec'
|
178
|
+
- 'foreman-tasks.gemspec'
|
179
|
+
- 'lib/foreman_tasks/engine.rb'
|
180
|
+
- 'script/rails'
|
107
181
|
|
182
|
+
# Offense count: 32
|
108
183
|
# Configuration parameters: MinBodyLength.
|
109
184
|
Style/GuardClause:
|
110
185
|
Enabled: false
|
111
186
|
|
112
|
-
#
|
113
|
-
# NamePrefix: is_, has_, have_
|
114
|
-
# NamePrefixBlacklist: is_, has_, have_
|
115
|
-
# NameWhitelist: is_a?
|
116
|
-
Naming/PredicateName:
|
117
|
-
Exclude:
|
118
|
-
- 'spec/**/*'
|
119
|
-
- 'app/models/foreman_tasks/task/status_explicator.rb'
|
120
|
-
|
187
|
+
# Offense count: 1
|
121
188
|
# Cop supports --auto-correct.
|
122
|
-
# Configuration parameters:
|
123
|
-
|
124
|
-
Style/RegexpLiteral:
|
189
|
+
# Configuration parameters: AllowMultipleReturnValues.
|
190
|
+
Style/RedundantReturn:
|
125
191
|
Exclude:
|
126
|
-
- '
|
127
|
-
|
128
|
-
# Configuration parameters: Methods.
|
129
|
-
# Methods: {"reduce"=>["acc", "elem"]}, {"inject"=>["acc", "elem"]}
|
130
|
-
Style/SingleLineBlockParams:
|
131
|
-
Exclude:
|
132
|
-
- 'app/models/foreman_tasks/concerns/action_subject.rb'
|
133
|
-
- 'app/models/foreman_tasks/lock.rb'
|
192
|
+
- 'app/models/foreman_tasks/concerns/action_triggering.rb'
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ Please see the Foreman manual for appropriate instructions:
|
|
24
24
|
|
25
25
|
Set up the repo as explained in the link above, then run
|
26
26
|
|
27
|
-
# yum install
|
27
|
+
# yum install tfm-rubygem-foreman-tasks
|
28
28
|
|
29
29
|
### Bundle (gem)
|
30
30
|
|
@@ -131,7 +131,7 @@ The executor process needs to be executed before the web server. You
|
|
131
131
|
can run it by:
|
132
132
|
|
133
133
|
```
|
134
|
-
foreman-rake
|
134
|
+
foreman-rake dynflow:executor
|
135
135
|
```
|
136
136
|
|
137
137
|
Also, there is a possibility to run the executor in daemonized mode
|
@@ -1,13 +1,7 @@
|
|
1
1
|
module ForemanTasks
|
2
2
|
module Concerns
|
3
3
|
module HostsControllerExtension
|
4
|
-
|
5
|
-
|
6
|
-
included do
|
7
|
-
alias_method_chain :facts, :dynflow
|
8
|
-
end
|
9
|
-
|
10
|
-
def facts_with_dynflow
|
4
|
+
def facts
|
11
5
|
task = ForemanTasks.async_task(::Actions::Foreman::Host::ImportFacts,
|
12
6
|
detect_host_type,
|
13
7
|
params[:name],
|
@@ -17,7 +17,12 @@ module Actions
|
|
17
17
|
|
18
18
|
def fill_planning_errors_to_continuous_output(continuous_output)
|
19
19
|
execution_plan.errors.map do |e|
|
20
|
-
|
20
|
+
case e.exception
|
21
|
+
when ::Actions::ProxyAction::ProxyActionMissing
|
22
|
+
continuous_output.add_output(e.message, 'debug', task.started_at)
|
23
|
+
else
|
24
|
+
continuous_output.add_exception(_('Failed to initialize'), e.exception, task.started_at)
|
25
|
+
end
|
21
26
|
end
|
22
27
|
end
|
23
28
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Actions
|
2
|
+
module Middleware
|
3
|
+
class WatchDelegatedProxySubTasks < ::Dynflow::Middleware
|
4
|
+
class CheckOnProxyActions; end
|
5
|
+
# Poll the proxy every 10 minutes
|
6
|
+
POLL_INTERVAL = 600
|
7
|
+
BATCH_SIZE = 1000
|
8
|
+
|
9
|
+
def run(event = nil)
|
10
|
+
if event.nil?
|
11
|
+
set_clock
|
12
|
+
elsif event == CheckOnProxyActions
|
13
|
+
check_triggered
|
14
|
+
set_clock
|
15
|
+
action.send(:suspend)
|
16
|
+
end
|
17
|
+
pass event
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def set_clock
|
23
|
+
action.world.clock.ping action.send(:suspended_action),
|
24
|
+
POLL_INTERVAL,
|
25
|
+
CheckOnProxyActions
|
26
|
+
end
|
27
|
+
|
28
|
+
def check_triggered
|
29
|
+
# Sort by proxy_url so there are less requests per proxy
|
30
|
+
source = remote_tasks.triggered.order(:proxy_url, :id)
|
31
|
+
source.find_in_batches(:batch_size => BATCH_SIZE) do |batch|
|
32
|
+
tasks = batch.group_by(&:proxy_url)
|
33
|
+
.map { |(url, tasks)| poll_proxy_tasks(url, tasks) }
|
34
|
+
.flatten
|
35
|
+
process_task_results tasks
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def process_task_results(tasks)
|
40
|
+
missing, present = tasks.partition { |task| task.result.nil? }
|
41
|
+
notify ::Actions::ProxyAction::ProxyActionMissing.new, missing if missing.any?
|
42
|
+
|
43
|
+
stopped = present.select { |task| %w[stopped paused].include? task.result['state'] }
|
44
|
+
notify ::Actions::ProxyAction::ProxyActionStopped.new, stopped if stopped.any?
|
45
|
+
end
|
46
|
+
|
47
|
+
def notify(event, tasks)
|
48
|
+
tasks.each do |task|
|
49
|
+
action.world.event task.execution_plan_id,
|
50
|
+
task.step_id,
|
51
|
+
event
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def remote_tasks
|
56
|
+
action.task.remote_sub_tasks
|
57
|
+
end
|
58
|
+
|
59
|
+
def poll_proxy_tasks(url, tasks)
|
60
|
+
proxy = ProxyAPI::ForemanDynflow::DynflowProxy.new(:url => url)
|
61
|
+
results = proxy.task_states(tasks.map(&:remote_task_id))
|
62
|
+
tasks.map do |task|
|
63
|
+
task.result = results[task.remote_task_id]
|
64
|
+
task
|
65
|
+
end
|
66
|
+
rescue => e
|
67
|
+
# We could not reach the remote task, we assume it's gone
|
68
|
+
action.action_logger.warn(_('Failed to check on tasks on proxy at %{url}: %{exception}') % { :url => url, :exception => e.message })
|
69
|
+
tasks
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -1,10 +1,12 @@
|
|
1
1
|
module Actions
|
2
2
|
class ProxyAction < Base
|
3
3
|
include ::Dynflow::Action::Cancellable
|
4
|
-
include ::Dynflow::Action::Timeouts
|
5
4
|
|
6
5
|
middleware.use ::Actions::Middleware::HideSecrets
|
7
6
|
|
7
|
+
execution_plan_hooks.use :clean_remote_task, :on => :stopped
|
8
|
+
execution_plan_hooks.use :wipe_secrets!, :on => :stopped
|
9
|
+
|
8
10
|
class CallbackData
|
9
11
|
attr_reader :data
|
10
12
|
|
@@ -13,6 +15,14 @@ module Actions
|
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
18
|
+
class ProxyActionMissing < RuntimeError
|
19
|
+
def backtrace
|
20
|
+
[]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class ProxyActionStopped; end
|
25
|
+
|
16
26
|
def plan(proxy, klass, options)
|
17
27
|
options[:connection_options] ||= {}
|
18
28
|
default_connection_options.each { |key, value| options[:connection_options][key] ||= value }
|
@@ -23,7 +33,7 @@ module Actions
|
|
23
33
|
with_connection_error_handling(event) do |event|
|
24
34
|
case event
|
25
35
|
when nil
|
26
|
-
if
|
36
|
+
if remote_task
|
27
37
|
on_resume
|
28
38
|
else
|
29
39
|
trigger_proxy_task
|
@@ -37,38 +47,41 @@ module Actions
|
|
37
47
|
abort_proxy_task
|
38
48
|
when CallbackData
|
39
49
|
on_data(event.data)
|
40
|
-
when
|
41
|
-
|
50
|
+
when ProxyActionMissing
|
51
|
+
on_proxy_action_missing
|
52
|
+
when ProxyActionStopped
|
53
|
+
on_proxy_action_stopped
|
42
54
|
else
|
43
55
|
raise "Unexpected event #{event.inspect}"
|
44
56
|
end
|
45
57
|
end
|
46
58
|
end
|
47
59
|
|
60
|
+
def remote_task
|
61
|
+
@remote_task ||= ForemanTasks::RemoteTask.find_by(:execution_plan_id => execution_plan_id, :step_id => run_step_id)
|
62
|
+
end
|
63
|
+
|
48
64
|
def trigger_proxy_task
|
49
65
|
suspend do |_suspended_action|
|
50
|
-
set_timeout! unless timeout_set?
|
51
66
|
response = proxy.trigger_task(proxy_action_name,
|
52
67
|
input.merge(:callback => { :task_id => task.id,
|
53
68
|
:step_id => run_step_id }))
|
69
|
+
::ForemanTasks::RemoteTask.new(:remote_task_id => response['task_id'], :execution_plan_id => execution_plan_id,
|
70
|
+
:state => 'triggered', :proxy_url => input[:proxy_url], :step_id => run_step_id).save!
|
54
71
|
output[:proxy_task_id] = response['task_id']
|
55
72
|
end
|
56
73
|
end
|
57
74
|
|
58
75
|
def check_task_status
|
59
|
-
|
60
|
-
|
61
|
-
if
|
62
|
-
|
63
|
-
raise ::Foreman::Exception, _('The smart proxy task %s failed.') % output[:proxy_task_id]
|
64
|
-
else
|
65
|
-
on_data(response['actions'].find { |block_action| block_action['class'] == proxy_action_name }['output'])
|
66
|
-
end
|
76
|
+
response = proxy.status_of_task(output[:proxy_task_id])
|
77
|
+
if %w[stopped paused].include? response['state']
|
78
|
+
if response['result'] == 'error'
|
79
|
+
raise ::Foreman::Exception, _('The smart proxy task %s failed.') % output[:proxy_task_id]
|
67
80
|
else
|
68
|
-
|
81
|
+
on_data(response['actions'].find { |block_action| block_action['class'] == proxy_action_name }['output'])
|
69
82
|
end
|
70
83
|
else
|
71
|
-
|
84
|
+
suspend
|
72
85
|
end
|
73
86
|
end
|
74
87
|
|
@@ -95,12 +108,21 @@ module Actions
|
|
95
108
|
# @override to put custom logic on event handling
|
96
109
|
def on_data(data)
|
97
110
|
output[:proxy_output] = data
|
98
|
-
wipe_secrets!
|
99
111
|
end
|
100
112
|
|
101
|
-
|
113
|
+
# Removes the :secrets key from the action's input and output and saves the action
|
114
|
+
def wipe_secrets!(_execution_plan)
|
102
115
|
input.delete(:secrets)
|
103
116
|
output.delete(:secrets)
|
117
|
+
world.persistence.save_action(execution_plan_id, self)
|
118
|
+
end
|
119
|
+
|
120
|
+
def on_proxy_action_missing
|
121
|
+
error! ProxyActionMissing.new(_('Proxy task gone missing from the smart proxy'))
|
122
|
+
end
|
123
|
+
|
124
|
+
def on_proxy_action_stopped
|
125
|
+
check_task_status
|
104
126
|
end
|
105
127
|
|
106
128
|
# @override String name of an action to be triggered on server
|
@@ -113,8 +135,8 @@ module Actions
|
|
113
135
|
end
|
114
136
|
|
115
137
|
def proxy_output(live = false)
|
116
|
-
if output.key?(:proxy_output)
|
117
|
-
output.fetch(:proxy_output
|
138
|
+
if output.key?(:proxy_output) || state == :error
|
139
|
+
output.fetch(:proxy_output, {})
|
118
140
|
elsif live && output[:proxy_task_id]
|
119
141
|
proxy_data = proxy.status_of_task(output[:proxy_task_id])['actions'].detect { |action| action['class'] == proxy_action_name }
|
120
142
|
proxy_data.fetch('output', {})
|
@@ -146,22 +168,15 @@ module Actions
|
|
146
168
|
output[:metadata] = thing
|
147
169
|
end
|
148
170
|
|
149
|
-
def timeout_set?
|
150
|
-
!metadata[:timeout].nil?
|
151
|
-
end
|
152
|
-
|
153
|
-
def set_timeout!
|
154
|
-
time = Time.zone.now + input[:connection_options][:timeout]
|
155
|
-
schedule_timeout(time)
|
156
|
-
metadata[:timeout] = time.to_s
|
157
|
-
end
|
158
|
-
|
159
171
|
def default_connection_options
|
160
172
|
# Fails if the plan is not finished within 60 seconds from the first task trigger attempt on the smart proxy
|
161
173
|
# If the triggering fails, it retries 3 more times with 15 second delays
|
162
174
|
{ :retry_interval => Setting['foreman_tasks_proxy_action_retry_interval'] || 15,
|
163
|
-
:retry_count => Setting['foreman_tasks_proxy_action_retry_count'] || 4
|
164
|
-
|
175
|
+
:retry_count => Setting['foreman_tasks_proxy_action_retry_count'] || 4 }
|
176
|
+
end
|
177
|
+
|
178
|
+
def clean_remote_task(*_args)
|
179
|
+
remote_task.destroy! if remote_task
|
165
180
|
end
|
166
181
|
|
167
182
|
private
|
@@ -173,7 +188,7 @@ module Actions
|
|
173
188
|
def with_connection_error_handling(event = nil)
|
174
189
|
yield event
|
175
190
|
rescue ::RestClient::Exception, Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ETIMEDOUT => e
|
176
|
-
if event.class == CallbackData
|
191
|
+
if event.class == CallbackData
|
177
192
|
raise e
|
178
193
|
else
|
179
194
|
handle_connection_exception(e, event)
|
@@ -34,6 +34,11 @@ module ProxyAPI
|
|
34
34
|
def tasks_count(state)
|
35
35
|
MultiJson.load(Task.new(@args).send(:get, "count?state=#{state}"))['count'].to_i
|
36
36
|
end
|
37
|
+
|
38
|
+
def task_states(ids)
|
39
|
+
payload = MultiJson.dump(:task_ids => ids)
|
40
|
+
MultiJson.load(Task.new(@args).send(:post, payload, 'status'))
|
41
|
+
end
|
37
42
|
end
|
38
43
|
end
|
39
44
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ForemanTasks
|
2
|
+
class RemoteTask < ApplicationRecord
|
3
|
+
attr_accessor :result
|
4
|
+
|
5
|
+
belongs_to :task, :class_name => 'ForemanTasks::Task',
|
6
|
+
:primary_key => :external_id,
|
7
|
+
:foreign_key => :execution_plan_id,
|
8
|
+
:inverse_of => :remote_tasks
|
9
|
+
|
10
|
+
scope :triggered, -> { where(:state => 'triggered') }
|
11
|
+
end
|
12
|
+
end
|
@@ -20,6 +20,10 @@ module ForemanTasks
|
|
20
20
|
has_many :sub_tasks, :class_name => 'ForemanTasks::Task', :foreign_key => :parent_task_id, :dependent => :nullify
|
21
21
|
has_many :locks, :dependent => :destroy
|
22
22
|
|
23
|
+
has_many :remote_sub_tasks, :class_name => 'ForemanTasks::RemoteTask', :through => :sub_tasks, :source => :remote_tasks
|
24
|
+
|
25
|
+
has_many :remote_tasks, :class_name => 'ForemanTasks::RemoteTask', :primary_key => :external_id, :foreign_key => :execution_plan_id, :dependent => :destroy
|
26
|
+
|
23
27
|
has_many :task_group_members, :dependent => :destroy
|
24
28
|
has_many :task_groups, :through => :task_group_members
|
25
29
|
if Rails::VERSION::MAJOR < 4
|
@@ -210,7 +214,8 @@ module ForemanTasks
|
|
210
214
|
end
|
211
215
|
|
212
216
|
def action
|
213
|
-
|
217
|
+
return to_label if super.blank?
|
218
|
+
super
|
214
219
|
end
|
215
220
|
|
216
221
|
def to_label
|
@@ -15,11 +15,11 @@ module ForemanTasks
|
|
15
15
|
self.start_at = string_to_time(utc_zone, data[:start_at]) if data[:start_at]
|
16
16
|
self.start_before = string_to_time(utc_zone, data[:start_before]) if data[:start_before]
|
17
17
|
self.parent_task_id ||= begin
|
18
|
-
if main_action.caller_execution_plan_id
|
18
|
+
if main_action.present? && main_action.caller_execution_plan_id
|
19
19
|
DynflowTask.where(:external_id => main_action.caller_execution_plan_id).first!.id
|
20
20
|
end
|
21
21
|
end
|
22
|
-
self
|
22
|
+
self[:label] ||= label
|
23
23
|
changes = self.changes
|
24
24
|
save!
|
25
25
|
changes
|
@@ -69,12 +69,19 @@ module ForemanTasks
|
|
69
69
|
execution_plan(false)
|
70
70
|
end
|
71
71
|
|
72
|
+
def label
|
73
|
+
return execution_plan_action.input['job_class'] if active_job?
|
74
|
+
return main_action.class.name if main_action.present?
|
75
|
+
self[:label]
|
76
|
+
end
|
77
|
+
|
72
78
|
def input
|
73
|
-
|
79
|
+
return execution_plan_action.input['job_arguments'] if active_job?
|
80
|
+
main_action.task_input if main_action.respond_to?(:task_input)
|
74
81
|
end
|
75
82
|
|
76
83
|
def output
|
77
|
-
main_action.respond_to?(:task_output)
|
84
|
+
main_action.task_output if main_action.respond_to?(:task_output)
|
78
85
|
end
|
79
86
|
|
80
87
|
def failed_steps
|
@@ -98,17 +105,47 @@ module ForemanTasks
|
|
98
105
|
|
99
106
|
def main_action
|
100
107
|
return @main_action if defined?(@main_action)
|
101
|
-
|
108
|
+
if active_job?
|
109
|
+
action = execution_plan_action
|
110
|
+
active_job_action(action.input['job_class'], action.input['job_arguments'])
|
111
|
+
else
|
112
|
+
@main_action = execution_plan && execution_plan.root_plan_step.try(:action, execution_plan)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# The class for ActiveJob jobs in Dynflow, JobWrapper is not expected to
|
117
|
+
# implement any humanized actions. Individual jobs are expected to implement
|
118
|
+
# humanized_* methods for foreman-tasks integration.
|
119
|
+
def active_job_action(klass, args)
|
120
|
+
return if klass.blank?
|
121
|
+
klass.constantize.new(*args)
|
122
|
+
end
|
123
|
+
|
124
|
+
def active_job?
|
125
|
+
return false unless execution_plan.present? &&
|
126
|
+
execution_plan.root_plan_step.present?
|
127
|
+
execution_plan_action.class == ::Dynflow::ActiveJob::QueueAdapters::JobWrapper
|
128
|
+
end
|
129
|
+
|
130
|
+
def execution_plan_action
|
131
|
+
execution_plan.root_plan_step.action(execution_plan)
|
132
|
+
end
|
133
|
+
|
134
|
+
def execution_scheduled?
|
135
|
+
main_action.nil? || main_action.respond_to?(:execution_plan) &&
|
136
|
+
main_action.execution_plan.state == :scheduled ||
|
137
|
+
execution_plan.state == :scheduled
|
102
138
|
end
|
103
139
|
|
104
140
|
def get_humanized(method)
|
105
141
|
@humanized_cache ||= {}
|
106
|
-
|
107
|
-
method = "humanized_#{method}".to_sym
|
108
|
-
end
|
142
|
+
method = find_humanize_method_kind(method)
|
109
143
|
Match! method, :humanized_name, :humanized_input, :humanized_output, :humanized_errors
|
110
|
-
|
111
|
-
|
144
|
+
if method != :humanized_name && execution_scheduled?
|
145
|
+
return N_('N/A')
|
146
|
+
elsif method == :humanized_name && main_action.nil?
|
147
|
+
return N_(label)
|
148
|
+
end
|
112
149
|
@humanized_cache[method] ||= begin
|
113
150
|
if main_action.respond_to? method
|
114
151
|
begin
|
@@ -120,6 +157,13 @@ module ForemanTasks
|
|
120
157
|
end
|
121
158
|
end
|
122
159
|
|
160
|
+
def find_humanize_method_kind(method)
|
161
|
+
return method if /humanized_.*/ =~ method
|
162
|
+
if [:name, :input, :output, :error].include?(method)
|
163
|
+
"humanized_#{method}".to_sym
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
123
167
|
def self.consistency_check
|
124
168
|
fixed_count = 0
|
125
169
|
logger = Foreman::Logging.logger('foreman-tasks')
|
@@ -9,8 +9,7 @@ class Setting::ForemanTasks < Setting
|
|
9
9
|
set('dynflow_enable_console', N_('Enable the dynflow console (/foreman_tasks/dynflow) for debugging'), true),
|
10
10
|
set('dynflow_console_require_auth', N_('Require user to be authenticated as user with admin rights when accessing dynflow console'), true),
|
11
11
|
set('foreman_tasks_proxy_action_retry_count', N_('Number of attempts to start a task on the smart proxy before failing'), 4),
|
12
|
-
set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15)
|
13
|
-
set('foreman_tasks_proxy_action_start_timeout', N_('Time in second during which a task has to be started on the proxy'), 60)
|
12
|
+
set('foreman_tasks_proxy_action_retry_interval', N_('Time in seconds between retries'), 15)
|
14
13
|
].each { |s| create! s.update(:category => 'Setting::ForemanTasks') }
|
15
14
|
end
|
16
15
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class AddRemoteTasks < ActiveRecord::Migration[5.0]
|
2
|
+
def change
|
3
|
+
create_table :foreman_tasks_remote_tasks do |t|
|
4
|
+
t.string :execution_plan_id, :null => false
|
5
|
+
t.integer :step_id, :null => false
|
6
|
+
t.string :state, :null => false, :default => 'new'
|
7
|
+
|
8
|
+
t.string :proxy_url, :null => false
|
9
|
+
t.string :remote_task_id
|
10
|
+
|
11
|
+
t.index [:execution_plan_id, :step_id], :name => 'index_foreman_tasks_plan_id_and_step_id'
|
12
|
+
|
13
|
+
t.datetime :created_at
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -100,6 +100,7 @@ module ForemanTasks
|
|
100
100
|
with_batches(source, name) do |chunk|
|
101
101
|
delete_tasks chunk
|
102
102
|
delete_dynflow_plans chunk
|
103
|
+
delete_remote_tasks(chunk)
|
103
104
|
end
|
104
105
|
end
|
105
106
|
delete_orphaned_dynflow_tasks
|
@@ -115,6 +116,10 @@ module ForemanTasks
|
|
115
116
|
tasks.delete_all
|
116
117
|
end
|
117
118
|
|
119
|
+
def delete_remote_tasks(chunk)
|
120
|
+
ForemanTasks::RemoteTask.where(:execution_plan_id => chunk.map(&:external_id)).delete_all
|
121
|
+
end
|
122
|
+
|
118
123
|
def tasks_to_csv(dataset, backup_dir, file_name)
|
119
124
|
with_backup_file(backup_dir, file_name) do |csv, appending|
|
120
125
|
csv << ForemanTasks::Task.attribute_names.to_csv unless appending
|
data/lib/foreman_tasks/engine.rb
CHANGED
@@ -133,7 +133,7 @@ module ForemanTasks
|
|
133
133
|
# to enable async Foreman operations using Dynflow
|
134
134
|
if ENV['FOREMAN_TASKS_MONKEYS'] == 'true'
|
135
135
|
config.to_prepare do
|
136
|
-
::Api::V2::HostsController.send :
|
136
|
+
::Api::V2::HostsController.send :prepend, ForemanTasks::Concerns::HostsControllerExtension
|
137
137
|
::Host::Base.send :include, ForemanTasks::Concerns::HostActionSubject
|
138
138
|
end
|
139
139
|
end
|
@@ -18,6 +18,33 @@ module ForemanTasks
|
|
18
18
|
assert_response :success
|
19
19
|
assert_template 'api/tasks/show'
|
20
20
|
end
|
21
|
+
|
22
|
+
test_attributes :pid => 'a2a81ca2-63c4-47f5-9314-5852f5e2617f'
|
23
|
+
it 'search for non-existent task' do
|
24
|
+
get :show, params: { :id => 'abc123' }
|
25
|
+
assert_response :missing
|
26
|
+
assert_includes @response.body, 'Resource task not found by id'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'GET /api/tasks/summary' do
|
31
|
+
class DummyTestSummaryAction < Support::DummyDynflowAction
|
32
|
+
# a dummy test action that do nothing
|
33
|
+
def run
|
34
|
+
# a dummy run method
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
test_attributes :pid => 'bdcab413-a25d-4fe1-9db4-b50b5c31ebce'
|
39
|
+
it 'get tasks summary' do
|
40
|
+
ForemanTasks.trigger(DummyTestSummaryAction)
|
41
|
+
get :summary
|
42
|
+
assert_response :success
|
43
|
+
response = JSON.parse(@response.body)
|
44
|
+
assert_kind_of Array, response
|
45
|
+
refute response.empty?
|
46
|
+
assert_kind_of Hash, response[0]
|
47
|
+
end
|
21
48
|
end
|
22
49
|
|
23
50
|
describe 'POST /tasks/callback' do
|
@@ -26,7 +26,7 @@ module ForemanTasks
|
|
26
26
|
{ 'foo' => 'bar',
|
27
27
|
'secrets' => secrets,
|
28
28
|
'connection_options' =>
|
29
|
-
{ 'retry_interval' => 15, 'retry_count' => 4
|
29
|
+
{ 'retry_interval' => 15, 'retry_count' => 4 },
|
30
30
|
'proxy_url' => 'proxy.example.com',
|
31
31
|
'proxy_action_name' => 'Proxy::DummyAction',
|
32
32
|
'callback' => { 'task_id' => Support::DummyProxyAction.proxy.uuid, 'step_id' => @action.run_step_id } }]
|
@@ -107,6 +107,13 @@ module ForemanTasks
|
|
107
107
|
it 'wipes secrets' do
|
108
108
|
@action.input[:secrets].must_equal secrets
|
109
109
|
action = run_action(@action, ::Actions::ProxyAction::CallbackData.new('result' => 'success'))
|
110
|
+
|
111
|
+
# #wipe_secrets! gets called as a hook, hooks are not triggered when using action testing helpers
|
112
|
+
persistence = mock
|
113
|
+
persistence.stubs(:save_action)
|
114
|
+
action.world.stubs(:persistence).returns(persistence)
|
115
|
+
action.wipe_secrets!(nil)
|
116
|
+
|
110
117
|
refute action.input.key?(:secrets)
|
111
118
|
end
|
112
119
|
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.13.
|
4
|
+
version: 0.13.1
|
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-
|
11
|
+
date: 2018-05-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: foreman-tasks-core
|
@@ -143,6 +143,7 @@ files:
|
|
143
143
|
- app/lib/actions/middleware/keep_current_user.rb
|
144
144
|
- app/lib/actions/middleware/rails_executor_wrap.rb
|
145
145
|
- app/lib/actions/middleware/recurring_logic.rb
|
146
|
+
- app/lib/actions/middleware/watch_delegated_proxy_sub_tasks.rb
|
146
147
|
- app/lib/actions/proxy_action.rb
|
147
148
|
- app/lib/actions/recurring_action.rb
|
148
149
|
- app/lib/actions/serializers/active_record_serializer.rb
|
@@ -152,6 +153,7 @@ files:
|
|
152
153
|
- app/models/foreman_tasks/concerns/host_action_subject.rb
|
153
154
|
- app/models/foreman_tasks/lock.rb
|
154
155
|
- app/models/foreman_tasks/recurring_logic.rb
|
156
|
+
- app/models/foreman_tasks/remote_task.rb
|
155
157
|
- app/models/foreman_tasks/task.rb
|
156
158
|
- app/models/foreman_tasks/task/dynflow_task.rb
|
157
159
|
- app/models/foreman_tasks/task/status_explicator.rb
|
@@ -203,6 +205,7 @@ files:
|
|
203
205
|
- db/migrate/20160924213030_change_tasks_widget_names.rb
|
204
206
|
- db/migrate/20161003091412_add_missing_indexes.rb
|
205
207
|
- db/migrate/20171026082635_add_task_action.foreman_tasks.rb
|
208
|
+
- db/migrate/20180207150921_add_remote_tasks.foreman_tasks.rb
|
206
209
|
- db/migrate/20180216092715_use_uuid.rb
|
207
210
|
- db/seeds.d/20-foreman_tasks_permissions.rb
|
208
211
|
- db/seeds.d/60-dynflow_proxy_feature.rb
|
@@ -283,7 +286,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
283
286
|
version: '0'
|
284
287
|
requirements: []
|
285
288
|
rubyforge_project:
|
286
|
-
rubygems_version: 2.
|
289
|
+
rubygems_version: 2.7.3
|
287
290
|
signing_key:
|
288
291
|
specification_version: 4
|
289
292
|
summary: Foreman plugin for showing tasks information for resoruces and users
|