foreman-tasks 4.0.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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