foreman-tasks 4.1.4 → 5.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_tests.yml +0 -1
  3. data/app/assets/javascripts/foreman_tasks/trigger_form.js +7 -0
  4. data/app/controllers/foreman_tasks/api/tasks_controller.rb +3 -3
  5. data/app/controllers/foreman_tasks/tasks_controller.rb +2 -2
  6. data/app/graphql/types/recurring_logic.rb +19 -0
  7. data/app/graphql/types/task.rb +25 -0
  8. data/app/graphql/types/triggering.rb +16 -0
  9. data/app/helpers/foreman_tasks/foreman_tasks_helper.rb +4 -1
  10. data/app/lib/actions/helpers/with_continuous_output.rb +1 -1
  11. data/app/lib/actions/trigger_proxy_batch.rb +79 -0
  12. data/app/models/foreman_tasks/recurring_logic.rb +10 -0
  13. data/app/models/foreman_tasks/task.rb +29 -0
  14. data/app/models/foreman_tasks/triggering.rb +14 -4
  15. data/app/views/foreman_tasks/api/tasks/show.json.rabl +1 -1
  16. data/app/views/foreman_tasks/layouts/react.html.erb +0 -1
  17. data/app/views/foreman_tasks/recurring_logics/index.html.erb +4 -2
  18. data/app/views/foreman_tasks/task_groups/recurring_logic_task_groups/_recurring_logic_task_group.html.erb +4 -0
  19. data/db/migrate/20210720115251_add_purpose_to_recurring_logic.rb +6 -0
  20. data/extra/foreman-tasks-cleanup.sh +127 -0
  21. data/extra/foreman-tasks-export.sh +117 -0
  22. data/foreman-tasks.gemspec +0 -1
  23. data/lib/foreman_tasks/continuous_output.rb +50 -0
  24. data/lib/foreman_tasks/engine.rb +6 -15
  25. data/lib/foreman_tasks/tasks/export_tasks.rake +6 -5
  26. data/lib/foreman_tasks/version.rb +1 -1
  27. data/lib/foreman_tasks.rb +2 -5
  28. data/test/controllers/api/tasks_controller_test.rb +29 -0
  29. data/test/controllers/tasks_controller_test.rb +19 -0
  30. data/test/graphql/queries/recurring_logic_test.rb +28 -0
  31. data/test/graphql/queries/recurring_logics_query_test.rb +30 -0
  32. data/test/graphql/queries/task_query_test.rb +33 -0
  33. data/test/graphql/queries/tasks_query_test.rb +31 -0
  34. data/test/unit/actions/trigger_proxy_batch_test.rb +59 -0
  35. data/test/unit/task_test.rb +39 -8
  36. data/test/unit/triggering_test.rb +22 -0
  37. data/webpack/ForemanTasks/Components/TasksTable/TasksTableReducer.js +7 -14
  38. data/webpack/ForemanTasks/Components/TasksTable/__tests__/TasksTableReducer.test.js +3 -1
  39. data/webpack/ForemanTasks/Components/TasksTable/__tests__/__snapshots__/TasksTableReducer.test.js.snap +2 -0
  40. metadata +20 -26
  41. data/test/core/unit/dispatcher_test.rb +0 -43
  42. data/test/core/unit/runner_test.rb +0 -116
  43. data/test/core/unit/task_launcher_test.rb +0 -56
  44. data/test/foreman_tasks_core_test_helper.rb +0 -4
  45. data/test/unit/otp_manager_test.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3152af7ac771946d9090d5a1b867c6843ff6657ab2434c4daba986c60c83579e
4
- data.tar.gz: 4d3cd7b0d58af7f59a5d7745e1b75444dc43066089c60854344c49dc1d58ed43
3
+ metadata.gz: c191bf6093506d0c5346c71a026908b3c5c6b10fa9ceb27e5fdfbfa93a72c2c6
4
+ data.tar.gz: 0fc1f15e955f6d13469aa28883db8447f8e3770d4751206faaec5c224d3ffcbc
5
5
  SHA512:
6
- metadata.gz: 528a33eb7ef2128cdc476411c705762b3b63ba22d8a3ff3ca70755a42247c5a538494b1b248eee547a2a7ffa49821612f05f0f53e42ef026a70e107e064246f1
7
- data.tar.gz: 85033e3dd3c84eb6aa8dd99e837ad024acd42639c1351f518883ef431b9b95c9ddb833f11e6708bda488113c1a33faf7959ac4a11bbeb4ea16e29a32c955e670
6
+ metadata.gz: d57a47e629be8d4bcfaba9bc853f7ec0853ea8f968884d4b589e98d74027a73bcef5ff45ff53f3e01f8794a28a01729bb4c811b004430296304a86b7936d4bd4
7
+ data.tar.gz: e4e3b07b3e4f84e233534c40adadaa8c4032c33e5bd5a8623a8d54bcece9de022c1c4e2fd81ca0f3ed1c9e512df23dc14b5d2fe8be349de03df1a5b4a41c7235
@@ -72,5 +72,4 @@ jobs:
72
72
  - name: Run plugin tests
73
73
  run: |
74
74
  bundle exec rake test:foreman_tasks
75
- bundle exec rake test:foreman_tasks_core
76
75
  bundle exec rake test TEST="test/unit/foreman/access_permissions_test.rb"
@@ -8,6 +8,7 @@ function trigger_form_selector_binds(form_name, form_object_name) {
8
8
  form.find('fieldset.trigger_mode_form#trigger_mode_' + type).hide();
9
9
  });
10
10
  form.find('fieldset.trigger_mode_form#trigger_mode_' + $(this).val()).show();
11
+ disable_hidden_start_field($(this).val(), form)
11
12
  });
12
13
 
13
14
  input_type_selector.on('change', function () {
@@ -39,3 +40,9 @@ function trigger_form_selector_binds(form_name, form_object_name) {
39
40
  };
40
41
  });
41
42
  };
43
+
44
+ function disable_hidden_start_field(clicked, form) {
45
+ ["future", "recurring"].forEach(function(type) {
46
+ form.find('.trigger_mode_form#trigger_mode_' + type + ' #triggering_start_at_raw').prop('disabled', type !== clicked);
47
+ });
48
+ };
@@ -260,7 +260,7 @@ module ForemanTasks
260
260
  raise BadRequest,
261
261
  _('Resource search_params requires resource_type and resource_id to be specified')
262
262
  end
263
- scope.joins(:locks).where(foreman_tasks_locks:
263
+ scope.joins(:links).where(foreman_tasks_links:
264
264
  { resource_type: search_params[:resource_type],
265
265
  resource_id: search_params[:resource_id] })
266
266
  when 'task'
@@ -320,7 +320,7 @@ module ForemanTasks
320
320
  end
321
321
 
322
322
  def find_task
323
- @task = resource_scope.find(params[:id])
323
+ @task = resource_scope.with_duration.find(params[:id])
324
324
  end
325
325
 
326
326
  def resource_scope(_options = {})
@@ -330,7 +330,7 @@ module ForemanTasks
330
330
  end
331
331
 
332
332
  def resource_scope_for_index(*args)
333
- super.select("DISTINCT foreman_tasks_tasks.*, coalesce(ended_at, current_timestamp) - coalesce(coalesce(started_at, ended_at), current_timestamp) as duration")
333
+ super.with_duration.distinct
334
334
  end
335
335
 
336
336
  def controller_permission
@@ -99,8 +99,8 @@ module ForemanTasks
99
99
  private
100
100
 
101
101
  def respond_with_tasks(scope)
102
- @tasks = filter(scope, paginate: false)
103
- csv_response(@tasks, [:id, :action, :state, :result, 'started_at.in_time_zone', 'ended_at.in_time_zone', :username], ['Id', 'Action', 'State', 'Result', 'Started At', 'Ended At', 'User'])
102
+ @tasks = filter(scope, paginate: false).with_duration
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'])
104
104
  end
105
105
 
106
106
  def controller_permission
@@ -0,0 +1,19 @@
1
+ module Types
2
+ class RecurringLogic < Types::BaseObject
3
+ description 'A Recurring Logic'
4
+ model_class ::ForemanTasks::RecurringLogic
5
+
6
+ global_id_field :id
7
+ field :cron_line, String
8
+ field :end_time, GraphQL::Types::ISO8601DateTime
9
+ field :max_iteration, Integer
10
+ field :iteration, Integer
11
+ field :state, String
12
+ field :purpose, String
13
+ belongs_to :triggering, Types::Triggering
14
+
15
+ def self.graphql_definition
16
+ super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::RecurringLogic') }
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module Types
2
+ class Task < Types::BaseObject
3
+ description 'A Task'
4
+ model_class ::ForemanTasks::Task
5
+
6
+ global_id_field :id
7
+ field :type, String
8
+ field :label, String
9
+ field :started_at, GraphQL::Types::ISO8601DateTime
10
+ field :ended_at, GraphQL::Types::ISO8601DateTime
11
+ field :state, String
12
+ field :result, String
13
+ field :external_id, String
14
+ field :parent_task_id, String
15
+ field :start_at, GraphQL::Types::ISO8601DateTime
16
+ field :start_before, GraphQL::Types::ISO8601DateTime
17
+ field :action, String
18
+ field :user_id, Integer
19
+ field :state_updated_at, GraphQL::Types::ISO8601DateTime
20
+
21
+ def self.graphql_definition
22
+ super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::Task') }
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,16 @@
1
+ module Types
2
+ class Triggering < Types::BaseObject
3
+ description 'A Task Triggering'
4
+ model_class ::ForemanTasks::Triggering
5
+
6
+ global_id_field :id
7
+ field :mode, String
8
+ field :start_at, GraphQL::Types::ISO8601DateTime
9
+ field :start_before, GraphQL::Types::ISO8601DateTime
10
+ field :recurring_logic, Types::RecurringLogic
11
+
12
+ def self.graphql_definition
13
+ super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::Triggering') }
14
+ end
15
+ end
16
+ end
@@ -141,7 +141,10 @@ module ForemanTasks
141
141
  weekly_fieldset(f, triggering),
142
142
  time_picker_fieldset(f, triggering),
143
143
  ]
144
-
144
+ tags << text_f(f, :start_at_raw, :label => _('Start at'), :placeholder => 'YYYY-mm-dd HH:MM')
145
+ tags << text_f(f, :purpose,
146
+ :label => _('Purpose'),
147
+ :label_help => N_('A special label for tracking a recurring job. There can be only one active job with a given purpose at a time.'))
145
148
  content_tag(:fieldset, nil, :id => 'trigger_mode_recurring', :class => "trigger_mode_form #{'hidden' unless triggering.recurring?}") do
146
149
  tags.join.html_safe
147
150
  end
@@ -8,7 +8,7 @@ module Actions
8
8
  end
9
9
 
10
10
  def continuous_output
11
- continuous_output = ::ForemanTasksCore::ContinuousOutput.new
11
+ continuous_output = ::ForemanTasks::ContinuousOutput.new
12
12
  continuous_output_providers.each do |continous_output_provider|
13
13
  continous_output_provider.fill_continuous_output(continuous_output)
14
14
  end
@@ -0,0 +1,79 @@
1
+ module Actions
2
+ # This action plans proxy tasks in batches.
3
+ # It needs to be manually notified about the next batch being available by sending a TriggerNextBatch event.
4
+ #
5
+ # The ProxyAction needs to be planned with `:use_batch_triggering => true` to activate the feature
6
+ class TriggerProxyBatch < Base
7
+ TriggerNextBatch = Algebrick.type do
8
+ fields! batches: Integer
9
+ end
10
+ TriggerLastBatch = Algebrick.atom
11
+
12
+ def run(event = nil)
13
+ case event
14
+ when nil
15
+ if output[:planned_count]
16
+ check_finish
17
+ else
18
+ init_counts and suspend
19
+ end
20
+ when TriggerNextBatch
21
+ trigger_remote_tasks_batches(event.batches) and suspend
22
+ when TriggerLastBatch
23
+ trigger_remote_tasks_batch and on_finish
24
+ when ::Dynflow::Action::Skip
25
+ # do nothing
26
+ end
27
+ end
28
+
29
+ def trigger_remote_tasks_batches(amount = 1)
30
+ amount.times { trigger_remote_tasks_batch }
31
+ end
32
+
33
+ def trigger_remote_tasks_batch
34
+ # Find the tasks in batches, order them by proxy_url so we get all tasks
35
+ # to a certain proxy "close to each other"
36
+ batch = remote_tasks.pending.order(:proxy_url, :id).first(batch_size)
37
+ # Group the tasks by operation, in theory there should be only one operation
38
+ batch.group_by(&:operation).each do |operation, group|
39
+ ForemanTasks::RemoteTask.batch_trigger(operation, group)
40
+ end
41
+ output[:planned_count] += batch.size
42
+ rescue => e
43
+ action_logger.warn "Could not trigger task on the smart proxy: #{e.message}"
44
+ batch.each { |remote_task| remote_task.update_from_batch_trigger({}) }
45
+ output[:failed_count] += batch.size
46
+ end
47
+
48
+ def init_counts
49
+ output[:planned_count] = 0
50
+ output[:failed_count] = 0
51
+ end
52
+
53
+ def check_finish
54
+ if output[:planned_count] + output[:failed_count] + batch_size >= input[:total_count]
55
+ trigger_remote_tasks_batch and on_finish
56
+ else
57
+ suspend
58
+ end
59
+ end
60
+
61
+ def done?
62
+ output[:planned_count] + output[:failed_count] >= input[:total_count]
63
+ end
64
+
65
+ def remote_tasks
66
+ task.remote_sub_tasks
67
+ end
68
+
69
+ def on_finish
70
+ # nothing for now
71
+ end
72
+
73
+ private
74
+
75
+ def batch_size
76
+ input[:batch_size] || Setting['foreman_tasks_proxy_batch_size']
77
+ end
78
+ end
79
+ end
@@ -4,6 +4,8 @@ module ForemanTasks
4
4
  class RecurringLogic < ApplicationRecord
5
5
  include Authorizable
6
6
 
7
+ graphql_type '::Types::RecurringLogic'
8
+
7
9
  belongs_to :task_group
8
10
  belongs_to :triggering
9
11
 
@@ -15,6 +17,9 @@ module ForemanTasks
15
17
  scoped_search :on => :iteration, :complete_value => false
16
18
  scoped_search :on => :cron_line, :complete_value => true
17
19
  scoped_search :on => :state, :complete_value => true
20
+ scoped_search :on => :purpose, :complete_value => true
21
+
22
+ validate :valid_purpose
18
23
 
19
24
  before_create do
20
25
  task_group.save
@@ -167,6 +172,7 @@ module ForemanTasks
167
172
  ::ForemanTasks::RecurringLogic.new_from_cronline(cronline).tap do |manager|
168
173
  manager.end_time = triggering.end_time if triggering.end_time_limited.present?
169
174
  manager.max_iteration = triggering.max_iteration if triggering.max_iteration.present?
175
+ manager.purpose = triggering.purpose if triggering.purpose.present?
170
176
  manager.triggering = triggering
171
177
  end
172
178
  end
@@ -187,5 +193,9 @@ module ForemanTasks
187
193
  end
188
194
  hash.select { |key, _| allowed_keys.include? key }
189
195
  end
196
+
197
+ def valid_purpose?
198
+ !(purpose.present? && self.class.where(:purpose => purpose, :state => %w[active disabled]).any?)
199
+ end
190
200
  end
191
201
  end
@@ -5,6 +5,8 @@ module ForemanTasks
5
5
  include Authorizable
6
6
  extend Search
7
7
 
8
+ graphql_type '::Types::Task'
9
+
8
10
  def check_permissions_after_save
9
11
  # there's no create_tasks permission, tasks are created as a result of internal actions, in such case we
10
12
  # don't do authorization, that should have been performed on wrapping action level
@@ -76,6 +78,7 @@ module ForemanTasks
76
78
  :"foreman_tasks_links.resource_type" => resource.class.name)
77
79
  end)
78
80
  scope :for_action_types, (->(action_types) { where('foreman_tasks_tasks.label IN (?)', Array(action_types)) })
81
+ scope :with_duration, -> { select("foreman_tasks_tasks.*, coalesce(ended_at, current_timestamp) - coalesce(coalesce(started_at, ended_at), current_timestamp) as duration") }
79
82
 
80
83
  apipie :class, "A class representing #{model_name.human} object" do
81
84
  name 'Task'
@@ -255,6 +258,32 @@ module ForemanTasks
255
258
  main_action.continuous_output.raw_outputs
256
259
  end
257
260
 
261
+ def self.latest_tasks_by_resource_ids(label, resource_type, resource_ids)
262
+ tasks = arel_table
263
+ links = ForemanTasks::Link.arel_table
264
+ started_at = tasks[:started_at]
265
+ resource_id = links[:resource_id]
266
+
267
+ base_combined_table = tasks
268
+ .join(links).on(tasks[:id].eq(links[:task_id]))
269
+ .where(tasks[:label].eq(label)
270
+ .and(links[:resource_type].eq(resource_type))
271
+ .and(links[:resource_id].in(resource_ids)))
272
+
273
+ grouped = base_combined_table.project(
274
+ started_at.maximum.as('started_at_max'),
275
+ resource_id
276
+ ).group(resource_id).order(resource_id).as('grouped')
277
+
278
+ max_per_resource_id = tasks
279
+ .join(links).on(tasks[:id].eq(links[:task_id]))
280
+ .join(grouped).on(grouped[:started_at_max].eq(started_at).and(grouped[:resource_id].eq(resource_id)))
281
+ .distinct
282
+ .project(tasks[Arel.star], grouped[:resource_id])
283
+
284
+ find_by_sql(max_per_resource_id.to_sql).index_by(&:resource_id)
285
+ end
286
+
258
287
  protected
259
288
 
260
289
  def generate_id
@@ -2,9 +2,11 @@ module ForemanTasks
2
2
  class Triggering < ApplicationRecord
3
3
  PARAMS = [:start_at_raw, :start_before_raw, :max_iteration, :input_type,
4
4
  :cronline, :days, :days_of_week, :time, :end_time_limited,
5
- :end_time].freeze
5
+ :end_time, :purpose].freeze
6
6
  attr_accessor(*PARAMS)
7
7
 
8
+ graphql_type '::Types::Triggering'
9
+
8
10
  before_save do
9
11
  if future?
10
12
  parse_start_at!
@@ -28,7 +30,7 @@ module ForemanTasks
28
30
  validates :input_type, :if => :recurring?,
29
31
  :inclusion => { :in => ALLOWED_INPUT_TYPES,
30
32
  :message => _('%{value} is not allowed input type') }
31
- validates :start_at_raw, format: { :with => TIME_REGEXP, :if => :future?,
33
+ validates :start_at_raw, format: { :with => TIME_REGEXP, :if => ->(triggering) { triggering.future? || (triggering.recurring? && triggering.start_at_raw) },
32
34
  :message => _('%{value} is wrong format') }
33
35
  validates :start_before_raw, format: { :with => TIME_REGEXP, :if => :future?,
34
36
  :message => _('%{value} is wrong format'), :allow_blank => true }
@@ -68,7 +70,7 @@ module ForemanTasks
68
70
  delay_options,
69
71
  *args
70
72
  when :recurring
71
- recurring_logic.start(action, *args)
73
+ recurring_logic.start_after(action, delay_options[:start_at] || Time.zone.now, *args)
72
74
  end
73
75
  end
74
76
 
@@ -92,7 +94,13 @@ module ForemanTasks
92
94
  end
93
95
 
94
96
  def parse_start_at!
95
- self.start_at ||= Time.zone.parse(start_at_raw)
97
+ self.start_at ||= Time.zone.parse(start_at_raw) if start_at_raw.present?
98
+ end
99
+
100
+ def parse_start_at
101
+ self.start_at ||= Time.zone.parse(start_at_raw) if start_at_raw.present?
102
+ rescue ArgumentError
103
+ errors.add(:start_at, _('is not a valid format'))
96
104
  end
97
105
 
98
106
  def parse_start_before!
@@ -102,7 +110,9 @@ module ForemanTasks
102
110
  private
103
111
 
104
112
  def can_start_recurring
113
+ parse_start_at
105
114
  errors.add(:input_type, _('No task could be started')) unless recurring_logic.valid?
115
+ errors.add(:purpose, _('Active or disabled recurring logic with purpose %s already exists') % recurring_logic.purpose) unless recurring_logic.valid_purpose?
106
116
  errors.add(:cronline, _('%s is not valid format of cron line') % cronline) unless recurring_logic.valid_cronline?
107
117
  end
108
118
 
@@ -3,6 +3,6 @@ object @task if @task
3
3
  extends 'api/v2/layouts/permissions'
4
4
 
5
5
  attributes :id, :label, :pending, :action
6
- attributes :username, :started_at, :ended_at, :state, :result, :progress
6
+ attributes :username, :started_at, :ended_at, :duration, :state, :result, :progress
7
7
  attributes :input, :output, :humanized, :cli_example, :start_at
8
8
  node(:available_actions) { |t| { cancellable: t.execution_plan&.cancellable?, resumable: t.resumable? } }
@@ -6,7 +6,6 @@
6
6
  <% end %>
7
7
 
8
8
  <% content_for(:content) do %>
9
- <%= notifications %>
10
9
  <div id="organization-id" data-id="<%= Organization.current.id if Organization.current %>" ></div>
11
10
  <div id="user-id" data-id="<%= User.current.id if User.current %>" ></div>
12
11
  <%= react_component('ForemanTasks') %>
@@ -8,9 +8,9 @@
8
8
  <% if authorized_for(:permission => :edit_recurring_logics, :auth_object => @recurring_logics) %>
9
9
  <% title_actions link_to(_('Clear Cancelled'),
10
10
  clear_cancelled_foreman_tasks_recurring_logics_path,
11
- class: ['btn', 'btn-sm', 'btn-danger'],
11
+ class: ['btn', 'btn-sm', 'btn-danger'],
12
12
  :'data-toggle' => "modal",
13
- :'data-target' => "#clear_modal")
13
+ :'data-target' => "#clear_modal")
14
14
  %>
15
15
 
16
16
  <div class="modal fade" id="clear_modal" tabindex="-1" role="dialog" aria-labelledby="Deploy" aria-hidden="true">
@@ -47,6 +47,7 @@
47
47
  <th><%= N_("Iteration limit") %></th>
48
48
  <th><%= N_("Repeat until") %></th>
49
49
  <th><%= N_("State") %></th>
50
+ <th><%= N_("Purpose") %></th>
50
51
  <th/>
51
52
  </thead>
52
53
  <% @recurring_logics.each do |recurring_logic| %>
@@ -61,6 +62,7 @@
61
62
  <td><%= format_recurring_logic_limit recurring_logic.max_iteration %></td>
62
63
  <td><%= format_recurring_logic_limit recurring_logic.end_time.try(:in_time_zone) %></td>
63
64
  <td><%= recurring_logic_state(recurring_logic) %></td>
65
+ <td><%= recurring_logic.purpose %></td>
64
66
  <td><%= recurring_logic_action_buttons(recurring_logic) %></td>
65
67
  </tr>
66
68
  <% end %>
@@ -36,4 +36,8 @@
36
36
  <th><%= N_("State") %></th>
37
37
  <td><%= recurring_logic_state(recurring_logic) %></td>
38
38
  </tr>
39
+ <tr>
40
+ <th><%= N_("Purpose") %></th>
41
+ <td><%= recurring_logic.purpose %></td>
42
+ </tr>
39
43
  </table>
@@ -0,0 +1,6 @@
1
+ class AddPurposeToRecurringLogic < ActiveRecord::Migration[6.0]
2
+ def change
3
+ add_column :foreman_tasks_recurring_logics, :purpose, :string
4
+ add_index :foreman_tasks_recurring_logics, :purpose, unique: true, where: "state IN ('active', 'disabled')"
5
+ end
6
+ end
@@ -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
+