foreman-tasks 0.13.0 → 0.13.1
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 +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
|