foreman-tasks 4.1.5 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ruby_tests.yml +0 -1
- data/.rubocop.yml +0 -4
- data/.rubocop_todo.yml +0 -2
- data/README.md +8 -6
- data/app/assets/javascripts/foreman_tasks/trigger_form.js +7 -0
- data/app/controllers/foreman_tasks/api/tasks_controller.rb +2 -2
- data/app/controllers/foreman_tasks/tasks_controller.rb +2 -2
- data/app/graphql/mutations/recurring_logics/cancel.rb +27 -0
- data/app/graphql/types/recurring_logic.rb +21 -0
- data/app/graphql/types/task.rb +25 -0
- data/app/graphql/types/triggering.rb +16 -0
- data/app/helpers/foreman_tasks/foreman_tasks_helper.rb +4 -1
- data/app/lib/actions/helpers/with_continuous_output.rb +1 -1
- data/app/lib/actions/proxy_action.rb +2 -12
- data/app/lib/actions/trigger_proxy_batch.rb +79 -0
- data/app/models/foreman_tasks/recurring_logic.rb +10 -0
- data/app/models/foreman_tasks/remote_task.rb +3 -19
- data/app/models/foreman_tasks/task.rb +29 -0
- data/app/models/foreman_tasks/triggering.rb +14 -4
- data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -1
- data/app/views/foreman_tasks/layouts/react.html.erb +0 -1
- data/app/views/foreman_tasks/recurring_logics/index.html.erb +4 -2
- data/app/views/foreman_tasks/task_groups/recurring_logic_task_groups/_recurring_logic_task_group.html.erb +8 -0
- data/db/migrate/20210720115251_add_purpose_to_recurring_logic.rb +6 -0
- data/extra/foreman-tasks-cleanup.sh +127 -0
- data/extra/foreman-tasks-export.sh +121 -0
- data/foreman-tasks.gemspec +1 -4
- data/lib/foreman_tasks/continuous_output.rb +50 -0
- data/lib/foreman_tasks/engine.rb +8 -15
- data/lib/foreman_tasks/tasks/export_tasks.rake +29 -8
- data/lib/foreman_tasks/version.rb +1 -1
- data/lib/foreman_tasks.rb +2 -5
- data/locale/fr/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/ja/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/locale/zh_CN/LC_MESSAGES/foreman_tasks.mo +0 -0
- data/package.json +7 -9
- data/test/controllers/api/tasks_controller_test.rb +19 -1
- data/test/controllers/tasks_controller_test.rb +19 -0
- data/test/factories/recurring_logic_factory.rb +7 -1
- data/test/graphql/mutations/recurring_logics/cancel_mutation_test.rb +66 -0
- data/test/graphql/queries/recurring_logic_test.rb +28 -0
- data/test/graphql/queries/recurring_logics_query_test.rb +30 -0
- data/test/graphql/queries/task_query_test.rb +33 -0
- data/test/graphql/queries/tasks_query_test.rb +31 -0
- data/test/support/dummy_proxy_action.rb +6 -0
- data/test/unit/actions/proxy_action_test.rb +11 -11
- data/test/unit/actions/trigger_proxy_batch_test.rb +59 -0
- data/test/unit/remote_task_test.rb +0 -8
- data/test/unit/task_test.rb +39 -8
- data/test/unit/triggering_test.rb +22 -0
- metadata +23 -26
- data/test/core/unit/dispatcher_test.rb +0 -43
- data/test/core/unit/runner_test.rb +0 -116
- data/test/core/unit/task_launcher_test.rb +0 -56
- data/test/foreman_tasks_core_test_helper.rb +0 -4
- data/test/unit/otp_manager_test.rb +0 -77
@@ -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
|