foreman-tasks 4.1.5 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,127 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
PROGNAME="$0"
|
4
|
+
EXECUTE=0
|
5
|
+
RAKE_COMMAND="${RAKE_COMMAND:-"foreman-rake"}"
|
6
|
+
|
7
|
+
function die() {
|
8
|
+
local code="$1"
|
9
|
+
local message="$2"
|
10
|
+
echo "$message" >&2
|
11
|
+
exit $code
|
12
|
+
}
|
13
|
+
|
14
|
+
function build_rake() {
|
15
|
+
echo -n "$RAKE_COMMAND "
|
16
|
+
echo -n 'foreman_tasks:cleanup '
|
17
|
+
for env in AFTER TASK_BACKUP BATCH_SIZE NOOP TASK_SEARCH STATES VERBOSE; do
|
18
|
+
local value="${!env}"
|
19
|
+
[ -n "${value}" ] && echo -n "${env}=$(printf '%q' "$value") "
|
20
|
+
done
|
21
|
+
echo
|
22
|
+
}
|
23
|
+
|
24
|
+
function usage() {
|
25
|
+
cat << EOF
|
26
|
+
Usage: $PROGNAME [script_options...] [options...]
|
27
|
+
|
28
|
+
An interface script for setting environment variables properly
|
29
|
+
for foreman-tasks:cleanup rake task. By default only prints
|
30
|
+
the command to run, with -E|--execute flag performs the cleanup.
|
31
|
+
|
32
|
+
Environment variables:
|
33
|
+
RAKE_COMMAND: can be used to redefine path to rake, by default foreman-rake
|
34
|
+
|
35
|
+
Script options:
|
36
|
+
EOF
|
37
|
+
cat <<EOF | column -s\& -t
|
38
|
+
-E|--execute & execute the created rake command
|
39
|
+
-h|--help & show this output
|
40
|
+
EOF
|
41
|
+
|
42
|
+
echo
|
43
|
+
echo Cleanup options:
|
44
|
+
cat <<EOF | column -s\& -t
|
45
|
+
-B|--batch-size BATCH_SIZE & process tasks in batches of BATCH_SIZE, 1000 by default
|
46
|
+
-S|--states STATES & operate on tasks in STATES, comma separated list of states, set to all to operate on tasks in any state
|
47
|
+
-a|--after AGE & operate on tasks older than AGE. Expected format is a number followed by the time unit (s,h,m,y), such as '10d' for 10 days
|
48
|
+
-b|--backup & backup deleted tasks
|
49
|
+
-n|--noop & do a dry run, print what would be done
|
50
|
+
-s|--search QUERY & use QUERY in scoped search format to match tasks to delete
|
51
|
+
-v|--verbose & be verbose
|
52
|
+
EOF
|
53
|
+
}
|
54
|
+
|
55
|
+
SHORTOPTS="a:bB:Ehns:S:v"
|
56
|
+
LONGOPTS="after:,backup,batch-size:,execute,help,noop,search:,states:,verbose"
|
57
|
+
|
58
|
+
ARGS=$(getopt -s bash \
|
59
|
+
--options $SHORTOPTS \
|
60
|
+
--longoptions $LONGOPTS \
|
61
|
+
--name $PROGNAME \
|
62
|
+
-- "$@" )
|
63
|
+
|
64
|
+
if [ $? -gt 0 ]; then
|
65
|
+
die 1 "getopt failed"
|
66
|
+
fi
|
67
|
+
|
68
|
+
eval set -- "$ARGS"
|
69
|
+
|
70
|
+
while true; do
|
71
|
+
case $1 in
|
72
|
+
-a|--after)
|
73
|
+
shift
|
74
|
+
AFTER="$1"
|
75
|
+
;;
|
76
|
+
-b|--backup)
|
77
|
+
TASK_BACKUP=true
|
78
|
+
;;
|
79
|
+
-B|--batch-size)
|
80
|
+
shift
|
81
|
+
BATCH_SIZE="$1"
|
82
|
+
;;
|
83
|
+
-n|--noop)
|
84
|
+
NOOP=1
|
85
|
+
;;
|
86
|
+
-s|--search)
|
87
|
+
shift
|
88
|
+
TASK_SEARCH="$1"
|
89
|
+
;;
|
90
|
+
-S|--states)
|
91
|
+
shift
|
92
|
+
if [ "$1" == "all" ]; then
|
93
|
+
STATES=","
|
94
|
+
else
|
95
|
+
STATES="$1"
|
96
|
+
fi
|
97
|
+
;;
|
98
|
+
-v|--verbose)
|
99
|
+
VERBOSE=1
|
100
|
+
;;
|
101
|
+
-h|--help)
|
102
|
+
usage
|
103
|
+
exit 0
|
104
|
+
;;
|
105
|
+
-E|--execute)
|
106
|
+
EXECUTE=1
|
107
|
+
;;
|
108
|
+
\?)
|
109
|
+
die 1 "Invalid option: -$OPTARG"
|
110
|
+
;;
|
111
|
+
--)
|
112
|
+
;;
|
113
|
+
*)
|
114
|
+
[ -n "$1" ] || break
|
115
|
+
die 1 "Unaccepted parameter: $1"
|
116
|
+
shift
|
117
|
+
;;
|
118
|
+
esac
|
119
|
+
shift
|
120
|
+
done
|
121
|
+
|
122
|
+
if [ "$EXECUTE" -eq 1 ]; then
|
123
|
+
build_rake | sh
|
124
|
+
else
|
125
|
+
build_rake
|
126
|
+
fi
|
127
|
+
|
@@ -0,0 +1,121 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
PROGNAME="$0"
|
4
|
+
EXECUTE=0
|
5
|
+
RAKE_COMMAND="${RAKE_COMMAND:-"foreman-rake"}"
|
6
|
+
|
7
|
+
function die() {
|
8
|
+
local code="$1"
|
9
|
+
local message="$2"
|
10
|
+
echo "$message" >&2
|
11
|
+
exit $code
|
12
|
+
}
|
13
|
+
|
14
|
+
function build_rake() {
|
15
|
+
echo -n "$RAKE_COMMAND "
|
16
|
+
echo -n 'foreman_tasks:export_tasks '
|
17
|
+
for env in TASK_SEARCH TASK_FILE TASK_FORMAT TASK_DAYS SKIP_FAILED; do
|
18
|
+
local value="${!env}"
|
19
|
+
[ -n "${value}" ] && echo -n "${env}=$(printf '%q' "$value") "
|
20
|
+
done
|
21
|
+
echo
|
22
|
+
}
|
23
|
+
|
24
|
+
function usage() {
|
25
|
+
cat << EOF
|
26
|
+
Usage: $PROGNAME [options]
|
27
|
+
|
28
|
+
An interface script for setting environment variables properly
|
29
|
+
for foreman-tasks:export_tasks rake task. By default only prints
|
30
|
+
the command to run, with -E|--execute flag performs the export.
|
31
|
+
|
32
|
+
Environment variables:
|
33
|
+
RAKE_COMMAND: can be used to redefine path to rake, by default foreman-rake
|
34
|
+
|
35
|
+
Script options:
|
36
|
+
EOF
|
37
|
+
cat <<EOF | column -s\& -t
|
38
|
+
-E|--execute & execute the created rake command
|
39
|
+
-h|--help & show this output
|
40
|
+
EOF
|
41
|
+
|
42
|
+
echo
|
43
|
+
echo Export options:
|
44
|
+
cat <<EOF | column -s\& -t
|
45
|
+
-d|--days DAYS & export only tasks started within the last DAYS days
|
46
|
+
-f|--format FORMAT & export tasks in FORMAT, one of html, html-dir, csv
|
47
|
+
-o|--output FILE & export tasks into FILE, a random file will be used if not provided
|
48
|
+
-s|--search QUERY & use QUERY in scoped search format to match tasks to export
|
49
|
+
-S|--skip-failed & skip tasks that fail to export
|
50
|
+
EOF
|
51
|
+
}
|
52
|
+
|
53
|
+
SHORTOPTS="d:Ehs:o:f:S"
|
54
|
+
LONGOPTS="days:,execute,help,search:,output:,format:,skip-failed"
|
55
|
+
|
56
|
+
ARGS=$(getopt -s bash \
|
57
|
+
--options $SHORTOPTS \
|
58
|
+
--longoptions $LONGOPTS \
|
59
|
+
--name $PROGNAME \
|
60
|
+
-- "$@" )
|
61
|
+
|
62
|
+
if [ $? -gt 0 ]; then
|
63
|
+
die 1 "getopt failed"
|
64
|
+
fi
|
65
|
+
|
66
|
+
eval set -- "$ARGS"
|
67
|
+
|
68
|
+
while true; do
|
69
|
+
case $1 in
|
70
|
+
-d|--days)
|
71
|
+
shift
|
72
|
+
TASK_DAYS="$1"
|
73
|
+
;;
|
74
|
+
-s|--search)
|
75
|
+
shift
|
76
|
+
TASK_SEARCH="$1"
|
77
|
+
;;
|
78
|
+
-o|--output)
|
79
|
+
shift
|
80
|
+
TASK_FILE="$1"
|
81
|
+
;;
|
82
|
+
-f|--format)
|
83
|
+
case "$2" in
|
84
|
+
"html" | "html-dir" | "csv")
|
85
|
+
shift
|
86
|
+
TASK_FORMAT="$1"
|
87
|
+
;;
|
88
|
+
*)
|
89
|
+
die 1 "Value for $1 must be one of html, csv. Given $2"
|
90
|
+
;;
|
91
|
+
esac
|
92
|
+
;;
|
93
|
+
-h|--help)
|
94
|
+
usage
|
95
|
+
exit 0
|
96
|
+
;;
|
97
|
+
-E|--execute)
|
98
|
+
EXECUTE=1
|
99
|
+
;;
|
100
|
+
-S|--skip-failed)
|
101
|
+
SKIP_FAILED=1
|
102
|
+
;;
|
103
|
+
\?)
|
104
|
+
die 1 "Invalid option: -$OPTARG"
|
105
|
+
;;
|
106
|
+
--)
|
107
|
+
;;
|
108
|
+
*)
|
109
|
+
[ -n "$1" ] || break
|
110
|
+
die 1 "Unaccepted parameter: $1"
|
111
|
+
shift
|
112
|
+
;;
|
113
|
+
esac
|
114
|
+
shift
|
115
|
+
done
|
116
|
+
|
117
|
+
if [ "$EXECUTE" -eq 1 ]; then
|
118
|
+
build_rake | sh
|
119
|
+
else
|
120
|
+
build_rake
|
121
|
+
fi
|
data/foreman-tasks.gemspec
CHANGED
@@ -20,16 +20,13 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
|
|
20
20
|
DESC
|
21
21
|
|
22
22
|
s.files = `git ls-files`.split("\n").reject do |file|
|
23
|
-
file.end_with?("test.rake")
|
24
|
-
file.start_with?('lib/foreman_tasks_core') ||
|
25
|
-
file == 'foreman-tasks-core.gemspec'
|
23
|
+
file.end_with?("test.rake")
|
26
24
|
end
|
27
25
|
|
28
26
|
s.test_files = `git ls-files test`.split("\n")
|
29
27
|
s.extra_rdoc_files = Dir['README*', 'LICENSE']
|
30
28
|
|
31
29
|
s.add_dependency "dynflow", '>= 1.2.3'
|
32
|
-
s.add_dependency "foreman-tasks-core"
|
33
30
|
s.add_dependency "get_process_mem" # for memory polling
|
34
31
|
s.add_dependency "parse-cron", '~> 0.1.4'
|
35
32
|
s.add_dependency "sinatra" # for Dynflow web console
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ForemanTasks
|
2
|
+
class ContinuousOutput
|
3
|
+
attr_accessor :raw_outputs
|
4
|
+
|
5
|
+
def initialize(raw_outputs = [])
|
6
|
+
@raw_outputs = []
|
7
|
+
raw_outputs.each { |raw_output| add_raw_output(raw_output) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_raw_output(raw_output)
|
11
|
+
missing_args = %w[output_type output timestamp] - raw_output.keys
|
12
|
+
unless missing_args.empty?
|
13
|
+
raise ArgumentError, "Missing args for raw output: #{missing_args.inspect}"
|
14
|
+
end
|
15
|
+
@raw_outputs << raw_output
|
16
|
+
end
|
17
|
+
|
18
|
+
def empty?
|
19
|
+
@raw_outputs.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def last_timestamp
|
23
|
+
return if @raw_outputs.empty?
|
24
|
+
@raw_outputs.last.fetch('timestamp')
|
25
|
+
end
|
26
|
+
|
27
|
+
def sort!
|
28
|
+
@raw_outputs.sort_by! { |record| record['timestamp'].to_f }
|
29
|
+
end
|
30
|
+
|
31
|
+
def humanize
|
32
|
+
sort!
|
33
|
+
raw_outputs.map { |output| output['output'] }.join("\n")
|
34
|
+
end
|
35
|
+
|
36
|
+
def add_exception(context, exception, timestamp = Time.now.getlocal)
|
37
|
+
add_output(context + ": #{exception.class} - #{exception.message}", 'debug', timestamp)
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_output(*args)
|
41
|
+
add_raw_output(self.class.format_output(*args))
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.format_output(message, type = 'debug', timestamp = Time.now.getlocal)
|
45
|
+
{ 'output_type' => type,
|
46
|
+
'output' => message,
|
47
|
+
'timestamp' => timestamp.to_f }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/foreman_tasks/engine.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
require 'foreman_tasks_core'
|
2
1
|
require 'fast_gettext'
|
3
2
|
require 'gettext_i18n_rails'
|
4
3
|
|
@@ -34,7 +33,7 @@ module ForemanTasks
|
|
34
33
|
|
35
34
|
initializer 'foreman_tasks.register_plugin', :before => :finisher_hook do |_app|
|
36
35
|
Foreman::Plugin.register :"foreman-tasks" do
|
37
|
-
requires_foreman '>= 2.
|
36
|
+
requires_foreman '>= 2.6.0'
|
38
37
|
divider :top_menu, :parent => :monitor_menu, :last => true, :caption => N_('Foreman Tasks')
|
39
38
|
menu :top_menu, :tasks,
|
40
39
|
:url_hash => { :controller => 'foreman_tasks/tasks', :action => :index },
|
@@ -66,6 +65,13 @@ module ForemanTasks
|
|
66
65
|
|
67
66
|
add_all_permissions_to_default_roles
|
68
67
|
|
68
|
+
register_graphql_query_field :task, '::Types::Task', :record_field
|
69
|
+
register_graphql_query_field :tasks, '::Types::Task', :collection_field
|
70
|
+
register_graphql_query_field :recurring_logic, '::Types::RecurringLogic', :record_field
|
71
|
+
register_graphql_query_field :recurring_logics, '::Types::RecurringLogic', :collection_field
|
72
|
+
|
73
|
+
register_graphql_mutation_field :cancel_recurring_logic, ::Mutations::RecurringLogics::Cancel
|
74
|
+
|
69
75
|
logger :dynflow, :enabled => true
|
70
76
|
logger :action, :enabled => true
|
71
77
|
|
@@ -116,19 +122,6 @@ module ForemanTasks
|
|
116
122
|
world.middleware.use Actions::Middleware::KeepCurrentRequestID
|
117
123
|
world.middleware.use ::Actions::Middleware::LoadSettingValues if Gem::Version.new(::SETTINGS[:version]) >= Gem::Version.new('2.5')
|
118
124
|
end
|
119
|
-
|
120
|
-
::ForemanTasks.dynflow.config.on_init do |world|
|
121
|
-
ForemanTasksCore.dynflow_setup(world)
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
initializer 'foreman_tasks.set_core_settings' do
|
126
|
-
ForemanTasksCore::SettingsLoader.settings_registry.each_key do |settings_keys|
|
127
|
-
settings = settings_keys.inject({}) do |h, settings_key|
|
128
|
-
h.merge(SETTINGS[settings_key] || {})
|
129
|
-
end
|
130
|
-
ForemanTasksCore::SettingsLoader.setup_settings(settings_keys.first, settings)
|
131
|
-
end
|
132
125
|
end
|
133
126
|
|
134
127
|
# to enable async Foreman operations using Dynflow
|
@@ -14,6 +14,7 @@ namespace :foreman_tasks do
|
|
14
14
|
* TASK_FILE : file to export to
|
15
15
|
* TASK_FORMAT : format to use for the export (either html, html-dir or csv)
|
16
16
|
* TASK_DAYS : number of days to go back
|
17
|
+
* SKIP_FAILED : skip tasks that fail to export (true or false[default])
|
17
18
|
|
18
19
|
If TASK_SEARCH is not defined, it defaults to all tasks in the past 7 days and
|
19
20
|
all unsuccessful tasks in the past 60 days. The default TASK_FORMAT is html
|
@@ -232,6 +233,7 @@ namespace :foreman_tasks do
|
|
232
233
|
<tr>
|
233
234
|
<td><a href=\"#{task.id}.html\">#{task.label}</a></td>
|
234
235
|
<td>#{task.started_at}</td>
|
236
|
+
<td>#{task.duration}</td>
|
235
237
|
<td>#{task.state}</td>
|
236
238
|
<td>#{task.result}</td>
|
237
239
|
</tr>
|
@@ -241,10 +243,12 @@ namespace :foreman_tasks do
|
|
241
243
|
|
242
244
|
def csv_export(export_filename, tasks)
|
243
245
|
CSV.open(export_filename, 'wb') do |csv|
|
244
|
-
csv << %w[id state type label result parent_task_id started_at ended_at]
|
246
|
+
csv << %w[id state type label result parent_task_id started_at ended_at duration]
|
245
247
|
tasks.find_each do |task|
|
246
|
-
|
247
|
-
|
248
|
+
with_error_handling(task) do
|
249
|
+
csv << [task.id, task.state, task.type, task.label, task.result,
|
250
|
+
task.parent_task_id, task.started_at, task.ended_at, task.duration]
|
251
|
+
end
|
248
252
|
end
|
249
253
|
end
|
250
254
|
end
|
@@ -253,15 +257,18 @@ namespace :foreman_tasks do
|
|
253
257
|
PageHelper.copy_assets(workdir)
|
254
258
|
|
255
259
|
renderer = TaskRender.new
|
256
|
-
total = tasks.count
|
260
|
+
total = tasks.count(:all)
|
257
261
|
index = File.open(File.join(workdir, 'index.html'), 'w')
|
258
262
|
|
259
263
|
File.open(File.join(workdir, 'index.html'), 'w') do |index|
|
260
264
|
PageHelper.pagify(index) do |io|
|
261
265
|
PageHelper.generate_with_index(io) do |index|
|
262
266
|
tasks.find_each.each_with_index do |task, count|
|
263
|
-
|
264
|
-
|
267
|
+
content = with_error_handling(task) { renderer.render_task(task) }
|
268
|
+
if content
|
269
|
+
File.open(File.join(workdir, "#{task.id}.html"), 'w') { |file| PageHelper.pagify(file, content) }
|
270
|
+
with_error_handling(task, _('task index entry')) { PageHelper.generate_index_entry(index, task) }
|
271
|
+
end
|
265
272
|
puts "#{count + 1}/#{total}"
|
266
273
|
end
|
267
274
|
end
|
@@ -281,6 +288,20 @@ namespace :foreman_tasks do
|
|
281
288
|
end
|
282
289
|
end
|
283
290
|
|
291
|
+
def with_error_handling(task, what = _('task'))
|
292
|
+
yield
|
293
|
+
rescue StandardError => e
|
294
|
+
resolution = SKIP_ERRORS ? _(', skipping') : ''
|
295
|
+
puts _("WARNING: %{what} failed to export%{resolution}. Additional task details below.") % { :what => what, :resolution => resolution }
|
296
|
+
puts task.inspect
|
297
|
+
unless SKIP_ERRORS
|
298
|
+
puts _("Re-run with SKIP_FAILED=true if you want to simply skip any tasks that fail to export.")
|
299
|
+
raise e
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
SKIP_ERRORS = ['true', '1', 'y', 'yes'].include? ENV['SKIP_FAILED'].downcase
|
304
|
+
|
284
305
|
filter = if ENV['TASK_SEARCH'].nil? && ENV['TASK_DAYS'].nil?
|
285
306
|
"started_at > \"#{7.days.ago.to_s(:db)}\" || " \
|
286
307
|
"(result != success && started_at > \"#{60.days.ago.to_s(:db)})\""
|
@@ -296,10 +317,10 @@ namespace :foreman_tasks do
|
|
296
317
|
format = ENV['TASK_FORMAT'] || 'html'
|
297
318
|
export_filename = ENV['TASK_FILE'] || generate_filename(format)
|
298
319
|
|
299
|
-
tasks = ForemanTasks::Task.search_for(filter).order(:started_at => :desc)
|
320
|
+
tasks = ForemanTasks::Task.search_for(filter).order(:started_at => :desc).with_duration.distinct
|
300
321
|
|
301
322
|
puts _("Exporting all tasks matching filter #{filter}")
|
302
|
-
puts _("Gathering #{tasks.count} tasks.")
|
323
|
+
puts _("Gathering #{tasks.count(:all)} tasks.")
|
303
324
|
case format
|
304
325
|
when 'html'
|
305
326
|
Dir.mktmpdir('task-export') do |tmp_dir|
|
data/lib/foreman_tasks.rb
CHANGED
@@ -6,17 +6,14 @@ require 'foreman_tasks/dynflow/configuration'
|
|
6
6
|
require 'foreman_tasks/triggers'
|
7
7
|
require 'foreman_tasks/authorizer_ext'
|
8
8
|
require 'foreman_tasks/cleaner'
|
9
|
+
require 'foreman_tasks/continuous_output'
|
9
10
|
|
10
11
|
module ForemanTasks
|
11
12
|
extend Algebrick::TypeCheck
|
12
13
|
extend Algebrick::Matching
|
13
14
|
|
14
15
|
def self.dynflow
|
15
|
-
@dynflow ||=
|
16
|
-
world = ForemanTasks::Dynflow.new(nil, ForemanTasks::Dynflow::Configuration.new)
|
17
|
-
ForemanTasksCore.dynflow_setup(world) if defined?(ForemanTasksCore)
|
18
|
-
world
|
19
|
-
end
|
16
|
+
@dynflow ||= ForemanTasks::Dynflow.new(nil, ForemanTasks::Dynflow::Configuration.new)
|
20
17
|
end
|
21
18
|
|
22
19
|
def self.trigger(action, *args, &block)
|
Binary file
|
Binary file
|
Binary file
|
data/package.json
CHANGED
@@ -23,20 +23,18 @@
|
|
23
23
|
"url": "http://projects.theforeman.org/projects/foreman-tasks/issues"
|
24
24
|
},
|
25
25
|
"peerDependencies": {
|
26
|
-
"@theforeman/vendor": "
|
26
|
+
"@theforeman/vendor": "^8.15.0"
|
27
27
|
},
|
28
28
|
"dependencies": {
|
29
|
-
"c3": "^0.4.11"
|
30
|
-
"humanize-duration": "^3.20.1",
|
31
|
-
"react-intl": "^2.8.0"
|
29
|
+
"c3": "^0.4.11"
|
32
30
|
},
|
33
31
|
"devDependencies": {
|
34
32
|
"@babel/core": "^7.7.0",
|
35
|
-
"@theforeman/builder": "^
|
36
|
-
"@theforeman/eslint-plugin-foreman": "
|
37
|
-
"@theforeman/stories": "^
|
38
|
-
"@theforeman/test": "^
|
39
|
-
"@theforeman/vendor-dev": "^
|
33
|
+
"@theforeman/builder": "^8.15.0",
|
34
|
+
"@theforeman/eslint-plugin-foreman": "^8.15.0",
|
35
|
+
"@theforeman/stories": "^8.15.0",
|
36
|
+
"@theforeman/test": "^8.15.0",
|
37
|
+
"@theforeman/vendor-dev": "^8.15.0",
|
40
38
|
"babel-eslint": "^10.0.3",
|
41
39
|
"eslint": "^6.7.2",
|
42
40
|
"jed": "^1.1.1",
|
@@ -102,6 +102,24 @@ module ForemanTasks
|
|
102
102
|
session: set_session_user(User.current)
|
103
103
|
assert_response :not_found
|
104
104
|
end
|
105
|
+
|
106
|
+
it 'shows duration column' do
|
107
|
+
task = ForemanTasks::Task.with_duration.find(FactoryBot.create(:dynflow_task).id)
|
108
|
+
get :show, params: { id: task.id }, session: set_session_user
|
109
|
+
assert_response :success
|
110
|
+
data = JSON.parse(response.body)
|
111
|
+
_(data['duration']).must_equal task.duration
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
describe 'GET /api/tasks/index' do
|
116
|
+
it 'shows duration column' do
|
117
|
+
task = ForemanTasks::Task.with_duration.find(FactoryBot.create(:dynflow_task).id)
|
118
|
+
get :index, session: set_session_user
|
119
|
+
assert_response :success
|
120
|
+
data = JSON.parse(response.body)
|
121
|
+
_(data['results'][0]['duration']).must_equal task.duration
|
122
|
+
end
|
105
123
|
end
|
106
124
|
|
107
125
|
describe 'GET /api/tasks/summary' do
|
@@ -174,7 +192,7 @@ module ForemanTasks
|
|
174
192
|
_(task.state).must_equal 'running'
|
175
193
|
_(task.result).must_equal 'pending'
|
176
194
|
|
177
|
-
callback = Support::DummyProxyAction.proxy.log[:trigger_task].first[1][:callback]
|
195
|
+
callback = Support::DummyProxyAction.proxy.log[:trigger_task].first[1][:action_input][:callback]
|
178
196
|
post :callback, params: { 'callback' => callback, 'data' => { 'result' => 'success' } }
|
179
197
|
triggered.finished.wait(5)
|
180
198
|
|
@@ -91,6 +91,15 @@ module ForemanTasks
|
|
91
91
|
end
|
92
92
|
end
|
93
93
|
|
94
|
+
describe 'index' do
|
95
|
+
it 'shows duration column' do
|
96
|
+
task = ForemanTasks::Task.with_duration.find(FactoryBot.create(:some_task).id)
|
97
|
+
get(:index, params: {}, session: set_session_user)
|
98
|
+
assert_response :success
|
99
|
+
assert_include response.body.lines[1], task.duration
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
94
103
|
describe 'sub_tasks' do
|
95
104
|
it 'does not allow user without permissions to see task details' do
|
96
105
|
setup_user('view', 'foreman_tasks', 'owner.id = current_user')
|
@@ -109,6 +118,16 @@ module ForemanTasks
|
|
109
118
|
assert_equal 2, response.body.lines.size
|
110
119
|
assert_include response.body.lines[1], 'Child action'
|
111
120
|
end
|
121
|
+
|
122
|
+
it 'shows duration column' do
|
123
|
+
parent = ForemanTasks::Task.find(FactoryBot.create(:some_task).id)
|
124
|
+
child = ForemanTasks::Task.with_duration.find(FactoryBot.create(:some_task).id)
|
125
|
+
child.parent_task_id = parent.id
|
126
|
+
child.save!
|
127
|
+
get(:sub_tasks, params: { id: parent.id }, session: set_session_user)
|
128
|
+
assert_response :success
|
129
|
+
assert_include response.body.lines[1], child.duration
|
130
|
+
end
|
112
131
|
end
|
113
132
|
|
114
133
|
describe 'taxonomy scoping' do
|
@@ -1,8 +1,14 @@
|
|
1
1
|
FactoryBot.define do
|
2
2
|
factory :recurring_logic, :class => ForemanTasks::RecurringLogic do
|
3
3
|
cron_line { '* * * * *' }
|
4
|
-
|
4
|
+
association :task_group
|
5
5
|
end
|
6
6
|
|
7
|
+
factory :task_group, :class => ::ForemanTasks::TaskGroup do
|
8
|
+
type { "ForemanTasks::TaskGroups::RecurringLogicTaskGroup" }
|
9
|
+
end
|
7
10
|
factory :recurring_logic_task_group, :class => ::ForemanTasks::TaskGroups::RecurringLogicTaskGroup
|
11
|
+
factory :task_group_member, :class => ::ForemanTasks::TaskGroupMember do
|
12
|
+
association :task_group, :task
|
13
|
+
end
|
8
14
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'foreman_tasks_test_helper'
|
2
|
+
|
3
|
+
module Mutations
|
4
|
+
module RecurringLogics
|
5
|
+
class CancelMutationTest < ActiveSupport::TestCase
|
6
|
+
setup do
|
7
|
+
@task = FactoryBot.create(:dynflow_task, state: 'pending')
|
8
|
+
@task_group = FactoryBot.create(:recurring_logic_task_group)
|
9
|
+
@task_group_member = FactoryBot.create(:task_group_member, task: @task, task_group: @task_group)
|
10
|
+
@recurring_logic = FactoryBot.create(:recurring_logic, task_group: @task_group)
|
11
|
+
@id = Foreman::GlobalId.for(@recurring_logic)
|
12
|
+
@variables = { id: @id }
|
13
|
+
@query =
|
14
|
+
<<-GRAPHQL
|
15
|
+
mutation CancelRecurringLogic($id:ID!) {
|
16
|
+
cancelRecurringLogic(input: { id: $id }){
|
17
|
+
recurringLogic {
|
18
|
+
id
|
19
|
+
state
|
20
|
+
cronLine
|
21
|
+
}
|
22
|
+
errors {
|
23
|
+
message
|
24
|
+
path
|
25
|
+
}
|
26
|
+
}
|
27
|
+
}
|
28
|
+
GRAPHQL
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'as admin' do
|
32
|
+
setup do
|
33
|
+
@context = { current_user: FactoryBot.create(:user, :admin) }
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'should cancel recurring logic' do
|
37
|
+
assert_not_equal 'cancelled', @recurring_logic.state
|
38
|
+
result = ForemanGraphqlSchema.execute(@query, variables: @variables, context: @context)
|
39
|
+
assert_empty result['errors']
|
40
|
+
assert_empty result['data']['cancelRecurringLogic']['errors']
|
41
|
+
assert_equal 'cancelled', result['data']['cancelRecurringLogic']['recurringLogic']['state']
|
42
|
+
@recurring_logic.reload
|
43
|
+
assert_equal 'cancelled', @recurring_logic.state
|
44
|
+
end
|
45
|
+
|
46
|
+
test 'should handle errors on execution plan load failure' do
|
47
|
+
invalid_plan = ::Dynflow::ExecutionPlan::InvalidPlan.new(StandardError.new('This is a failure'), 'xyz', 'test-label', 'invalid')
|
48
|
+
::Dynflow::Persistence.any_instance.stubs(:load_execution_plan).returns(invalid_plan)
|
49
|
+
result = ForemanGraphqlSchema.execute(@query, variables: @variables, context: @context)
|
50
|
+
assert_equal "There has been an error when canceling one of the tasks: This is a failure", result['data']['cancelRecurringLogic']['errors'].first['message']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'as viewer' do
|
55
|
+
setup do
|
56
|
+
@context = { current_user: setup_user('view', 'recurring_logics') }
|
57
|
+
end
|
58
|
+
|
59
|
+
test 'should not allow to cancel recurring logic' do
|
60
|
+
result = ForemanGraphqlSchema.execute(@query, variables: @variables, context: @context)
|
61
|
+
assert_includes result['errors'].first['message'], 'Unauthorized.'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|