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
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ module Api
6
+ module V2
7
+ class TemplateInvocationsControllerTest < ActionController::TestCase
8
+ setup do
9
+ @job = FactoryBot.create(:job_invocation, :with_template, :with_task)
10
+ @template_invocation = @job.template_invocations.first
11
+ end
12
+
13
+ test 'should get template invocations belonging to job invocation' do
14
+ get :template_invocations, params: { :id => @job.id }
15
+ invocations = ActiveSupport::JSON.decode(@response.body)
16
+ assert_equal @job.template_invocations.count, invocations['results'].count
17
+ assert_equal @job.template_invocations.count, invocations['total']
18
+
19
+ expected_result = {
20
+ 'id' => @template_invocation.id,
21
+ 'host_id' => @template_invocation.host_id,
22
+ 'host_name' => @template_invocation.host.name,
23
+ 'template_id' => @template_invocation.template_id,
24
+ 'effective_user' => @template_invocation.effective_user,
25
+ 'job_invocation_id' => @job.id,
26
+ 'run_host_job_task_id' => @template_invocation.run_host_job_task_id,
27
+ }
28
+ assert_equal [expected_result], invocations['results']
29
+ assert_response :success
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class CockpitControllerTest < ActionController::TestCase
4
+ def setup
5
+ as_admin do
6
+ @host = FactoryBot.create(:host)
7
+ end
8
+ end
9
+
10
+ test "should get host_ssh_params" do
11
+ get :host_ssh_params, params: { id: @host.id }, session: set_session_user
12
+ assert_response :success
13
+ response = ActiveSupport::JSON.decode(@response.body)
14
+ assert response.key?('ssh_user'), 'ssh_params response must include ssh_user'
15
+ end
16
+ end
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+ require_relative '../support/remote_execution_helper'
5
+
6
+ class JobInvocationsControllerTest < ActionController::TestCase
7
+ test 'should parse inputs coming from the URL params' do
8
+ template = FactoryBot.create(:job_template, :with_input)
9
+ feature = FactoryBot.create(:remote_execution_feature,
10
+ :job_template => template)
11
+ params = {
12
+ feature: feature.label,
13
+ inputs: { template.template_inputs.first.name => 'foobar' },
14
+ }
15
+
16
+ get :new, params: params, session: set_session_user
17
+ template_invocation_params = [
18
+ {
19
+ 'input_values' =>
20
+ [
21
+ {
22
+ 'value' => 'foobar',
23
+ 'template_input_id' => template.template_inputs.first.id,
24
+ },
25
+ ],
26
+ 'template_id' => template.id,
27
+ },
28
+ ]
29
+ assert_equal(template_invocation_params,
30
+ assigns(:composer).params['template_invocations'])
31
+ end
32
+
33
+ test 'should allow no inputs' do
34
+ template = FactoryBot.create(:job_template)
35
+ feature = FactoryBot.create(:remote_execution_feature,
36
+ :job_template => template)
37
+ params = {
38
+ feature: feature.label,
39
+ }
40
+ get :new, params: params, session: set_session_user
41
+ template_invocation_params = [
42
+ {
43
+ 'template_id' => template.id,
44
+ 'input_values' => {},
45
+ },
46
+ ]
47
+ assert_equal(template_invocation_params,
48
+ assigns(:composer).params['template_invocations'])
49
+ end
50
+
51
+ test 'new via GET and POST' do
52
+ template = FactoryBot.create(:job_template, :with_input)
53
+ feature = FactoryBot.create(:remote_execution_feature, job_template: template)
54
+ params = { feature: feature.label, inputs: { template.template_inputs.first.name => 'foobar' } }
55
+
56
+ get :new, params: params, session: set_session_user
57
+ assert_response :success
58
+
59
+ post :new, params: params, session: set_session_user
60
+ assert_response :success
61
+ end
62
+
63
+ context 'restricted access' do
64
+ setup do
65
+ @admin = users(:admin)
66
+ @user = FactoryBot.create(:user, mail: 'test23@test.foreman.com', admin: false)
67
+ @invocation = FactoryBot.create(:job_invocation, :with_template, :with_task)
68
+ @invocation2 = FactoryBot.create(:job_invocation, :with_template, :with_task)
69
+
70
+ @invocation.task.update(user: @admin)
71
+ @invocation2.task.update(user: @user)
72
+
73
+ setup_user 'view', 'hosts', nil, @user
74
+ setup_user 'view', 'job_invocations', 'user = current_user', @user
75
+ setup_user 'create', 'job_invocations', 'user = current_user', @user
76
+ setup_user 'cancel', 'job_invocations', 'user = current_user', @user
77
+ end
78
+
79
+ context 'without user filter' do
80
+ test '#index' do
81
+ get :index, session: prepare_user(@admin)
82
+ assert_response :success
83
+ assert 2, assigns(:job_invocations).size
84
+ end
85
+
86
+ test '#show' do
87
+ get :show, params: { id: @invocation2.id }, session: prepare_user(@admin)
88
+ assert_response :success
89
+ end
90
+
91
+ test '#rerun' do
92
+ get :rerun, params: { id: @invocation2.id }, session: prepare_user(@admin)
93
+ assert_response :success
94
+ end
95
+
96
+ test '#cancel' do
97
+ ForemanTasks::Task.any_instance.expects(:cancel).returns(true)
98
+ post :cancel, params: { id: @invocation2.id }, session: prepare_user(@admin)
99
+ assert_response :redirect
100
+ end
101
+ end
102
+
103
+ context 'with user filter' do
104
+ test '#index' do
105
+ get :index, session: prepare_user(@user)
106
+ assert_response :success
107
+ assert_equal 1, assigns(:job_invocations).size
108
+ assert_equal @invocation2, assigns(:job_invocations)[0]
109
+ end
110
+
111
+ test '#show' do
112
+ get :show, params: { id: @invocation.id }, session: prepare_user(@user)
113
+ assert_response :not_found
114
+ end
115
+
116
+ test '#rerun' do
117
+ get :rerun, params: { id: @invocation.id }, session: prepare_user(@user)
118
+ assert_response :not_found
119
+ end
120
+
121
+ test 'cancel' do
122
+ post :cancel, params: { id: @invocation.id }, session: prepare_user(@user)
123
+ assert_response :not_found
124
+ end
125
+ end
126
+ end
127
+
128
+ def prepare_user(user)
129
+ User.current = user
130
+ set_session_user(user)
131
+ end
132
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class JobTemplatesControllerTest < ActionController::TestCase
6
+ context '#preview' do
7
+ let(:template) { FactoryBot.create(:job_template) }
8
+ let(:host) { FactoryBot.create(:host, :managed) }
9
+
10
+ test 'should render a preview version of a template' do
11
+ post :preview, params: { job_template: template.to_param, template: 'uptime' }, session: set_session_user
12
+ assert_response :success
13
+ end
14
+
15
+ test 'should render a preview version of a template for a specific host' do
16
+ post :preview, params: {
17
+ job_template: template.to_param,
18
+ template: '<%= @host.name %>',
19
+ preview_host_id: host.id,
20
+ }, session: set_session_user
21
+ assert_response :success
22
+ assert_equal host.name, @response.body
23
+ end
24
+
25
+ test 'should render a error message when template has errors' do
26
+ InputTemplateRenderer.any_instance.stubs(:render).returns(false)
27
+ post :preview, params: { job_template: template.to_param }, session: set_session_user
28
+ assert_response :not_acceptable
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class UIJobWizardControllerTest < ActionController::TestCase
4
+ def setup
5
+ FactoryBot.create(:job_template, :job_category => 'cat1')
6
+ FactoryBot.create(:job_template, :job_category => 'cat2')
7
+ FactoryBot.create(:job_template, :job_category => 'cat2')
8
+ end
9
+
10
+ test 'should respond with categories' do
11
+ get :categories, :params => {}, :session => set_session_user
12
+ assert_response :success
13
+ res = JSON.parse @response.body
14
+ assert_equal ['cat1','cat2'], res['job_categories']
15
+ end
16
+ end
@@ -0,0 +1,58 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Mutations
4
+ module JobInvocations
5
+ class CreateMutationTest < ActiveSupport::TestCase
6
+ let(:host) { FactoryBot.create(:host) }
7
+ let(:job_template) { FactoryBot.create(:job_template, :with_input) }
8
+ let(:cron_line) { '5 * * * *' }
9
+ let(:purpose) { 'test' }
10
+ let(:variables) do
11
+ {
12
+ jobInvocation: {
13
+ hostIds: [host.id],
14
+ jobTemplateId: job_template.id,
15
+ targetingType: 'static_query',
16
+ inputs: { job_template.template_inputs.first.name => "bar" },
17
+ recurrence: {
18
+ cronLine: cron_line,
19
+ purpose: purpose,
20
+ },
21
+ },
22
+ }
23
+ end
24
+
25
+ let(:query) do
26
+ <<-GRAPHQL
27
+ mutation CreateJobInvocation($jobInvocation: JobInvocationInput!) {
28
+ createJobInvocation(input: { jobInvocation: $jobInvocation }) {
29
+ jobInvocation {
30
+ id
31
+ description
32
+ recurringLogic {
33
+ cronLine
34
+ purpose
35
+ }
36
+ }
37
+ }
38
+ }
39
+ GRAPHQL
40
+ end
41
+
42
+ context 'with admin user' do
43
+ let(:user) { FactoryBot.create(:user, :admin) }
44
+ let(:context) { { current_user: user } }
45
+
46
+ test 'create a job invocation' do
47
+ assert_difference('JobInvocation.count', +1) do
48
+ result = ForemanGraphqlSchema.execute(query, variables: variables, context: context)
49
+ assert_empty result['errors']
50
+ assert_empty result['data']['createJobInvocation']['jobInvocation']['errors']
51
+ assert_equal cron_line, result['data']['createJobInvocation']['jobInvocation']['recurringLogic']['cronLine']
52
+ assert_equal purpose, result['data']['createJobInvocation']['jobInvocation']['recurringLogic']['purpose']
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ module Queries
4
+ class JobInvocationQueryTest < GraphQLQueryTestCase
5
+ let(:query) do
6
+ <<-GRAPHQL
7
+ query (
8
+ $id: String!
9
+ ) {
10
+ jobInvocation(id: $id) {
11
+ id
12
+ jobCategory
13
+ }
14
+ }
15
+ GRAPHQL
16
+ end
17
+
18
+ let(:job_invocation) { FactoryBot.create(:job_invocation) }
19
+
20
+ let(:global_id) { Foreman::GlobalId.for(job_invocation) }
21
+ let(:variables) { { id: global_id } }
22
+ let(:data) { result['data']['jobInvocation'] }
23
+
24
+ test 'fetching job invocation attributes' do
25
+ assert_empty result['errors']
26
+
27
+ assert_equal global_id, data['id']
28
+ assert_equal job_invocation.job_category, data['jobCategory']
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,35 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Queries
4
+ class JobInvocationsQueryTest < GraphQLQueryTestCase
5
+ let(:query) do
6
+ <<-GRAPHQL
7
+ query {
8
+ jobInvocations {
9
+ totalCount
10
+ nodes {
11
+ id
12
+ jobCategory
13
+ }
14
+ }
15
+ }
16
+ GRAPHQL
17
+ end
18
+
19
+ let(:data) { result['data']['jobInvocations'] }
20
+
21
+ setup do
22
+ FactoryBot.create_list(:job_invocation, 2)
23
+ end
24
+
25
+ test 'should fetch job invocations' do
26
+ assert_empty result['errors']
27
+
28
+ expected_count = JobInvocation.count
29
+
30
+ assert_not_equal 0, expected_count
31
+ assert_equal expected_count, data['totalCount']
32
+ assert_equal expected_count, data['nodes'].count
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,46 @@
1
+ require 'test_plugin_helper'
2
+
3
+ class RemoteExecutionHelperTest < ActionView::TestCase
4
+ describe '#normalize_line_sets' do
5
+ let :line_sets do
6
+ [{"output_type"=>"stdout", "output"=>"one", "timestamp"=>1},
7
+ {"output_type"=>"stdout", "output"=>"\r\ntwo\r\n", "timestamp"=>2},
8
+ {"output_type"=>"stdout", "output"=>"\r\nthr", "timestamp"=>3},
9
+ {"output_type"=>"stdout", "output"=>"ee\r\nfour\r\n", "timestamp"=>4},
10
+ {"output_type"=>"stdout", "output"=>"\r\n\r\n", "timestamp"=>5},
11
+ {"output_type"=>"stdout", "output"=>"five\r\n", "timestamp"=>6},
12
+ {"output_type"=>"stdout", "output"=>"Exit status: 0", "timestamp"=>7}]
13
+ end
14
+
15
+ it 'ensures the line sets end with new line' do
16
+ new_line_sets = normalize_line_sets(line_sets)
17
+ expected_output = ["one\r\n",
18
+ "two\r\n",
19
+ "\r\nthree\r\n",
20
+ "four\r\n",
21
+ "\r\n\r\n",
22
+ "five\r\n",
23
+ "Exit status: 0"]
24
+ assert_equal(expected_output, new_line_sets.map { |s| s['output'] })
25
+ end
26
+ end
27
+
28
+ describe 'test correct setting' do
29
+ it 'should found correct template from setting' do
30
+ template_name = 'Job Invocation Report Template'
31
+ setting_key = 'remote_execution_job_invocation_report_template'
32
+ template = FactoryBot.create(:report_template, name: template_name)
33
+ input = FactoryBot.create(:template_input, name: 'job_id', input_type: 'user')
34
+ template.template_inputs << input
35
+ Setting.expects(:[]).with(setting_key).returns(template_name)
36
+
37
+ found_template = job_report_template
38
+
39
+ assert_equal template.id, found_template.id
40
+ end
41
+
42
+ it 'should not crash if the template cannot be found' do
43
+ assert_nil job_report_template
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,5 @@
1
+ module RemoteExecutionHelper
2
+ def job_invocation_task_buttons(task)
3
+ return []
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ # This calls the main test_helper in Foreman-core
2
+ require 'test_helper'
3
+ require 'dynflow/testing'
4
+
5
+ # Add plugin to FactoryBot's paths
6
+ FactoryBot.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
7
+ # Add foreman tasks factories too
8
+ FactoryBot.definition_file_paths << "#{ForemanTasks::Engine.root}/test/factories"
9
+ FactoryBot.reload
@@ -0,0 +1,115 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module ForemanRemoteExecution
4
+ class RunHostJobTest < ActiveSupport::TestCase
5
+ include Dynflow::Testing
6
+
7
+ subject { create_action(Actions::RemoteExecution::RunHostJob) }
8
+
9
+ describe '#secrets' do
10
+ let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
11
+ let(:host) { job_invocation.template_invocations.first.host }
12
+ let(:provider) do
13
+ provider = ::SSHExecutionProvider
14
+ provider.expects(:ssh_password).with(host).returns('sshpass')
15
+ provider.expects(:effective_user_password).with(host).returns('sudopass')
16
+ provider.expects(:ssh_key_passphrase).with(host).returns('keypass')
17
+ provider
18
+ end
19
+
20
+ it 'uses provider secrets' do
21
+ secrets = subject.secrets(host, job_invocation, provider)
22
+
23
+ assert_equal 'sshpass', secrets[:ssh_password]
24
+ assert_equal 'sudopass', secrets[:effective_user_password]
25
+ assert_equal 'keypass', secrets[:key_passphrase]
26
+ end
27
+
28
+ it 'prefers job secrets over provider secrets' do
29
+ job_invocation.password = 'jobsshpass'
30
+ job_invocation.key_passphrase = 'jobkeypass'
31
+ secrets = subject.secrets(host, job_invocation, provider)
32
+
33
+ assert_equal 'jobsshpass', secrets[:ssh_password]
34
+ assert_equal 'sudopass', secrets[:effective_user_password]
35
+ assert_equal 'jobkeypass', secrets[:key_passphrase]
36
+ end
37
+ end
38
+
39
+ describe '#verify_permission' do
40
+ let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
41
+ let(:template_invocation) { job_invocation.template_invocations.first }
42
+
43
+ before { job_invocation }
44
+
45
+ it 'raises an exception when run against an infrastructure host' do
46
+ template_invocation.host = FactoryBot.create(:host, :with_infrastructure_facet)
47
+
48
+ setup_user('view', 'hosts')
49
+ setup_user('view', 'job_templates')
50
+ setup_user('create', 'template_invocations')
51
+
52
+ action = Actions::RemoteExecution::RunHostJob.allocate
53
+ exception = assert_raises do
54
+ action.send(:verify_permissions, template_invocation.host, template_invocation)
55
+ end
56
+ assert_includes exception.message, "infrastructure host #{template_invocation.host.name}"
57
+ end
58
+ end
59
+
60
+ describe '#finalize' do
61
+ let(:host) { FactoryBot.create(:host, :with_execution) }
62
+
63
+ before do
64
+ subject.stubs(:input).returns({ host: { id: host.id } })
65
+ Host.expects(:find).with(host.id).returns(host)
66
+ end
67
+
68
+ describe 'updates the host status' do
69
+ before do
70
+ subject.expects(:check_exit_status).returns(nil)
71
+ end
72
+
73
+ context 'with stubbed status' do
74
+ let(:stub_status) do
75
+ status = HostStatus::ExecutionStatus.new
76
+ status.stubs(:save!).returns(true)
77
+ status
78
+ end
79
+
80
+ before do
81
+ host.expects(:execution_status_object).returns(stub_status)
82
+ end
83
+
84
+ context 'exit_status is 0' do
85
+ it 'updates the host status to OK' do
86
+ subject.stubs(:exit_status).returns(0)
87
+ stub_status.expects(:"status=").with(HostStatus::ExecutionStatus::OK)
88
+ subject.finalize
89
+ end
90
+ end
91
+
92
+ context 'exit_status is NOT 0' do
93
+ it 'updates the host status to ERROR' do
94
+ subject.stubs(:exit_status).returns(1)
95
+ stub_status.expects(:"status=").with(HostStatus::ExecutionStatus::ERROR)
96
+ subject.finalize
97
+ end
98
+ end
99
+ end
100
+
101
+ context 'host has no execution status yet' do
102
+ before do
103
+ assert_nil host.execution_status_object
104
+ subject.stubs(:exit_status).returns(0)
105
+ end
106
+
107
+ it 'creates a new status' do
108
+ subject.finalize
109
+ assert_not_nil host.execution_status_object
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end