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.
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