foreman-tasks 4.0.0 → 5.0.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_tests.yml +4 -3
  3. data/app/controllers/concerns/foreman_tasks/find_tasks_common.rb +14 -0
  4. data/app/controllers/foreman_tasks/api/tasks_controller.rb +4 -6
  5. data/app/controllers/foreman_tasks/tasks_controller.rb +3 -11
  6. data/app/graphql/types/recurring_logic.rb +18 -0
  7. data/app/graphql/types/task.rb +25 -0
  8. data/app/graphql/types/triggering.rb +16 -0
  9. data/app/lib/actions/helpers/with_continuous_output.rb +1 -1
  10. data/app/lib/actions/middleware/load_setting_values.rb +35 -0
  11. data/app/lib/actions/observable_action.rb +1 -1
  12. data/app/lib/actions/proxy_action.rb +2 -4
  13. data/app/models/foreman_tasks/recurring_logic.rb +2 -0
  14. data/app/models/foreman_tasks/task.rb +30 -2
  15. data/app/models/foreman_tasks/triggering.rb +2 -0
  16. data/db/migrate/20180927120509_add_user_id.foreman_tasks.rb +4 -2
  17. data/foreman-tasks.gemspec +0 -1
  18. data/lib/foreman_tasks.rb +2 -5
  19. data/lib/foreman_tasks/continuous_output.rb +50 -0
  20. data/lib/foreman_tasks/engine.rb +7 -15
  21. data/lib/foreman_tasks/version.rb +1 -1
  22. data/locale/action_names.rb +3 -2
  23. data/locale/en/foreman_tasks.po +60 -27
  24. data/locale/foreman_tasks.pot +180 -132
  25. data/locale/fr/foreman_tasks.po +61 -28
  26. data/locale/ja/foreman_tasks.po +61 -28
  27. data/locale/zh_CN/foreman_tasks.po +61 -28
  28. data/test/controllers/api/tasks_controller_test.rb +16 -1
  29. data/test/controllers/tasks_controller_test.rb +2 -2
  30. data/test/factories/task_factory.rb +31 -4
  31. data/test/graphql/queries/recurring_logic_test.rb +28 -0
  32. data/test/graphql/queries/recurring_logics_query_test.rb +30 -0
  33. data/test/graphql/queries/task_query_test.rb +33 -0
  34. data/test/graphql/queries/tasks_query_test.rb +31 -0
  35. data/test/unit/actions/proxy_action_test.rb +4 -1
  36. data/test/unit/task_test.rb +54 -23
  37. data/test/unit/triggering_test.rb +2 -2
  38. metadata +16 -26
  39. data/test/core/unit/dispatcher_test.rb +0 -43
  40. data/test/core/unit/runner_test.rb +0 -129
  41. data/test/core/unit/task_launcher_test.rb +0 -56
  42. data/test/foreman_tasks_core_test_helper.rb +0 -4
  43. data/test/unit/otp_manager_test.rb +0 -77
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bded348bac199b96bf0c1bf458c0785cdc233892acc1a5d06a940a8c5b28309b
4
- data.tar.gz: f90eaf365ebfb191b45ab12b4693c7253d829ffcf0c57192e8bb4c1fa3e51986
3
+ metadata.gz: 3e48966ae6049eb85b3051151b7d15d48beb91a55b3580525fe114a3e045ad0f
4
+ data.tar.gz: 93da446f348f1a27600a79223ba4daab0e5e83417db711919392cfab2eedf57a
5
5
  SHA512:
6
- metadata.gz: 41d60c3a188d66c0f7f3e9982c72de16f323d458529e2b90a16e8c023733acaedcf94a09ae30e94e54a66f4303f0b904a06d54c33b6db2af3acf49669c8b8b0f
7
- data.tar.gz: b0ec169bf37128b471263c189b839416d8ae572a69732d3a9d4dcb64d4e2cdf2aef53d423cb2a7eef1c5f50f0756d332066dc9a244788f2b8c05643e1ca73b0f
6
+ metadata.gz: 740ff700f75e43154068d3838611dc7c7306d7f5da91e3d2d41a6fce5ea0ccde84bd5f027545f24874b8fb112d9848505ae2a535386ee2f356852695dacd61a2
7
+ data.tar.gz: 8f61c3c868af4b7c3d3173b728858537c72ccf695e60b63971ec19f8a675a6ad9c19ecdd3dedb2549e2eb27b7cde62a2be67ead4d737bb43b774301abb303228
@@ -12,7 +12,7 @@ jobs:
12
12
  - name: Setup Ruby
13
13
  uses: ruby/setup-ruby@v1
14
14
  with:
15
- ruby-version: 2.6
15
+ ruby-version: 2.7
16
16
  - name: Setup
17
17
  run: |
18
18
  gem install bundler
@@ -31,7 +31,7 @@ jobs:
31
31
  fail-fast: false
32
32
  matrix:
33
33
  foreman-core-branch: [develop]
34
- ruby-version: [2.5, 2.6]
34
+ ruby-version: [2.5, 2.6, 2.7]
35
35
  node-version: [12]
36
36
  steps:
37
37
  - run: sudo apt-get update
@@ -60,6 +60,7 @@ jobs:
60
60
  - name: Setup Bundler
61
61
  run: |
62
62
  echo "gem 'foreman-tasks', path: './foreman-tasks'" > bundler.d/foreman-tasks.local.rb
63
+ echo "gem 'sqlite3'" >> bundler.d/foreman-tasks.local.rb
63
64
  gem install bundler
64
65
  bundle config set without journald development console libvirt
65
66
  bundle config set path vendor/bundle
@@ -70,5 +71,5 @@ jobs:
70
71
  bundle exec rake db:migrate
71
72
  - name: Run plugin tests
72
73
  run: |
73
- bundle exec rake test:foreman-tasks
74
+ bundle exec rake test:foreman_tasks
74
75
  bundle exec rake test TEST="test/unit/foreman/access_permissions_test.rb"
@@ -0,0 +1,14 @@
1
+ module ForemanTasks
2
+ module FindTasksCommon
3
+ def search_query
4
+ [current_taxonomy_search, params[:search]].select(&:present?).join(' AND ')
5
+ end
6
+
7
+ def current_taxonomy_search
8
+ conditions = []
9
+ conditions << "organization_id = #{Organization.current.id}" if Organization.current
10
+ conditions << "location_id = #{Location.current.id}" if Location.current
11
+ conditions.empty? ? '' : "(#{conditions.join(' AND ')})"
12
+ end
13
+ end
14
+ end
@@ -1,6 +1,7 @@
1
1
  module ForemanTasks
2
2
  module Api
3
3
  class TasksController < ::Api::V2::BaseController
4
+ include ForemanTasks::FindTasksCommon
4
5
  include ::Foreman::Controller::SmartProxyAuth
5
6
  add_smart_proxy_filters :callback, :features => 'Dynflow'
6
7
 
@@ -51,7 +52,7 @@ module ForemanTasks
51
52
  In case :type = 'resource', what resource id we're searching the tasks for
52
53
  DESC
53
54
  param :action_types, [String], :desc => <<-DESC
54
- Return just tasks of given action type, e.g. ["Actions::Katello::Repository::Synchronize"]
55
+ Return just tasks of given action type, e.g. `["Actions::Katello::Repository::Synchronize"]`
55
56
  DESC
56
57
  param :active_only, :bool
57
58
  param :page, String
@@ -195,7 +196,7 @@ module ForemanTasks
195
196
  end
196
197
 
197
198
  def search_options
198
- [params[:search], {}]
199
+ [search_query, {}]
199
200
  end
200
201
 
201
202
  def_param_group :callback_target do
@@ -253,10 +254,7 @@ module ForemanTasks
253
254
  if search_params[:user_id].blank?
254
255
  raise BadRequest, _('User search_params requires user_id to be specified')
255
256
  end
256
- scope.joins(:locks).where(foreman_tasks_locks:
257
- { name: ::ForemanTasks::Lock::OWNER_LOCK_NAME,
258
- resource_type: 'User',
259
- resource_id: search_params[:user_id] })
257
+ scope.search_for("user_id = #{search_params[:user_id]}")
260
258
  when 'resource'
261
259
  if search_params[:resource_type].blank? || search_params[:resource_id].blank?
262
260
  raise BadRequest,
@@ -2,6 +2,7 @@ module ForemanTasks
2
2
  class TasksController < ::ApplicationController
3
3
  include Foreman::Controller::AutoCompleteSearch
4
4
  include Foreman::Controller::CsvResponder
5
+ include ForemanTasks::FindTasksCommon
5
6
 
6
7
  def show
7
8
  @task = resource_base.find(params[:id])
@@ -126,19 +127,10 @@ module ForemanTasks
126
127
  end
127
128
 
128
129
  def filter(scope, paginate: true)
129
- search = current_taxonomy_search
130
- search = [search, params[:search]].select(&:present?).join(' AND ')
131
130
  scope = DashboardTableFilter.new(scope, params).scope
132
- scope = scope.search_for(search, :order => params[:order])
133
- scope = scope.paginate(:page => params[:page], :per_page => params[:per_page]) if paginate
131
+ scope = scope.search_for(search_query, order: params[:order])
132
+ scope = scope.paginate(page: params[:page], per_page: params[:per_page]) if paginate
134
133
  scope.distinct
135
134
  end
136
-
137
- def current_taxonomy_search
138
- conditions = []
139
- conditions << "organization_id = #{Organization.current.id}" if Organization.current
140
- conditions << "location_id = #{Location.current.id}" if Location.current
141
- conditions.empty? ? '' : "(#{conditions.join(' AND ')})"
142
- end
143
135
  end
144
136
  end
@@ -0,0 +1,18 @@
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
+ belongs_to :triggering, Types::Triggering
13
+
14
+ def self.graphql_definition
15
+ super.tap { |type| type.instance_variable_set(:@name, 'ForemanTasks::RecurringLogic') }
16
+ end
17
+ end
18
+ 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
@@ -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,35 @@
1
+ module Actions
2
+ module Middleware
3
+ class LoadSettingValues < ::Dynflow::Middleware
4
+ # ::Actions::Middleware::LoadSettingValues
5
+ #
6
+ # A middleware to ensure we load current setting values
7
+
8
+ def delay(*args)
9
+ reload_setting_values
10
+ pass(*args)
11
+ end
12
+
13
+ def plan(*args)
14
+ reload_setting_values
15
+ pass(*args)
16
+ end
17
+
18
+ def run(*args)
19
+ reload_setting_values
20
+ pass(*args)
21
+ end
22
+
23
+ def finalize(*args)
24
+ reload_setting_values
25
+ pass(*args)
26
+ end
27
+
28
+ private
29
+
30
+ def reload_setting_values
31
+ ::Foreman.settings.load_values
32
+ end
33
+ end
34
+ end
35
+ end
@@ -70,7 +70,7 @@ module Actions
70
70
  apipie :class, "An common ancestor action for observable actions" do
71
71
  name 'Actions::ObservableAction'
72
72
  refs 'Actions::ObservableAction'
73
- sections only: %w[webhooks]
73
+ sections only: %w[all webhooks]
74
74
  property :task, object_of: 'Task', desc: 'Returns the task to which this action belongs'
75
75
  end
76
76
  class Jail < Safemode::Jail
@@ -218,10 +218,8 @@ module Actions
218
218
  end
219
219
 
220
220
  def get_proxy_data(response)
221
- proxy_data = response['actions'].detect do |action|
222
- action['class'] == proxy_action_name || action.fetch('input', {})['proxy_operation_name'] == proxy_operation_name
223
- end
224
- proxy_data.fetch('output', {})
221
+ response['actions'].detect { |action| action.fetch('input', {})['task_id'] == task.id }
222
+ .try(:fetch, 'output', {}) || {}
225
223
  end
226
224
 
227
225
  def proxy_version(proxy)
@@ -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
 
@@ -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
@@ -89,7 +91,7 @@ module ForemanTasks
89
91
  property :ended_at, ActiveSupport::TimeWithZone, desc: 'Returns date with time the task ended at'
90
92
  end
91
93
  class Jail < Safemode::Jail
92
- allow :started_at, :ended_at, :result, :state, :label, :main_action, :action_output
94
+ allow :started_at, :ended_at, :result, :state, :label, :main_action, :action_continuous_output
93
95
  end
94
96
 
95
97
  def input
@@ -249,12 +251,38 @@ module ForemanTasks
249
251
  parts.join(' ').strip
250
252
  end
251
253
 
252
- def action_output
254
+ def action_continuous_output
253
255
  return unless main_action.is_a?(Actions::Helpers::WithContinuousOutput)
254
256
  main_action.continuous_output.sort!
255
257
  main_action.continuous_output.raw_outputs
256
258
  end
257
259
 
260
+ def self.latest_tasks_by_resource_ids(label, resource_type, resource_ids)
261
+ tasks = arel_table
262
+ links = ForemanTasks::Link.arel_table
263
+ started_at = tasks[:started_at]
264
+ resource_id = links[:resource_id]
265
+
266
+ base_combined_table = tasks
267
+ .join(links).on(tasks[:id].eq(links[:task_id]))
268
+ .where(tasks[:label].eq(label)
269
+ .and(links[:resource_type].eq(resource_type))
270
+ .and(links[:resource_id].in(resource_ids)))
271
+
272
+ grouped = base_combined_table.project(
273
+ started_at.maximum.as('started_at_max'),
274
+ resource_id
275
+ ).group(resource_id).order(resource_id).as('grouped')
276
+
277
+ max_per_resource_id = tasks
278
+ .join(links).on(tasks[:id].eq(links[:task_id]))
279
+ .join(grouped).on(grouped[:started_at_max].eq(started_at).and(grouped[:resource_id].eq(resource_id)))
280
+ .distinct
281
+ .project(tasks[Arel.star], grouped[:resource_id])
282
+
283
+ find_by_sql(max_per_resource_id.to_sql).index_by(&:resource_id)
284
+ end
285
+
258
286
  protected
259
287
 
260
288
  def generate_id
@@ -5,6 +5,8 @@ module ForemanTasks
5
5
  :end_time].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!
@@ -1,4 +1,6 @@
1
1
  class AddUserId < ActiveRecord::Migration[5.0]
2
+ OWNER_LOCK_NAME = :task_owner
3
+
2
4
  def up
3
5
  add_reference :foreman_tasks_tasks, :user, :foreign_key => true
4
6
 
@@ -29,7 +31,7 @@ class AddUserId < ActiveRecord::Migration[5.0]
29
31
  private
30
32
 
31
33
  def create_lock(user_id, task)
32
- ForemanTasks::Lock.new(:name => ForemanTasks::Lock::OWNER_LOCK_NAME,
34
+ ForemanTasks::Lock.new(:name => OWNER_LOCK_NAME,
33
35
  :resource_type => User.name,
34
36
  :resource_id => user_id,
35
37
  :task_id => task.id,
@@ -37,6 +39,6 @@ class AddUserId < ActiveRecord::Migration[5.0]
37
39
  end
38
40
 
39
41
  def user_locks
40
- ForemanTasks::Lock.where(:name => ForemanTasks::Lock::OWNER_LOCK_NAME)
42
+ ForemanTasks::Lock.where(:name => OWNER_LOCK_NAME)
41
43
  end
42
44
  end
@@ -29,7 +29,6 @@ same resource. It also optionally provides Dynflow infrastructure for using it f
29
29
  s.extra_rdoc_files = Dir['README*', 'LICENSE']
30
30
 
31
31
  s.add_dependency "dynflow", '>= 1.2.3'
32
- s.add_dependency "foreman-tasks-core"
33
32
  s.add_dependency "get_process_mem" # for memory polling
34
33
  s.add_dependency "parse-cron", '~> 0.1.4'
35
34
  s.add_dependency "sinatra" # for Dynflow web console
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 ||= begin
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)
@@ -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