foreman_remote_execution 14.1.0 → 14.1.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 +4 -4
- data/app/models/job_template.rb +1 -0
- data/app/views/template_invocations/show.js.erb +1 -1
- data/db/migrate/20240312133027_extend_template_invocation_events.rb +9 -0
- data/lib/foreman_remote_execution/engine.rb +1 -1
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/lib/tasks/foreman_remote_execution_tasks.rake +3 -0
- data/test/benchmark/run_hosts_job_benchmark.rb +70 -0
- data/test/benchmark/targeting_benchmark.rb +31 -0
- data/test/factories/foreman_remote_execution_factories.rb +147 -0
- data/test/functional/api/v2/foreign_input_sets_controller_test.rb +58 -0
- data/test/functional/api/v2/job_invocations_controller_test.rb +446 -0
- data/test/functional/api/v2/job_templates_controller_test.rb +110 -0
- data/test/functional/api/v2/registration_controller_test.rb +73 -0
- data/test/functional/api/v2/remote_execution_features_controller_test.rb +34 -0
- data/test/functional/api/v2/template_invocations_controller_test.rb +33 -0
- data/test/functional/cockpit_controller_test.rb +16 -0
- data/test/functional/job_invocations_controller_test.rb +132 -0
- data/test/functional/job_templates_controller_test.rb +31 -0
- data/test/functional/ui_job_wizard_controller_test.rb +16 -0
- data/test/graphql/mutations/job_invocations/create_test.rb +58 -0
- data/test/graphql/queries/job_invocation_query_test.rb +31 -0
- data/test/graphql/queries/job_invocations_query_test.rb +35 -0
- data/test/helpers/remote_execution_helper_test.rb +46 -0
- data/test/support/remote_execution_helper.rb +5 -0
- data/test/test_plugin_helper.rb +9 -0
- data/test/unit/actions/run_host_job_test.rb +115 -0
- data/test/unit/actions/run_hosts_job_test.rb +214 -0
- data/test/unit/api_params_test.rb +25 -0
- data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +29 -0
- data/test/unit/concerns/host_extensions_test.rb +219 -0
- data/test/unit/concerns/nic_extensions_test.rb +9 -0
- data/test/unit/execution_task_status_mapper_test.rb +92 -0
- data/test/unit/input_template_renderer_test.rb +503 -0
- data/test/unit/job_invocation_composer_test.rb +974 -0
- data/test/unit/job_invocation_report_template_test.rb +60 -0
- data/test/unit/job_invocation_test.rb +232 -0
- data/test/unit/job_template_effective_user_test.rb +37 -0
- data/test/unit/job_template_test.rb +316 -0
- data/test/unit/remote_execution_feature_test.rb +86 -0
- data/test/unit/remote_execution_provider_test.rb +298 -0
- data/test/unit/renderer_scope_input_test.rb +49 -0
- data/test/unit/targeting_test.rb +206 -0
- data/test/unit/template_invocation_input_value_test.rb +38 -0
- metadata +39 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 237cac074270280b4bab25b144716d3ddb515fe4d9159e3e86badfb2530dc667
|
4
|
+
data.tar.gz: bcb74259222068245df6df8af2ff6a0d7e7cd58516d4654e2b3a3635f09176b4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a68f13a3060ef118f1d44f262cb4c6d072acb1abe88cdd0ef6bbcd03dcca716e7468aaa0526c7697ce99729b0cdabe7bbfe1e3ac2b16f282b87d7316cf984ca
|
7
|
+
data.tar.gz: ce09bfa5f114f7cf0c43b4b880557b7394bff115e8d66750cea8bd5c861d8c89a06b0fa243a14ba57d899227bab655565a9b15c33fbcb4d21d6bcd7a02497b4e
|
data/app/models/job_template.rb
CHANGED
@@ -64,6 +64,7 @@ class JobTemplate < ::Template
|
|
64
64
|
# will overwrite (sync) an existing template if options[:update] is true.
|
65
65
|
def import_raw(contents, options = {})
|
66
66
|
metadata = Template.parse_metadata(contents)
|
67
|
+
return unless SeedHelper.test_template_requirements(metadata['name'], metadata['require'] || [])
|
67
68
|
import_parsed(metadata['name'], contents, metadata, options)
|
68
69
|
end
|
69
70
|
|
@@ -9,7 +9,7 @@ var target = $("div.terminal div.printable");
|
|
9
9
|
target.html(lines);
|
10
10
|
<% end %>
|
11
11
|
|
12
|
-
<%= render :
|
12
|
+
<%= render partial: 'refresh', formats: :js %>
|
13
13
|
|
14
14
|
if (scrolledToBottom) {
|
15
15
|
$("html, body").animate({ scrollTop: $(document).height() }, "slow");
|
@@ -6,6 +6,15 @@ class ExtendTemplateInvocationEvents < ActiveRecord::Migration[6.1]
|
|
6
6
|
|
7
7
|
TemplateInvocationEvent.update_all("external_id = CASE WHEN event_type = 'exit' THEN 'exit' ELSE sequence_id::varchar END")
|
8
8
|
|
9
|
+
# For each template invocation return the lowest id of its exit event, if there is more than 1 exit event
|
10
|
+
scope = TemplateInvocationEvent.select("MIN(id) as id")
|
11
|
+
.where(external_id: 'exit')
|
12
|
+
.group(:template_invocation_id)
|
13
|
+
.having("count(*) > 1")
|
14
|
+
|
15
|
+
# Make sure there is at most one exit event per template invocation
|
16
|
+
TemplateInvocationEvent.where(:id => scope.limit(BATCH_SIZE)).delete_all while scope.any?
|
17
|
+
|
9
18
|
remove_index :template_invocation_events, name: :unique_template_invocation_events_index
|
10
19
|
remove_column :template_invocation_events, :sequence_id
|
11
20
|
|
@@ -176,7 +176,7 @@ module ForemanRemoteExecution
|
|
176
176
|
permission :create_job_invocations, { :job_invocations => [:new, :create, :legacy_create, :refresh, :rerun, :preview_hosts],
|
177
177
|
'api/v2/job_invocations' => [:create, :rerun] }, :resource_type => 'JobInvocation'
|
178
178
|
permission :view_job_invocations, { :job_invocations => [:index, :chart, :show, :auto_complete_search, :preview_job_invocations_per_host], :template_invocations => [:show],
|
179
|
-
'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs] }, :resource_type => 'JobInvocation'
|
179
|
+
'api/v2/job_invocations' => [:index, :show, :output, :raw_output, :outputs, :hosts] }, :resource_type => 'JobInvocation'
|
180
180
|
permission :view_template_invocations, { :template_invocations => [:show],
|
181
181
|
'api/v2/template_invocations' => [:template_invocations], :ui_job_wizard => [:job_invocation] }, :resource_type => 'TemplateInvocation'
|
182
182
|
permission :create_template_invocations, {}, :resource_type => 'TemplateInvocation'
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'benchmark/benchmark_helper'
|
2
|
+
require 'dynflow/testing'
|
3
|
+
|
4
|
+
# Add plugin to FactoryBot's paths
|
5
|
+
FactoryBot.definition_file_paths << File.expand_path('../../factories', __FILE__)
|
6
|
+
FactoryBot.definition_file_paths << "#{ForemanTasks::Engine.root}/test/factories"
|
7
|
+
FactoryBot.reload
|
8
|
+
|
9
|
+
module Actions
|
10
|
+
module RemoteExecution
|
11
|
+
class RunHostsJob < Actions::ActionWithSubPlans
|
12
|
+
# stub function that calls other actions.
|
13
|
+
def trigger(*args)
|
14
|
+
nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module Support
|
21
|
+
class DummyDynflowAction < Dynflow::Action
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def generate_hosts(total)
|
26
|
+
FactoryBot.create_list(:host, total, :comment => "benchmark-#{Foreman.uuid}")
|
27
|
+
end
|
28
|
+
|
29
|
+
Rails.logger.level = Logger::ERROR
|
30
|
+
|
31
|
+
class ActionTester
|
32
|
+
include Dynflow::Testing::Factories
|
33
|
+
|
34
|
+
def initialize(task)
|
35
|
+
@task = task
|
36
|
+
end
|
37
|
+
|
38
|
+
def run_action(*args)
|
39
|
+
action = create_action(Actions::RemoteExecution::RunHostsJob)
|
40
|
+
@task.update(:external_id => action.execution_plan_id)
|
41
|
+
plan_action(action, *args)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
puts 'generating admin user'
|
46
|
+
admin = FactoryBot.build(:user, :admin)
|
47
|
+
admin.save(:validate => false)
|
48
|
+
User.current = admin
|
49
|
+
targeting = FactoryBot.create(:targeting, :search_query => "comment = benchmark-#{Foreman.uuid}", :user => User.current)
|
50
|
+
template_invocation = FactoryBot.build(:template_invocation, :job_invocation => nil)
|
51
|
+
job_invocation = FactoryBot.build(:job_invocation, :targeting => targeting, :pattern_template_invocations => [template_invocation]).tap do |invocation|
|
52
|
+
invocation.targeting = targeting
|
53
|
+
invocation.save
|
54
|
+
end
|
55
|
+
|
56
|
+
task = FactoryBot.create(:dynflow_task, :external_id => '1')
|
57
|
+
tester = ActionTester.new(task)
|
58
|
+
|
59
|
+
puts 'generating hosts'
|
60
|
+
generate_hosts(1000)
|
61
|
+
puts 'starting benchmarking'
|
62
|
+
foreman_benchmark do
|
63
|
+
Benchmark.ips do |x|
|
64
|
+
x.config(:time => 10, :warmup => 0)
|
65
|
+
|
66
|
+
x.report('rex-run-hosts-job') do
|
67
|
+
tester.run_action(job_invocation)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'benchmark/benchmark_helper'
|
2
|
+
require 'dynflow/testing'
|
3
|
+
|
4
|
+
# Add plugin to FactoryBot's paths
|
5
|
+
FactoryBot.definition_file_paths << File.expand_path('../../factories', __FILE__)
|
6
|
+
FactoryBot.reload
|
7
|
+
|
8
|
+
def generate_hosts(total)
|
9
|
+
FactoryBot.create_list(:host, total, :comment => "benchmark-#{Foreman.uuid}")
|
10
|
+
end
|
11
|
+
|
12
|
+
Rails.logger.level = Logger::ERROR
|
13
|
+
|
14
|
+
puts 'generating hosts'
|
15
|
+
generate_hosts(1000)
|
16
|
+
puts 'generating admin user'
|
17
|
+
admin = FactoryBot.build(:user, :admin)
|
18
|
+
admin.save(:validate => false)
|
19
|
+
User.current = admin
|
20
|
+
|
21
|
+
puts 'starting benchmarking'
|
22
|
+
foreman_benchmark do
|
23
|
+
Benchmark.ips do |x|
|
24
|
+
x.config(:time => 10, :warmup => 0)
|
25
|
+
|
26
|
+
x.report('rex-targeting-resolve-hosts') do
|
27
|
+
targeting = FactoryBot.create(:targeting, :search_query => "comment = benchmark-#{Foreman.uuid}", :user => User.current)
|
28
|
+
targeting.resolve_hosts!
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
FactoryBot.define do
|
2
|
+
factory :job_template do
|
3
|
+
sequence(:name) { |n| "Job template #{n}" }
|
4
|
+
sequence(:job_category) { |n| "Job name #{n}" }
|
5
|
+
template { 'id' }
|
6
|
+
provider_type { 'SSH' }
|
7
|
+
organizations { [Organization.find_by(name: 'Organization 1')] }
|
8
|
+
locations { [Location.find_by(name: 'Location 1')] }
|
9
|
+
|
10
|
+
trait :with_input do
|
11
|
+
after(:build) do |template, evaluator|
|
12
|
+
template.template_inputs << FactoryBot.build(:template_input)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
trait :with_description_format do
|
17
|
+
description_format { 'Factory-built %{job_category}' }
|
18
|
+
end
|
19
|
+
|
20
|
+
trait :with_feature do
|
21
|
+
remote_execution_feature
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
factory :foreign_input_set
|
26
|
+
|
27
|
+
factory :targeting do
|
28
|
+
search_query { 'name = foo' }
|
29
|
+
targeting_type { 'static_query' }
|
30
|
+
user
|
31
|
+
|
32
|
+
trait :with_randomized_ordering do
|
33
|
+
randomized_ordering { true }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
factory :job_invocation do |f|
|
38
|
+
targeting
|
39
|
+
f.sequence(:job_category) { |n| "Job name #{n}" }
|
40
|
+
f.description_format { "%{job_category}" }
|
41
|
+
trait :with_template do
|
42
|
+
after(:build) do |invocation, evaluator|
|
43
|
+
invocation.pattern_template_invocations << FactoryBot.build(:template_invocation)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
trait :with_failed_task do
|
48
|
+
after(:build) do |invocation, _evaluator|
|
49
|
+
invocation.template_invocations << FactoryBot.build(:template_invocation, :with_failed_task, :with_host)
|
50
|
+
invocation.task = FactoryBot.build(:some_task)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
trait :with_task do
|
55
|
+
after(:build) do |invocation, _evaluator|
|
56
|
+
invocation.template_invocations << FactoryBot.build(:template_invocation, :with_task, :with_host)
|
57
|
+
invocation.task = FactoryBot.build(:some_task)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
trait :with_unplanned_host do
|
62
|
+
after(:build) do |invocation, _evaluator|
|
63
|
+
invocation.targeting.hosts << FactoryBot.build(:host)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
factory :remote_execution_provider do |f|
|
69
|
+
f.sequence(:name) { |n| "Provider #{n}" }
|
70
|
+
end
|
71
|
+
|
72
|
+
factory :template_invocation do
|
73
|
+
job_invocation
|
74
|
+
association :template, :factory => :job_template
|
75
|
+
|
76
|
+
trait :with_task do
|
77
|
+
after(:build) do |template, _evaluator|
|
78
|
+
template.run_host_job_task = FactoryBot.build(:some_task)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
trait :with_failed_task do
|
83
|
+
after(:build) do |template, _evaluator|
|
84
|
+
template.run_host_job_task = FactoryBot.build(:some_task, :result => 'error')
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
trait :with_host do
|
89
|
+
after(:build) do |template, _evaluator|
|
90
|
+
template.host = FactoryBot.build(:host)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
factory :template_invocation_input_value do |f|
|
96
|
+
f.sequence(:value) { |n| "Input Value #{n}" }
|
97
|
+
end
|
98
|
+
|
99
|
+
factory :remote_execution_feature do |f|
|
100
|
+
f.sequence(:label) { |n| "remote_execution_feature_#{n}" }
|
101
|
+
f.sequence(:name) { |n| "Remote Execution Feature #{n}" }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
FactoryBot.modify do
|
106
|
+
factory :feature do
|
107
|
+
trait :ssh do
|
108
|
+
name { 'SSH' }
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
factory :smart_proxy do
|
113
|
+
trait :ssh do
|
114
|
+
features { [FactoryBot.create(:feature, :ssh)] }
|
115
|
+
pubkey { 'ssh-rsa AAAAB3N...' }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
factory :subnet do
|
120
|
+
trait :execution do
|
121
|
+
remote_execution_proxies { [FactoryBot.build(:smart_proxy, :ssh)] }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
factory :host do
|
126
|
+
trait :with_execution do
|
127
|
+
managed
|
128
|
+
domain
|
129
|
+
subnet do
|
130
|
+
overrides = {
|
131
|
+
:remote_execution_proxies => [FactoryBot.create(:smart_proxy, :ssh)],
|
132
|
+
}
|
133
|
+
|
134
|
+
overrides[:locations] = [location] unless location.nil?
|
135
|
+
overrides[:organizations] = [organization] unless organization.nil?
|
136
|
+
|
137
|
+
FactoryBot.create(
|
138
|
+
:subnet_ipv4,
|
139
|
+
overrides
|
140
|
+
)
|
141
|
+
end
|
142
|
+
interfaces do
|
143
|
+
[FactoryBot.build(:nic_primary_and_provision, :ip => subnet.network.sub(/0\Z/, '1'), :execution => true)]
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'test_plugin_helper'
|
2
|
+
|
3
|
+
module Api
|
4
|
+
module V2
|
5
|
+
class ForeignInputSetsControllerTest < ActionController::TestCase
|
6
|
+
setup do
|
7
|
+
@template = FactoryBot.create(:job_template)
|
8
|
+
@foreign_template = FactoryBot.create(:job_template, :with_input)
|
9
|
+
@new_foreign_template = FactoryBot.create(:job_template, :with_input)
|
10
|
+
@input_set = @template.foreign_input_sets.create(:target_template_id => @foreign_template.id)
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'should get index' do
|
14
|
+
get :index, params: { :template_id => @template.id }
|
15
|
+
input_sets = ActiveSupport::JSON.decode(@response.body)
|
16
|
+
assert_not input_sets.empty?, 'Should respond with input sets'
|
17
|
+
assert_response :success
|
18
|
+
end
|
19
|
+
|
20
|
+
test 'should get input set detail' do
|
21
|
+
get :show, params: { :template_id => @template.to_param, :id => @input_set.to_param }
|
22
|
+
assert_response :success
|
23
|
+
input_set = ActiveSupport::JSON.decode(@response.body)
|
24
|
+
assert_not input_set.empty?
|
25
|
+
assert_equal input_set['target_template_name'], @foreign_template.name
|
26
|
+
end
|
27
|
+
|
28
|
+
test 'should create valid' do
|
29
|
+
valid_attrs = { :target_template_id => @new_foreign_template.id }
|
30
|
+
post :create, params: { :foreign_input_set => valid_attrs, :template_id => @template.to_param }
|
31
|
+
input_set = ActiveSupport::JSON.decode(@response.body)
|
32
|
+
assert_equal input_set['target_template_name'], @new_foreign_template.name
|
33
|
+
assert_response :success
|
34
|
+
end
|
35
|
+
|
36
|
+
test 'should not create invalid' do
|
37
|
+
post :create, params: { :template_id => @template.to_param }
|
38
|
+
assert_response :unprocessable_entity
|
39
|
+
end
|
40
|
+
|
41
|
+
test 'should update valid' do
|
42
|
+
put :update, params: { :template_id => @template.to_param, :id => @input_set.to_param, :foreign_input_set => { :include_all => false } }
|
43
|
+
assert_response :ok
|
44
|
+
end
|
45
|
+
|
46
|
+
test 'should not update invalid' do
|
47
|
+
put :update, params: { :template_id => @template.to_param, :id => @input_set.to_param, :foreign_input_set => { :target_template_id => '' } }
|
48
|
+
assert_response :unprocessable_entity
|
49
|
+
end
|
50
|
+
|
51
|
+
test 'should destroy' do
|
52
|
+
delete :destroy, params: { :template_id => @template.to_param, :id => @input_set.to_param }
|
53
|
+
assert_response :ok
|
54
|
+
assert_not ForeignInputSet.exists?(@input_set.id)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|