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.
- checksums.yaml +4 -4
- data/.github/workflows/ruby_tests.yml +4 -3
- data/app/controllers/concerns/foreman_tasks/find_tasks_common.rb +14 -0
- data/app/controllers/foreman_tasks/api/tasks_controller.rb +4 -6
- data/app/controllers/foreman_tasks/tasks_controller.rb +3 -11
- data/app/graphql/types/recurring_logic.rb +18 -0
- data/app/graphql/types/task.rb +25 -0
- data/app/graphql/types/triggering.rb +16 -0
- data/app/lib/actions/helpers/with_continuous_output.rb +1 -1
- data/app/lib/actions/middleware/load_setting_values.rb +35 -0
- data/app/lib/actions/observable_action.rb +1 -1
- data/app/lib/actions/proxy_action.rb +2 -4
- data/app/models/foreman_tasks/recurring_logic.rb +2 -0
- data/app/models/foreman_tasks/task.rb +30 -2
- data/app/models/foreman_tasks/triggering.rb +2 -0
- data/db/migrate/20180927120509_add_user_id.foreman_tasks.rb +4 -2
- data/foreman-tasks.gemspec +0 -1
- data/lib/foreman_tasks.rb +2 -5
- data/lib/foreman_tasks/continuous_output.rb +50 -0
- data/lib/foreman_tasks/engine.rb +7 -15
- data/lib/foreman_tasks/version.rb +1 -1
- data/locale/action_names.rb +3 -2
- data/locale/en/foreman_tasks.po +60 -27
- data/locale/foreman_tasks.pot +180 -132
- data/locale/fr/foreman_tasks.po +61 -28
- data/locale/ja/foreman_tasks.po +61 -28
- data/locale/zh_CN/foreman_tasks.po +61 -28
- data/test/controllers/api/tasks_controller_test.rb +16 -1
- data/test/controllers/tasks_controller_test.rb +2 -2
- data/test/factories/task_factory.rb +31 -4
- data/test/graphql/queries/recurring_logic_test.rb +28 -0
- data/test/graphql/queries/recurring_logics_query_test.rb +30 -0
- data/test/graphql/queries/task_query_test.rb +33 -0
- data/test/graphql/queries/tasks_query_test.rb +31 -0
- data/test/unit/actions/proxy_action_test.rb +4 -1
- data/test/unit/task_test.rb +54 -23
- data/test/unit/triggering_test.rb +2 -2
- metadata +16 -26
- data/test/core/unit/dispatcher_test.rb +0 -43
- data/test/core/unit/runner_test.rb +0 -129
- data/test/core/unit/task_launcher_test.rb +0 -56
- data/test/foreman_tasks_core_test_helper.rb +0 -4
- data/test/unit/otp_manager_test.rb +0 -77
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e48966ae6049eb85b3051151b7d15d48beb91a55b3580525fe114a3e045ad0f
|
4
|
+
data.tar.gz: 93da446f348f1a27600a79223ba4daab0e5e83417db711919392cfab2eedf57a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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:
|
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
|
-
[
|
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.
|
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(
|
133
|
-
scope = scope.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 = ::
|
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
|
-
|
222
|
-
|
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)
|
@@ -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, :
|
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
|
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
|
@@ -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 =>
|
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 =>
|
42
|
+
ForemanTasks::Lock.where(:name => OWNER_LOCK_NAME)
|
41
43
|
end
|
42
44
|
end
|
data/foreman-tasks.gemspec
CHANGED
@@ -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 ||=
|
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
|