foreman_remote_execution 14.1.0 → 14.1.2

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/app/models/job_template.rb +1 -0
  3. data/app/views/template_invocations/show.js.erb +1 -1
  4. data/db/migrate/20240312133027_extend_template_invocation_events.rb +9 -0
  5. data/lib/foreman_remote_execution/engine.rb +1 -1
  6. data/lib/foreman_remote_execution/version.rb +1 -1
  7. data/lib/tasks/foreman_remote_execution_tasks.rake +3 -0
  8. data/test/benchmark/run_hosts_job_benchmark.rb +70 -0
  9. data/test/benchmark/targeting_benchmark.rb +31 -0
  10. data/test/factories/foreman_remote_execution_factories.rb +147 -0
  11. data/test/functional/api/v2/foreign_input_sets_controller_test.rb +58 -0
  12. data/test/functional/api/v2/job_invocations_controller_test.rb +446 -0
  13. data/test/functional/api/v2/job_templates_controller_test.rb +110 -0
  14. data/test/functional/api/v2/registration_controller_test.rb +73 -0
  15. data/test/functional/api/v2/remote_execution_features_controller_test.rb +34 -0
  16. data/test/functional/api/v2/template_invocations_controller_test.rb +33 -0
  17. data/test/functional/cockpit_controller_test.rb +16 -0
  18. data/test/functional/job_invocations_controller_test.rb +132 -0
  19. data/test/functional/job_templates_controller_test.rb +31 -0
  20. data/test/functional/ui_job_wizard_controller_test.rb +16 -0
  21. data/test/graphql/mutations/job_invocations/create_test.rb +58 -0
  22. data/test/graphql/queries/job_invocation_query_test.rb +31 -0
  23. data/test/graphql/queries/job_invocations_query_test.rb +35 -0
  24. data/test/helpers/remote_execution_helper_test.rb +46 -0
  25. data/test/support/remote_execution_helper.rb +5 -0
  26. data/test/test_plugin_helper.rb +9 -0
  27. data/test/unit/actions/run_host_job_test.rb +115 -0
  28. data/test/unit/actions/run_hosts_job_test.rb +214 -0
  29. data/test/unit/api_params_test.rb +25 -0
  30. data/test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb +29 -0
  31. data/test/unit/concerns/host_extensions_test.rb +219 -0
  32. data/test/unit/concerns/nic_extensions_test.rb +9 -0
  33. data/test/unit/execution_task_status_mapper_test.rb +92 -0
  34. data/test/unit/input_template_renderer_test.rb +503 -0
  35. data/test/unit/job_invocation_composer_test.rb +974 -0
  36. data/test/unit/job_invocation_report_template_test.rb +60 -0
  37. data/test/unit/job_invocation_test.rb +232 -0
  38. data/test/unit/job_template_effective_user_test.rb +37 -0
  39. data/test/unit/job_template_test.rb +316 -0
  40. data/test/unit/remote_execution_feature_test.rb +86 -0
  41. data/test/unit/remote_execution_provider_test.rb +298 -0
  42. data/test/unit/renderer_scope_input_test.rb +49 -0
  43. data/test/unit/targeting_test.rb +206 -0
  44. data/test/unit/template_invocation_input_value_test.rb +38 -0
  45. metadata +39 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e0b4dd4f17f6d245a627cf09e014206e0392de844821d35cef065a9af835b399
4
- data.tar.gz: 4b60be78f70b3791c02f57c08060cc771aeb33f5cfd985335f26f25f1db030c7
3
+ metadata.gz: 237cac074270280b4bab25b144716d3ddb515fe4d9159e3e86badfb2530dc667
4
+ data.tar.gz: bcb74259222068245df6df8af2ff6a0d7e7cd58516d4654e2b3a3635f09176b4
5
5
  SHA512:
6
- metadata.gz: 9f87b034acd048bf1a5fe968a418a3b03cd76880342e9aeec52303b19e8bfb097013f877c9a43a34ffc23cb8b5cc9614c30dba16188c8dbfa101fc0c94d18d52
7
- data.tar.gz: a90d5d70bc9bf9a0480202699c62d48f7e78ad1f518d6421fbf478c866a29fe265a3a7a7a9ac9e4a9a15177212877ea3dbb2101696384faef7fc7eb9a0c27650
6
+ metadata.gz: 4a68f13a3060ef118f1d44f262cb4c6d072acb1abe88cdd0ef6bbcd03dcca716e7468aaa0526c7697ce99729b0cdabe7bbfe1e3ac2b16f282b87d7316cf984ca
7
+ data.tar.gz: ce09bfa5f114f7cf0c43b4b880557b7394bff115e8d66750cea8bd5c861d8c89a06b0fa243a14ba57d899227bab655565a9b15c33fbcb4d21d6bcd7a02497b4e
@@ -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 :partial => 'refresh.js' %>
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'
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '14.1.0'.freeze
2
+ VERSION = '14.1.2'.freeze
3
3
  end
@@ -17,6 +17,9 @@ namespace :test do
17
17
  t.pattern = "#{test_dir}/**/*_test.rb"
18
18
  t.verbose = true
19
19
  t.warning = false
20
+ t.test_files = [
21
+ Rails.root.join('test/unit/foreman/access_permissions_test.rb'),
22
+ ]
20
23
  end
21
24
  end
22
25
 
@@ -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