foreman-tasks 5.1.1 → 5.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c191bf6093506d0c5346c71a026908b3c5c6b10fa9ceb27e5fdfbfa93a72c2c6
4
- data.tar.gz: 0fc1f15e955f6d13469aa28883db8447f8e3770d4751206faaec5c224d3ffcbc
3
+ metadata.gz: 882587333f9ebd555b6bd0ca6adf6c9e75789fce2e84cbbc2e3af0943cdf0051
4
+ data.tar.gz: 1db069d66971fed8af95856a2d582b1320c02c48fddec68f9f9a1061abc6965c
5
5
  SHA512:
6
- metadata.gz: d57a47e629be8d4bcfaba9bc853f7ec0853ea8f968884d4b589e98d74027a73bcef5ff45ff53f3e01f8794a28a01729bb4c811b004430296304a86b7936d4bd4
7
- data.tar.gz: e4e3b07b3e4f84e233534c40adadaa8c4032c33e5bd5a8623a8d54bcece9de022c1c4e2fd81ca0f3ed1c9e512df23dc14b5d2fe8be349de03df1a5b4a41c7235
6
+ metadata.gz: b204e43fe719216b44377fa0eb69654a404dfc7d53fbd5ed803efca3f5dd649d2dc901abd1b29b4048c1f99f7a37bd1b94c5c349ae9844e08ae95bd86a942b35
7
+ data.tar.gz: 90c69b77188a55d8d993a6e098945448dce1ff720697def8ea1914ba2c03fe2ed7efec30b4a7e33224afa9b32d3b9f1c8eb4fd37cdee37566321e69d67d78b1a
data/.rubocop.yml CHANGED
@@ -46,10 +46,6 @@ Style/SymbolArray:
46
46
  Style/FormatString:
47
47
  Enabled: false
48
48
 
49
- Rails/Present:
50
- Exclude:
51
- - lib/foreman_tasks_core/**/*
52
-
53
49
  Rails/FilePath:
54
50
  Enabled: false
55
51
 
data/.rubocop_todo.yml CHANGED
@@ -11,7 +11,6 @@
11
11
  # Include: **/*.gemspec
12
12
  Gemspec/RequiredRubyVersion:
13
13
  Exclude:
14
- - 'foreman-tasks-core.gemspec'
15
14
  - 'foreman-tasks.gemspec'
16
15
 
17
16
  # Offense count: 1
@@ -37,7 +36,6 @@ Naming/MemoizedInstanceVariableName:
37
36
  Exclude:
38
37
  - 'app/controllers/foreman_tasks/recurring_logics_controller.rb'
39
38
  - 'app/lib/actions/recurring_action.rb'
40
- - 'lib/foreman_tasks_core/otp_manager.rb'
41
39
 
42
40
  # Offense count: 11
43
41
  # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
data/README.md CHANGED
@@ -6,7 +6,8 @@ happening/happened in your Foreman instance. A framework for asynchronous tasks
6
6
 
7
7
  * Website: [TheForeman.org](http://theforeman.org)
8
8
  * ServerFault tag: [Foreman](http://serverfault.com/questions/tagged/foreman)
9
- * Issues: [foreman-tasks Redmine](http://projects.theforeman.org/projects/foreman-tasks)
9
+ * Issues: [Foreman-tasks Redmine](http://projects.theforeman.org/projects/foreman-tasks)
10
+ * Manual: [Foreman-tasks Manual](https://www.theforeman.org/plugins/foreman_tasks/0.8/index.html)
10
11
  * Wiki: [Foreman wiki](http://projects.theforeman.org/projects/foreman/wiki/About)
11
12
  * Community and support: #theforeman for general support, #theforeman-dev for development chat in [Freenode](irc.freenode.net)
12
13
  * Mailing lists:
@@ -25,6 +26,7 @@ happening/happened in your Foreman instance. A framework for asynchronous tasks
25
26
  | >= 1.22 | ~> 0.15.0 |
26
27
  | >= 2.0 | ~> 1.0.0 |
27
28
  | >= 2.1 | ~> 2.0.0 |
29
+ | >= 2.6 | ~> 5.2.0 |
28
30
 
29
31
  Installation
30
32
  ------------
@@ -154,9 +156,9 @@ rails root directory. See `-h` for more details and options
154
156
  Tasks cleanup
155
157
  -------------
156
158
 
157
- Although, the history of tasks has an auditing value, some kinds of
158
- tasks can grow up in number quite soon. Therefore there is a mechanism
159
- how to clean the tasks, using a rake command. When running without
159
+ Although the history of tasks has an auditing value, some kinds of
160
+ tasks can rapidly increase. Therefore, there is a mechanism for
161
+ cleaning up the tasks using a rake command. When running without
160
162
  any arguments, the tasks are deleted based on the default parameters
161
163
  defined in the code.
162
164
 
@@ -179,7 +181,7 @@ override the default configuration inside the configuration
179
181
  ```
180
182
  :foreman-tasks:
181
183
  :cleanup:
182
- # the period after witch to delete all the tasks (by default all tasks are not being deleted after some period)
184
+ # the period after which to delete all the tasks (by default, all tasks are not deleted after some period)
183
185
  :after: 365d
184
186
  # per action settings to override the default defined in the actions (cleanup_after method)
185
187
  :actions:
@@ -194,7 +196,7 @@ to specify the search criteria for the cleanup manually:
194
196
  * `TASK_SEARCH`: scoped search filter (example: 'label =
195
197
  "Actions::Foreman::Host::ImportFacts"')
196
198
  * `AFTER`: delete tasks created after `AFTER` period. Expected format
197
- is a number followed by the time unit (`s`, `h`, `m`, `y`), such as
199
+ is a number followed by the time unit (`s`, `h`, `d`, `m`, `y`), such as
198
200
  `10d` for 10 days (applicable only when the `TASK_SEARCH` option is
199
201
  specified)
200
202
  * `STATES`: comma separated list of task states to touch with the
@@ -4,6 +4,8 @@ module ForemanTasks
4
4
  include Foreman::Controller::CsvResponder
5
5
  include ForemanTasks::FindTasksCommon
6
6
 
7
+ before_action :find_dynflow_task, only: [:unlock, :force_unlock, :cancel, :cancel_step, :resume]
8
+
7
9
  def show
8
10
  @task = resource_base.find(params[:id])
9
11
  render :layout => !request.xhr?
@@ -31,8 +33,7 @@ module ForemanTasks
31
33
  end
32
34
 
33
35
  def cancel_step
34
- task = find_dynflow_task
35
- result = ForemanTasks.dynflow.world.event(task.external_id, params[:step_id].to_i, ::Dynflow::Action::Cancellable::Cancel).wait
36
+ result = ForemanTasks.dynflow.world.event(@dynflow_task.external_id, params[:step_id].to_i, ::Dynflow::Action::Cancellable::Cancel).wait
36
37
  if result.rejected?
37
38
  render json: { error: result.reason }, status: :bad_request
38
39
  else
@@ -41,8 +42,7 @@ module ForemanTasks
41
42
  end
42
43
 
43
44
  def cancel
44
- task = find_dynflow_task
45
- if task.cancel
45
+ if @dynflow_task.cancel
46
46
  render json: { statusText: 'OK' }
47
47
  else
48
48
  render json: {}, status: :bad_request
@@ -50,19 +50,17 @@ module ForemanTasks
50
50
  end
51
51
 
52
52
  def abort
53
- task = find_dynflow_task
54
- if task.abort
53
+ if @dynflow_task.abort
55
54
  flash[:info] = _('Trying to abort the task')
56
55
  else
57
56
  flash[:warning] = _('The task cannot be aborted at the moment.')
58
57
  end
59
- redirect_back(:fallback_location => foreman_tasks_task_path(task))
58
+ redirect_back(:fallback_location => foreman_tasks_task_path(@dynflow_task))
60
59
  end
61
60
 
62
61
  def resume
63
- task = find_dynflow_task
64
- if task.resumable?
65
- ForemanTasks.dynflow.world.execute(task.execution_plan.id)
62
+ if @dynflow_task.resumable?
63
+ ForemanTasks.dynflow.world.execute(@dynflow_task.execution_plan.id)
66
64
  render json: { statusText: 'OK' }
67
65
  else
68
66
  render json: {}, status: :bad_request
@@ -70,10 +68,8 @@ module ForemanTasks
70
68
  end
71
69
 
72
70
  def unlock
73
- task = find_dynflow_task
74
- if task.paused?
75
- task.state = :stopped
76
- task.save!
71
+ if @dynflow_task.paused?
72
+ unlock_task(@dynflow_task)
77
73
  render json: { statusText: 'OK' }
78
74
  else
79
75
  render json: {}, status: :bad_request
@@ -81,9 +77,7 @@ module ForemanTasks
81
77
  end
82
78
 
83
79
  def force_unlock
84
- task = find_dynflow_task
85
- task.state = :stopped
86
- task.save!
80
+ unlock_task(@dynflow_task)
87
81
  render json: { statusText: 'OK' }
88
82
  end
89
83
 
@@ -98,6 +92,12 @@ module ForemanTasks
98
92
 
99
93
  private
100
94
 
95
+ def unlock_task(task)
96
+ task.state = :stopped
97
+ task.locks.destroy_all
98
+ task.save!
99
+ end
100
+
101
101
  def respond_with_tasks(scope)
102
102
  @tasks = filter(scope, paginate: false).with_duration
103
103
  csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :duration, :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'Duration', 'User'])
@@ -123,7 +123,7 @@ module ForemanTasks
123
123
  end
124
124
 
125
125
  def find_dynflow_task
126
- resource_scope.where(:type => 'ForemanTasks::Task::DynflowTask').find(params[:id])
126
+ @dynflow_task = resource_scope.where(:type => 'ForemanTasks::Task::DynflowTask').find(params[:id])
127
127
  end
128
128
 
129
129
  def filter(scope, paginate: true)
@@ -0,0 +1,27 @@
1
+ module Mutations
2
+ module RecurringLogics
3
+ class Cancel < BaseMutation
4
+ graphql_name 'CancelRecurringLogic'
5
+ description 'Cancels recurring logic and all its active tasks'
6
+ resource_class ::ForemanTasks::RecurringLogic
7
+
8
+ argument :id, ID, required: true
9
+
10
+ field :errors, [Types::AttributeError], null: false
11
+ field :recurring_logic, Types::RecurringLogic, null: true
12
+
13
+ def resolve(id:)
14
+ recurring_logic = load_object_by(id: id)
15
+ authorize!(recurring_logic, :edit)
16
+ task_errors = []
17
+ begin
18
+ recurring_logic.cancel
19
+ rescue => e
20
+ task_errors = [{ path: ['tasks'], message: "There has been an error when canceling one of the tasks: #{e}" }]
21
+ end
22
+ errors = recurring_logic.errors.any? ? map_errors_to_path(recurring_logic) : []
23
+ { recurring_logic: recurring_logic, errors: (errors + task_errors) }
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,6 +3,8 @@ module Types
3
3
  description 'A Recurring Logic'
4
4
  model_class ::ForemanTasks::RecurringLogic
5
5
 
6
+ include ::Types::Concerns::MetaField
7
+
6
8
  global_id_field :id
7
9
  field :cron_line, String
8
10
  field :end_time, GraphQL::Types::ISO8601DateTime
@@ -29,10 +29,10 @@ module Actions
29
29
  default_connection_options.each do |key, value|
30
30
  options[:connection_options][key] = value unless options[:connection_options].key?(key)
31
31
  end
32
- plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s, :proxy_version => proxy_version(proxy)))
32
+ plan_self(options.merge(:proxy_url => proxy.url, :proxy_action_name => klass.to_s))
33
33
  # Just saving the RemoteTask is enough when using batch triggering
34
34
  # It will be picked up by the ProxyBatchTriggering middleware
35
- if input[:use_batch_triggering] && with_batch_triggering?(input[:proxy_version])
35
+ if input[:use_batch_triggering] && input.dig(:connection_options, :proxy_batch_triggering)
36
36
  prepare_remote_task.save!
37
37
  end
38
38
  end
@@ -193,11 +193,6 @@ module Actions
193
193
  :proxy_batch_triggering => Setting['foreman_tasks_proxy_batch_trigger'] || false }
194
194
  end
195
195
 
196
- def with_batch_triggering?(proxy_version)
197
- ((proxy_version[:major] == 1 && proxy_version[:minor] > 20) || proxy_version[:major] > 1) &&
198
- input.fetch(:connection_options, {}).fetch(:proxy_batch_triggering, false)
199
- end
200
-
201
196
  def clean_remote_task(*_args)
202
197
  remote_task.destroy! if remote_task
203
198
  end
@@ -222,11 +217,6 @@ module Actions
222
217
  .try(:fetch, 'output', {}) || {}
223
218
  end
224
219
 
225
- def proxy_version(proxy)
226
- match = proxy.statuses[:version].version['version'].match(/(\d+)\.(\d+)\.(\d+)/)
227
- { :major => match[1].to_i, :minor => match[2].to_i, :patch => match[3].to_i }
228
- end
229
-
230
220
  def failed_proxy_tasks
231
221
  metadata[:failed_proxy_tasks] ||= []
232
222
  end
@@ -40,7 +40,8 @@ module Actions
40
40
  end
41
41
  output[:planned_count] += batch.size
42
42
  rescue => e
43
- action_logger.warn "Could not trigger task on the smart proxy: #{e.message}"
43
+ action_logger.warn "Could not trigger task on the smart proxy"
44
+ action_logger.warn e
44
45
  batch.each { |remote_task| remote_task.update_from_batch_trigger({}) }
45
46
  output[:failed_count] += batch.size
46
47
  end
@@ -16,9 +16,10 @@ module ForemanTasks
16
16
  # Triggers a task on the proxy "the old way"
17
17
  def trigger(proxy_action_name, input)
18
18
  response = begin
19
- proxy.trigger_task(proxy_action_name, input).merge('result' => 'success')
19
+ proxy.launch_tasks('single', :action_class => proxy_action_name, :action_input => input)
20
20
  rescue RestClient::Exception => e
21
- logger.warn "Could not trigger task on the smart proxy: #{e.message}"
21
+ logger.warn "Could not trigger task on the smart proxy"
22
+ logger.warn e
22
23
  {}
23
24
  end
24
25
  update_from_batch_trigger(response)
@@ -26,33 +27,17 @@ module ForemanTasks
26
27
  end
27
28
 
28
29
  def self.batch_trigger(operation, remote_tasks)
29
- remote_tasks.group_by(&:proxy_url).values.map do |group|
30
+ remote_tasks.group_by(&:proxy_url).each_value do |group|
30
31
  input_hash = group.reduce({}) do |acc, remote_task|
31
32
  acc.merge(remote_task.execution_plan_id => { :action_input => remote_task.proxy_input,
32
33
  :action_class => remote_task.proxy_action_name })
33
34
  end
34
- safe_batch_trigger(operation, group, input_hash)
35
+ results = group.first.proxy.launch_tasks(operation, input_hash)
36
+ group.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
35
37
  end
36
38
  remote_tasks
37
39
  end
38
40
 
39
- # Attempt to trigger the tasks using the new API and fall back to the old one
40
- # if it fails
41
- def self.safe_batch_trigger(operation, remote_tasks, input_hash)
42
- results = remote_tasks.first.proxy.launch_tasks(operation, input_hash)
43
- remote_tasks.each { |remote_task| remote_task.update_from_batch_trigger results[remote_task.execution_plan_id] }
44
- rescue RestClient::NotFound
45
- fallback_batch_trigger remote_tasks, input_hash
46
- end
47
-
48
- # Trigger the tasks one-by-one using the old API
49
- def self.fallback_batch_trigger(remote_tasks, input_hash)
50
- remote_tasks.each do |remote_task|
51
- task_data = input_hash[remote_task.execution_plan_id]
52
- remote_task.trigger(task_data[:action_class], task_data[:action_input])
53
- end
54
- end
55
-
56
41
  def update_from_batch_trigger(data)
57
42
  if data['result'] == 'success'
58
43
  self.remote_task_id = data['task_id']
@@ -40,4 +40,8 @@
40
40
  <th><%= N_("Purpose") %></th>
41
41
  <td><%= recurring_logic.purpose %></td>
42
42
  </tr>
43
+ <tr>
44
+ <th><%= N_("Task count") %></th>
45
+ <td><%= link_to(task_group.tasks.count, foreman_tasks_tasks_url(:search => "task_group.id = #{task_group.id}")) %></td>
46
+ </tr>
43
47
  </table>
@@ -21,6 +21,21 @@ function build_rake() {
21
21
  echo
22
22
  }
23
23
 
24
+ function incorrect_usage() {
25
+ echo "$1" >&2
26
+ echo
27
+ usage
28
+
29
+ exit 1
30
+ }
31
+
32
+ function validate_options!() {
33
+ if [ -z "$TASK_SEARCH" ]; then
34
+ [ -n "$AFTER" ] && incorrect_usage "Error: -a|--after cannot be used without -s|--search"
35
+ [ -n "$STATES" ] && incorrect_usage "Error: -S|--states cannot be used without -s|--search"
36
+ fi
37
+ }
38
+
24
39
  function usage() {
25
40
  cat << EOF
26
41
  Usage: $PROGNAME [script_options...] [options...]
@@ -43,8 +58,8 @@ EOF
43
58
  echo Cleanup options:
44
59
  cat <<EOF | column -s\& -t
45
60
  -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
61
+ -S|--states STATES & operate on tasks in STATES, comma separated list of states, set to all to operate on tasks in any state. Has to be used together with -s|--search
62
+ -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. Has to be used together with -s|--search
48
63
  -b|--backup & backup deleted tasks
49
64
  -n|--noop & do a dry run, print what would be done
50
65
  -s|--search QUERY & use QUERY in scoped search format to match tasks to delete
@@ -119,6 +134,8 @@ while true; do
119
134
  shift
120
135
  done
121
136
 
137
+ validate_options!
138
+
122
139
  if [ "$EXECUTE" -eq 1 ]; then
123
140
  build_rake | sh
124
141
  else
@@ -14,7 +14,7 @@ function die() {
14
14
  function build_rake() {
15
15
  echo -n "$RAKE_COMMAND "
16
16
  echo -n 'foreman_tasks:export_tasks '
17
- for env in TASK_SEARCH TASK_FILE TASK_FORMAT TASK_DAYS; do
17
+ for env in TASK_SEARCH TASK_FILE TASK_FORMAT TASK_DAYS SKIP_FAILED; do
18
18
  local value="${!env}"
19
19
  [ -n "${value}" ] && echo -n "${env}=$(printf '%q' "$value") "
20
20
  done
@@ -46,11 +46,12 @@ EOF
46
46
  -f|--format FORMAT & export tasks in FORMAT, one of html, html-dir, csv
47
47
  -o|--output FILE & export tasks into FILE, a random file will be used if not provided
48
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
49
50
  EOF
50
51
  }
51
52
 
52
- SHORTOPTS="d:Ehs:o:f:"
53
- LONGOPTS="days:,execute,help,search:,output:,format:"
53
+ SHORTOPTS="d:Ehs:o:f:S"
54
+ LONGOPTS="days:,execute,help,search:,output:,format:,skip-failed"
54
55
 
55
56
  ARGS=$(getopt -s bash \
56
57
  --options $SHORTOPTS \
@@ -96,6 +97,9 @@ while true; do
96
97
  -E|--execute)
97
98
  EXECUTE=1
98
99
  ;;
100
+ -S|--skip-failed)
101
+ SKIP_FAILED=1
102
+ ;;
99
103
  \?)
100
104
  die 1 "Invalid option: -$OPTARG"
101
105
  ;;
@@ -20,9 +20,7 @@ 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")
@@ -70,6 +70,8 @@ module ForemanTasks
70
70
  register_graphql_query_field :recurring_logic, '::Types::RecurringLogic', :record_field
71
71
  register_graphql_query_field :recurring_logics, '::Types::RecurringLogic', :collection_field
72
72
 
73
+ register_graphql_mutation_field :cancel_recurring_logic, ::Mutations::RecurringLogics::Cancel
74
+
73
75
  logger :dynflow, :enabled => true
74
76
  logger :action, :enabled => true
75
77
 
@@ -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
@@ -240,30 +241,42 @@ namespace :foreman_tasks do
240
241
  end
241
242
  end
242
243
 
243
- def csv_export(export_filename, tasks)
244
+ def csv_export(export_filename, id_scope, task_scope)
244
245
  CSV.open(export_filename, 'wb') do |csv|
245
246
  csv << %w[id state type label result parent_task_id started_at ended_at duration]
246
- tasks.find_each do |task|
247
- csv << [task.id, task.state, task.type, task.label, task.result,
248
- task.parent_task_id, task.started_at, task.ended_at, task.duration]
247
+ id_scope.pluck(:id).each_slice(1000).each do |ids|
248
+ task_scope.where(id: ids).each do |task|
249
+ with_error_handling(task) do
250
+ csv << [task.id, task.state, task.type, task.label, task.result,
251
+ task.parent_task_id, task.started_at, task.ended_at, task.duration]
252
+ end
253
+ end
249
254
  end
250
255
  end
251
256
  end
252
257
 
253
- def html_export(workdir, tasks)
258
+ def html_export(workdir, id_scope, task_scope)
254
259
  PageHelper.copy_assets(workdir)
255
260
 
261
+ ids = id_scope.pluck(:id)
256
262
  renderer = TaskRender.new
257
- total = tasks.count(:all)
263
+ count = 0
264
+ total = ids.count
258
265
  index = File.open(File.join(workdir, 'index.html'), 'w')
259
266
 
260
267
  File.open(File.join(workdir, 'index.html'), 'w') do |index|
261
268
  PageHelper.pagify(index) do |io|
262
269
  PageHelper.generate_with_index(io) do |index|
263
- tasks.find_each.each_with_index do |task, count|
264
- File.open(File.join(workdir, "#{task.id}.html"), 'w') { |file| PageHelper.pagify(file, renderer.render_task(task)) }
265
- PageHelper.generate_index_entry(index, task)
266
- puts "#{count + 1}/#{total}"
270
+ ids.each_slice(1000).each do |ids|
271
+ task_scope.where(id: ids).each do |task|
272
+ content = with_error_handling(task) { renderer.render_task(task) }
273
+ if content
274
+ File.open(File.join(workdir, "#{task.id}.html"), 'w') { |file| PageHelper.pagify(file, content) }
275
+ with_error_handling(task, _('task index entry')) { PageHelper.generate_index_entry(index, task) }
276
+ end
277
+ count += 1
278
+ puts "#{count}/#{total}"
279
+ end
267
280
  end
268
281
  end
269
282
  end
@@ -282,6 +295,20 @@ namespace :foreman_tasks do
282
295
  end
283
296
  end
284
297
 
298
+ def with_error_handling(task, what = _('task'))
299
+ yield
300
+ rescue StandardError => e
301
+ resolution = SKIP_ERRORS ? _(', skipping') : ''
302
+ puts _("WARNING: %{what} failed to export%{resolution}. Additional task details below.") % { :what => what, :resolution => resolution }
303
+ puts task.inspect
304
+ unless SKIP_ERRORS
305
+ puts _("Re-run with SKIP_FAILED=true if you want to simply skip any tasks that fail to export.")
306
+ raise e
307
+ end
308
+ end
309
+
310
+ SKIP_ERRORS = ['true', '1', 'y', 'yes'].include? (ENV['SKIP_FAILED'] || '').downcase
311
+
285
312
  filter = if ENV['TASK_SEARCH'].nil? && ENV['TASK_DAYS'].nil?
286
313
  "started_at > \"#{7.days.ago.to_s(:db)}\" || " \
287
314
  "(result != success && started_at > \"#{60.days.ago.to_s(:db)})\""
@@ -297,21 +324,22 @@ namespace :foreman_tasks do
297
324
  format = ENV['TASK_FORMAT'] || 'html'
298
325
  export_filename = ENV['TASK_FILE'] || generate_filename(format)
299
326
 
300
- tasks = ForemanTasks::Task.search_for(filter).order(:started_at => :desc).with_duration.distinct
327
+ task_scope = ForemanTasks::Task.search_for(filter).with_duration.order(:started_at => :desc)
328
+ id_scope = task_scope.group(:id, :started_at)
301
329
 
302
330
  puts _("Exporting all tasks matching filter #{filter}")
303
- puts _("Gathering #{tasks.count(:all)} tasks.")
331
+ puts _("Gathering #{id_scope.count(:all).count} tasks.")
304
332
  case format
305
333
  when 'html'
306
334
  Dir.mktmpdir('task-export') do |tmp_dir|
307
- html_export(tmp_dir, tasks)
335
+ html_export(tmp_dir, id_scope, task_scope)
308
336
  system("tar", "czf", export_filename, tmp_dir)
309
337
  end
310
338
  when 'html-dir'
311
339
  FileUtils.mkdir_p(export_filename)
312
- html_export(export_filename, tasks)
340
+ html_export(export_filename, id_scope, task_scope)
313
341
  when 'csv'
314
- csv_export(export_filename, tasks)
342
+ csv_export(export_filename, id_scope, task_scope)
315
343
  else
316
344
  raise "Unkonwn export format '#{format}'"
317
345
  end
@@ -1,3 +1,3 @@
1
1
  module ForemanTasks
2
- VERSION = '5.1.1'.freeze
2
+ VERSION = '5.2.2'.freeze
3
3
  end
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": ">= 6.0.0"
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": "^6.0.0",
36
- "@theforeman/eslint-plugin-foreman": "6.0.0",
37
- "@theforeman/stories": "^6.0.0",
38
- "@theforeman/test": "^6.0.0",
39
- "@theforeman/vendor-dev": "^6.0.0",
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",
@@ -192,7 +192,7 @@ module ForemanTasks
192
192
  _(task.state).must_equal 'running'
193
193
  _(task.result).must_equal 'pending'
194
194
 
195
- callback = Support::DummyProxyAction.proxy.log[:trigger_task].first[1][:callback]
195
+ callback = Support::DummyProxyAction.proxy.log[:trigger_task].first[1][:action_input][:callback]
196
196
  post :callback, params: { 'callback' => callback, 'data' => { 'result' => 'success' } }
197
197
  triggered.finished.wait(5)
198
198
 
@@ -1,8 +1,14 @@
1
1
  FactoryBot.define do
2
2
  factory :recurring_logic, :class => ForemanTasks::RecurringLogic do
3
3
  cron_line { '* * * * *' }
4
- after(:build) { |logic| logic.task_group = build(:recurring_logic_task_group) }
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
@@ -36,6 +36,12 @@ module Support
36
36
  def statuses
37
37
  { version: DummyProxyVersion.new('1.21.0') }
38
38
  end
39
+
40
+ def launch_tasks(operation, args = {})
41
+ @log[:trigger_task] << [operation, args]
42
+ @task_triggered.fulfill(true)
43
+ { 'task_id' => @uuid, 'result' => 'success' }
44
+ end
39
45
  end
40
46
 
41
47
  class ProxySelector < ::ForemanTasks::ProxySelector
@@ -11,12 +11,11 @@ module ForemanTasks
11
11
  let(:batch_triggering) { false }
12
12
 
13
13
  before do
14
- Support::DummyProxyAction.any_instance.stubs(:with_batch_triggering?).returns(batch_triggering)
15
14
  Support::DummyProxyAction.reset
16
15
  RemoteTask.any_instance.stubs(:proxy).returns(Support::DummyProxyAction.proxy)
17
16
  Setting.stubs(:[]).with('foreman_tasks_proxy_action_retry_interval')
18
17
  Setting.stubs(:[]).with('foreman_tasks_proxy_action_retry_count')
19
- Setting.stubs(:[]).with('foreman_tasks_proxy_batch_trigger')
18
+ Setting.stubs(:[]).with('foreman_tasks_proxy_batch_trigger').returns(batch_triggering)
20
19
  @action = create_and_plan_action(Support::DummyProxyAction,
21
20
  Support::DummyProxyAction.proxy,
22
21
  'Proxy::DummyAction',
@@ -29,17 +28,18 @@ module ForemanTasks
29
28
  describe 'first run' do
30
29
  it 'triggers the corresponding action on the proxy' do
31
30
  proxy_call = Support::DummyProxyAction.proxy.log[:trigger_task].first
32
- expected_call = ['Proxy::DummyAction',
33
- { 'foo' => 'bar',
34
- 'secrets' => secrets,
35
- 'connection_options' =>
31
+ expected_call = ['single',
32
+ { :action_class => 'Proxy::DummyAction',
33
+ :action_input =>
34
+ { 'foo' => 'bar',
35
+ 'secrets' => secrets,
36
+ 'connection_options' =>
36
37
  { 'retry_interval' => 15, 'retry_count' => 4,
37
38
  'proxy_batch_triggering' => batch_triggering },
38
- 'use_batch_triggering' => batch_triggering,
39
- 'proxy_url' => 'proxy.example.com',
40
- 'proxy_action_name' => 'Proxy::DummyAction',
41
- "proxy_version" => { "major" => 1, "minor" => 21, "patch" => 0 },
42
- 'callback' => { 'task_id' => Support::DummyProxyAction.proxy.uuid, 'step_id' => @action.run_step_id } }]
39
+ 'use_batch_triggering' => batch_triggering,
40
+ 'proxy_url' => 'proxy.example.com',
41
+ 'proxy_action_name' => 'Proxy::DummyAction',
42
+ 'callback' => { 'task_id' => Support::DummyProxyAction.proxy.uuid, 'step_id' => @action.run_step_id } } }]
43
43
  _(proxy_call).must_equal(expected_call)
44
44
  end
45
45
 
@@ -29,12 +29,30 @@ module ForemanTasks
29
29
  end
30
30
  end
31
31
 
32
- it 'fallbacks to old way when batch trigger gets 404' do
32
+ it 'honors the batches with multiple proxies' do
33
+ remote_task = remote_tasks.last
34
+ remote_task.proxy_url = 'something else'
35
+
36
+ results = remote_tasks.reduce({}) do |acc, cur|
37
+ acc.merge(cur.execution_plan_id.to_s => { 'task_id' => cur.id + 5, 'result' => 'success' })
38
+ end
39
+ other_results = { remote_task.execution_plan_id => results.delete(remote_task.execution_plan_id) }
40
+
33
41
  fake_proxy = mock
34
- fake_proxy.expects(:launch_tasks).raises(RestClient::NotFound.new)
42
+ fake_proxy.expects(:launch_tasks).returns(results)
43
+
44
+ another_fake_proxy = mock
45
+ another_fake_proxy.expects(:launch_tasks).returns(other_results)
46
+
35
47
  remote_tasks.first.expects(:proxy).returns(fake_proxy)
36
- remote_tasks.each { |task| task.expects(:trigger) }
48
+ remote_tasks.last.expects(:proxy).returns(another_fake_proxy)
49
+
37
50
  RemoteTask.batch_trigger('a_operation', remote_tasks)
51
+ remote_tasks.each do |remote_task|
52
+ remote_task.reload
53
+ _(remote_task.state).must_equal 'triggered'
54
+ _(remote_task.remote_task_id).must_equal((remote_task.id + 5).to_s)
55
+ end
38
56
  end
39
57
  end
40
58
  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: 5.1.1
4
+ version: 5.2.2
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: 2021-09-21 00:00:00.000000000 Z
11
+ date: 1980-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dynflow
@@ -133,6 +133,7 @@ files:
133
133
  - app/controllers/foreman_tasks/react_controller.rb
134
134
  - app/controllers/foreman_tasks/recurring_logics_controller.rb
135
135
  - app/controllers/foreman_tasks/tasks_controller.rb
136
+ - app/graphql/mutations/recurring_logics/cancel.rb
136
137
  - app/graphql/types/recurring_logic.rb
137
138
  - app/graphql/types/task.rb
138
139
  - app/graphql/types/triggering.rb
@@ -304,6 +305,7 @@ files:
304
305
  - test/factories/task_factory.rb
305
306
  - test/factories/triggering_factory.rb
306
307
  - test/foreman_tasks_test_helper.rb
308
+ - test/graphql/mutations/recurring_logics/cancel_mutation_test.rb
307
309
  - test/graphql/queries/recurring_logic_test.rb
308
310
  - test/graphql/queries/recurring_logics_query_test.rb
309
311
  - test/graphql/queries/task_query_test.rb
@@ -605,7 +607,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
605
607
  - !ruby/object:Gem::Version
606
608
  version: '0'
607
609
  requirements: []
608
- rubygems_version: 3.1.2
610
+ rubygems_version: 3.2.26
609
611
  signing_key:
610
612
  specification_version: 4
611
613
  summary: Foreman plugin for showing tasks information for resources and users
@@ -618,6 +620,7 @@ test_files:
618
620
  - test/factories/task_factory.rb
619
621
  - test/factories/triggering_factory.rb
620
622
  - test/foreman_tasks_test_helper.rb
623
+ - test/graphql/mutations/recurring_logics/cancel_mutation_test.rb
621
624
  - test/graphql/queries/recurring_logic_test.rb
622
625
  - test/graphql/queries/recurring_logics_query_test.rb
623
626
  - test/graphql/queries/task_query_test.rb