foreman-tasks 5.1.1 → 5.2.2

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 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