foreman_remote_execution 4.6.0 → 5.0.1
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/.github/workflows/ruby_ci.yml +7 -0
- data/.rubocop_todo.yml +1 -0
- data/app/controllers/api/v2/job_invocations_controller.rb +16 -1
- data/app/controllers/job_invocations_controller.rb +1 -1
- data/app/controllers/ui_job_wizard_controller.rb +21 -2
- data/app/graphql/mutations/job_invocations/create.rb +43 -0
- data/app/graphql/types/job_invocation.rb +16 -0
- data/app/graphql/types/job_invocation_input.rb +13 -0
- data/app/graphql/types/recurrence_input.rb +8 -0
- data/app/graphql/types/scheduling_input.rb +6 -0
- data/app/graphql/types/targeting_enum.rb +7 -0
- data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +5 -1
- data/app/helpers/remote_execution_helper.rb +9 -3
- data/app/lib/actions/remote_execution/run_host_job.rb +10 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +58 -4
- data/app/mailers/rex_job_mailer.rb +15 -0
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +10 -0
- data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
- data/app/models/host_proxy_invocation.rb +4 -0
- data/app/models/host_status/execution_status.rb +3 -3
- data/app/models/job_invocation.rb +12 -5
- data/app/models/job_invocation_composer.rb +25 -17
- data/app/models/job_template.rb +1 -1
- data/app/models/remote_execution_feature.rb +5 -1
- data/app/models/remote_execution_provider.rb +18 -2
- data/app/models/rex_mail_notification.rb +13 -0
- data/app/models/targeting.rb +7 -3
- data/app/services/ui_notifications/remote_execution_jobs/base_job_finish.rb +2 -1
- data/app/views/dashboard/_latest-jobs.html.erb +21 -0
- data/app/views/job_invocations/index.html.erb +1 -1
- data/app/views/job_invocations/refresh.js.erb +1 -0
- data/app/views/rex_job_mailer/job_finished.html.erb +24 -0
- data/app/views/rex_job_mailer/job_finished.text.erb +9 -0
- data/app/views/template_invocations/show.html.erb +2 -1
- data/app/views/templates/ssh/module_action.erb +1 -0
- data/app/views/templates/ssh/power_action.erb +2 -0
- data/app/views/templates/ssh/puppet_run_once.erb +1 -0
- data/config/routes.rb +1 -0
- data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
- data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +5 -0
- data/db/seeds.d/50-notification_blueprints.rb +14 -0
- data/db/seeds.d/95-mail_notifications.rb +24 -0
- data/foreman_remote_execution.gemspec +2 -3
- data/lib/foreman_remote_execution/engine.rb +114 -8
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/package.json +9 -7
- data/test/functional/api/v2/job_invocations_controller_test.rb +20 -0
- data/test/functional/cockpit_controller_test.rb +0 -1
- data/test/graphql/mutations/job_invocations/create.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 +0 -1
- data/test/unit/actions/run_host_job_test.rb +21 -0
- data/test/unit/actions/run_hosts_job_test.rb +99 -4
- data/test/unit/concerns/host_extensions_test.rb +40 -7
- data/test/unit/input_template_renderer_test.rb +1 -89
- data/test/unit/job_invocation_composer_test.rb +18 -18
- data/test/unit/job_invocation_report_template_test.rb +16 -13
- data/test/unit/job_invocation_test.rb +1 -1
- data/test/unit/job_template_effective_user_test.rb +0 -4
- data/test/unit/remote_execution_provider_test.rb +46 -4
- data/test/unit/targeting_test.rb +68 -1
- data/webpack/JobWizard/JobWizard.js +158 -24
- data/webpack/JobWizard/JobWizard.scss +93 -1
- data/webpack/JobWizard/JobWizardConstants.js +54 -0
- data/webpack/JobWizard/JobWizardSelectors.js +41 -0
- data/webpack/JobWizard/__tests__/fixtures.js +188 -3
- data/webpack/JobWizard/__tests__/integration.test.js +41 -106
- data/webpack/JobWizard/__tests__/validation.test.js +141 -0
- data/webpack/JobWizard/autofill.js +38 -0
- data/webpack/JobWizard/index.js +7 -0
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +41 -10
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +90 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +116 -55
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +354 -16
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +79 -246
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +5 -2
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +123 -51
- data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -2
- data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +62 -0
- data/webpack/JobWizard/steps/HostsAndInputs/HostSearch.js +54 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectAPI.js +33 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +52 -0
- data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +100 -0
- data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +23 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +151 -0
- data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +53 -0
- data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +18 -0
- data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +8 -0
- data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +8 -0
- data/webpack/JobWizard/steps/HostsAndInputs/index.js +214 -0
- data/webpack/JobWizard/steps/ReviewDetails/index.js +193 -0
- data/webpack/JobWizard/steps/Schedule/PurposeField.js +31 -0
- data/webpack/JobWizard/steps/Schedule/QueryType.js +51 -0
- data/webpack/JobWizard/steps/Schedule/RepeatCron.js +53 -0
- data/webpack/JobWizard/steps/Schedule/RepeatDaily.js +37 -0
- data/webpack/JobWizard/steps/Schedule/RepeatHour.js +54 -0
- data/webpack/JobWizard/steps/Schedule/RepeatMonth.js +46 -0
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +125 -0
- data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +70 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +28 -0
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +106 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +402 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +32 -0
- data/webpack/JobWizard/steps/Schedule/index.js +178 -0
- data/webpack/JobWizard/steps/form/DateTimePicker.js +126 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +5 -0
- data/webpack/JobWizard/steps/form/Formatter.js +181 -0
- data/webpack/JobWizard/steps/form/NumberInput.js +36 -0
- data/webpack/JobWizard/steps/form/ResourceSelect.js +29 -0
- data/webpack/JobWizard/steps/form/SearchSelect.js +121 -0
- data/webpack/JobWizard/steps/form/SelectField.js +28 -5
- data/webpack/JobWizard/steps/form/WizardTitle.js +14 -0
- data/webpack/JobWizard/steps/form/__tests__/SelectSearch.test.js +33 -0
- data/webpack/JobWizard/submit.js +120 -0
- data/webpack/JobWizard/validation.js +53 -0
- data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +2 -0
- data/webpack/__mocks__/foremanReact/common/I18n.js +2 -0
- data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteActions.js +1 -0
- data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteConstants.js +1 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
- data/webpack/__mocks__/foremanReact/routes/RouterSelector.js +1 -0
- data/webpack/helpers.js +1 -0
- data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +43 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +73 -66
- data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +98 -0
- data/webpack/react_app/components/RecentJobsCard/constants.js +11 -0
- data/webpack/react_app/components/RecentJobsCard/styles.scss +11 -0
- data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
- data/webpack/react_app/extend/fillRecentJobsCard.js +1 -1
- metadata +71 -16
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
- data/app/models/setting/remote_execution.rb +0 -88
- data/test/models/orchestration/ssh_test.rb +0 -56
- data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
- data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
- data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
- data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
- data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
- data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
- data/webpack/react_app/components/RecentJobsCard/styles.css +0 -15
data/package.json
CHANGED
|
@@ -21,18 +21,20 @@
|
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@babel/core": "^7.7.0",
|
|
24
|
-
"@theforeman/builder": "^
|
|
25
|
-
"@theforeman/eslint-plugin-foreman": "^
|
|
26
|
-
"@theforeman/stories": "^
|
|
27
|
-
"@theforeman/test": "^
|
|
28
|
-
"@theforeman/vendor-dev": "^
|
|
24
|
+
"@theforeman/builder": "^8.16.0",
|
|
25
|
+
"@theforeman/eslint-plugin-foreman": "^8.16.0",
|
|
26
|
+
"@theforeman/stories": "^8.16.0",
|
|
27
|
+
"@theforeman/test": "^8.16.0",
|
|
28
|
+
"@theforeman/vendor-dev": "^8.16.0",
|
|
29
29
|
"babel-eslint": "^10.0.0",
|
|
30
30
|
"eslint": "^6.8.0",
|
|
31
31
|
"prettier": "^1.19.1",
|
|
32
32
|
"@patternfly/react-catalog-view-extension": "^4.8.126",
|
|
33
|
-
"redux-mock-store": "^1.2.2"
|
|
33
|
+
"redux-mock-store": "^1.2.2",
|
|
34
|
+
"graphql-tag": "^2.11.0",
|
|
35
|
+
"graphql": "^15.5.0"
|
|
34
36
|
},
|
|
35
37
|
"peerDependencies": {
|
|
36
|
-
"@theforeman/vendor": "^8.
|
|
38
|
+
"@theforeman/vendor": "^8.16.0"
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -90,6 +90,16 @@ module Api
|
|
|
90
90
|
assert_response :success
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
test 'should create with a scheduled recurrence' do
|
|
94
|
+
@attrs[:scheduling] = { start_at: (Time.now + 1.hour) }
|
|
95
|
+
@attrs[:recurrence] = { cron_line: '5 * * * *' }
|
|
96
|
+
post :create, params: { job_invocation: @attrs }
|
|
97
|
+
invocation = ActiveSupport::JSON.decode(@response.body)
|
|
98
|
+
assert_equal 'recurring', invocation['mode']
|
|
99
|
+
assert invocation['start_at']
|
|
100
|
+
assert_response :success
|
|
101
|
+
end
|
|
102
|
+
|
|
93
103
|
context 'with_feature' do
|
|
94
104
|
setup do
|
|
95
105
|
@feature = FactoryBot.create(:remote_execution_feature,
|
|
@@ -181,6 +191,11 @@ module Api
|
|
|
181
191
|
host_id: FactoryBot.create(:host).id }
|
|
182
192
|
assert_response :missing
|
|
183
193
|
end
|
|
194
|
+
|
|
195
|
+
test 'should not break when taxonomy parameters are provided' do
|
|
196
|
+
get :output, params: { :job_invocation_id => @invocation.id, :host_id => host.id, :organization_id => host.organization_id, :location_id => host.location_id }
|
|
197
|
+
assert_response :success
|
|
198
|
+
end
|
|
184
199
|
end
|
|
185
200
|
|
|
186
201
|
describe '#outputs' do
|
|
@@ -233,6 +248,11 @@ module Api
|
|
|
233
248
|
assert_response :success
|
|
234
249
|
end
|
|
235
250
|
|
|
251
|
+
test 'should not break when taxonomy parameters are provided' do
|
|
252
|
+
get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id, :organization_id => host.organization_id, :location_id => host.location_id }
|
|
253
|
+
assert_response :success
|
|
254
|
+
end
|
|
255
|
+
|
|
236
256
|
test 'should provide raw output for delayed task' do
|
|
237
257
|
start_time = Time.now
|
|
238
258
|
JobInvocation.any_instance
|
|
@@ -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
|
|
@@ -26,7 +26,6 @@ class RemoteExecutionHelperTest < ActionView::TestCase
|
|
|
26
26
|
|
|
27
27
|
describe 'test correct setting' do
|
|
28
28
|
it 'should found correct template from setting' do
|
|
29
|
-
Setting::RemoteExecution.load_defaults
|
|
30
29
|
template_name = 'Job Invocation Report Template'
|
|
31
30
|
setting_key = 'remote_execution_job_invocation_report_template'
|
|
32
31
|
template = FactoryBot.create(:report_template, name: template_name)
|
|
@@ -36,6 +36,27 @@ module ForemanRemoteExecution
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
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
|
+
_(exception.message).must_include "infrastructure host #{template_invocation.host.name}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
39
60
|
describe '#finalize' do
|
|
40
61
|
let(:host) { FactoryBot.create(:host, :with_execution) }
|
|
41
62
|
|
|
@@ -5,6 +5,16 @@ module ForemanRemoteExecution
|
|
|
5
5
|
class RunHostsJobTest < ActiveSupport::TestCase
|
|
6
6
|
include Dynflow::Testing
|
|
7
7
|
|
|
8
|
+
# Adding run_step_id wich is needed in RunHostsJob as a quick fix
|
|
9
|
+
# it will be added to dynflow in the future see https://github.com/Dynflow/dynflow/pull/391
|
|
10
|
+
# rubocop:disable Style/ClassAndModuleChildren
|
|
11
|
+
class Dynflow::Testing::DummyPlannedAction
|
|
12
|
+
def run_step_id
|
|
13
|
+
Dynflow::Testing.get_id
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
# rubocop:enable Style/ClassAndModuleChildren
|
|
17
|
+
|
|
8
18
|
let(:host) { FactoryBot.create(:host, :with_execution) }
|
|
9
19
|
let(:proxy) { host.remote_execution_proxies('SSH')[:subnet].first }
|
|
10
20
|
let(:targeting) { FactoryBot.create(:targeting, :search_query => "name = #{host.name}", :user => User.current) }
|
|
@@ -94,6 +104,22 @@ module ForemanRemoteExecution
|
|
|
94
104
|
planned # To make the expectations happy
|
|
95
105
|
end
|
|
96
106
|
|
|
107
|
+
describe '#proxy_batch_size' do
|
|
108
|
+
it 'defaults to Setting[foreman_tasks_proxy_batch_size]' do
|
|
109
|
+
Setting.expects(:[]).with('foreman_tasks_proxy_batch_size').returns(14)
|
|
110
|
+
planned
|
|
111
|
+
_(planned.proxy_batch_size).must_equal 14
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'gets the provider value' do
|
|
115
|
+
provider = mock('provider')
|
|
116
|
+
provider.expects(:proxy_batch_size).returns(15)
|
|
117
|
+
JobTemplate.any_instance.expects(:provider).returns(provider)
|
|
118
|
+
|
|
119
|
+
_(planned.proxy_batch_size).must_equal 15
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
97
123
|
describe 'concurrency control' do
|
|
98
124
|
let(:level) { 5 }
|
|
99
125
|
let(:span) { 60 }
|
|
@@ -127,10 +153,79 @@ module ForemanRemoteExecution
|
|
|
127
153
|
end
|
|
128
154
|
|
|
129
155
|
describe 'notifications' do
|
|
130
|
-
it 'creates notification on
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
156
|
+
it 'creates drawer notification on succeess' do
|
|
157
|
+
blueprint = planned.job_invocation.build_notification
|
|
158
|
+
blueprint.expects(:deliver!)
|
|
159
|
+
planned.job_invocation.expects(:build_notification).returns(blueprint)
|
|
160
|
+
planned.notify_on_success(nil)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
it 'creates drawer notification on failure' do
|
|
164
|
+
blueprint = planned.job_invocation.build_notification
|
|
165
|
+
blueprint.expects(:deliver!)
|
|
166
|
+
planned.job_invocation.expects(:build_notification).returns(blueprint)
|
|
167
|
+
planned.notify_on_failure(nil)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
describe 'ignoring drawer notification' do
|
|
171
|
+
before do
|
|
172
|
+
blueprint = planned.job_invocation.build_notification
|
|
173
|
+
blueprint.expects(:deliver!)
|
|
174
|
+
planned.job_invocation.expects(:build_notification).returns(blueprint)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
let(:mail) do
|
|
178
|
+
object = mock
|
|
179
|
+
object.stubs(:deliver_now)
|
|
180
|
+
object
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
describe 'for user subscribed to all' do
|
|
184
|
+
before do
|
|
185
|
+
planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::ALL_JOBS))
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
it 'sends the mail notification on success' do
|
|
189
|
+
RexJobMailer.expects(:job_finished).returns(mail)
|
|
190
|
+
planned.notify_on_success(nil)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
it 'sends the mail notification on failure' do
|
|
194
|
+
RexJobMailer.expects(:job_finished).returns(mail)
|
|
195
|
+
planned.notify_on_failure(nil)
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
describe 'for user subscribed to failures' do
|
|
200
|
+
before do
|
|
201
|
+
planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::FAILED_JOBS))
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it 'it does not send the mail notification on success' do
|
|
205
|
+
RexJobMailer.expects(:job_finished).never
|
|
206
|
+
planned.notify_on_success(nil)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'sends the mail notification on failure' do
|
|
210
|
+
RexJobMailer.expects(:job_finished).returns(mail)
|
|
211
|
+
planned.notify_on_failure(nil)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
describe 'for user subscribed to successful jobs' do
|
|
216
|
+
before do
|
|
217
|
+
planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::SUCCEEDED_JOBS))
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'sends the mail notification on success' do
|
|
221
|
+
RexJobMailer.expects(:job_finished).returns(mail)
|
|
222
|
+
planned.notify_on_success(nil)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'does not send the mail notification on failure' do
|
|
226
|
+
RexJobMailer.expects(:job_finished).never
|
|
227
|
+
planned.notify_on_failure(nil)
|
|
228
|
+
end
|
|
134
229
|
end
|
|
135
230
|
end
|
|
136
231
|
end
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
require 'test_plugin_helper'
|
|
2
2
|
|
|
3
3
|
class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
4
|
-
before do
|
|
5
|
-
Setting::RemoteExecution.load_defaults
|
|
6
|
-
end
|
|
7
4
|
let(:provider) { 'SSH' }
|
|
8
5
|
|
|
9
6
|
before { User.current = FactoryBot.build(:user, :admin) }
|
|
@@ -131,23 +128,23 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
|
131
128
|
end
|
|
132
129
|
|
|
133
130
|
context 'fallback strategy' do
|
|
134
|
-
let(:host) { FactoryBot.build(:host, :
|
|
131
|
+
let(:host) { FactoryBot.build(:host, :with_tftp_subnet) }
|
|
135
132
|
|
|
136
133
|
context 'enabled' do
|
|
137
134
|
before do
|
|
138
135
|
Setting[:remote_execution_fallback_proxy] = true
|
|
139
|
-
host.
|
|
136
|
+
host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
|
|
140
137
|
end
|
|
141
138
|
|
|
142
139
|
it 'returns a fallback proxy' do
|
|
143
|
-
host.remote_execution_proxies(provider)[:fallback].must_include host.
|
|
140
|
+
host.remote_execution_proxies(provider)[:fallback].must_include host.subnet.tftp
|
|
144
141
|
end
|
|
145
142
|
end
|
|
146
143
|
|
|
147
144
|
context 'disabled' do
|
|
148
145
|
before do
|
|
149
146
|
Setting[:remote_execution_fallback_proxy] = false
|
|
150
|
-
host.
|
|
147
|
+
host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
|
|
151
148
|
end
|
|
152
149
|
|
|
153
150
|
it 'returns no proxy' do
|
|
@@ -182,4 +179,40 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
|
182
179
|
end
|
|
183
180
|
end
|
|
184
181
|
end
|
|
182
|
+
|
|
183
|
+
describe '#execution_scope' do
|
|
184
|
+
let(:host) { FactoryBot.create(:host) }
|
|
185
|
+
let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
|
|
186
|
+
|
|
187
|
+
before do
|
|
188
|
+
host
|
|
189
|
+
infra_host
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
context 'without infrastructure host permission' do
|
|
193
|
+
it 'omits the infrastructure host' do
|
|
194
|
+
setup_user('view', 'hosts')
|
|
195
|
+
|
|
196
|
+
hosts = ::Host::Managed.execution_scope
|
|
197
|
+
hosts.must_include host
|
|
198
|
+
hosts.wont_include infra_host
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
context 'with infrastructure host permission' do
|
|
203
|
+
it 'finds the host as admin' do
|
|
204
|
+
assert User.current.admin?
|
|
205
|
+
hosts = ::Host::Managed.execution_scope
|
|
206
|
+
hosts.must_include host
|
|
207
|
+
hosts.must_include infra_host
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'finds the host as user with needed permissions' do
|
|
211
|
+
setup_user('execute_jobs_on', 'infrastructure_hosts')
|
|
212
|
+
hosts = ::Host::Managed.execution_scope
|
|
213
|
+
hosts.must_include host
|
|
214
|
+
hosts.must_include infra_host
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
185
218
|
end
|
|
@@ -446,8 +446,7 @@ class InputTemplateRendererTest < ActiveSupport::TestCase
|
|
|
446
446
|
before { User.current = FactoryBot.build(:user, :admin) }
|
|
447
447
|
after { User.current = nil }
|
|
448
448
|
|
|
449
|
-
|
|
450
|
-
before { renderer.host = FactoryBot.create(:host, :environment => environment) }
|
|
449
|
+
before { renderer.host = FactoryBot.create(:host) }
|
|
451
450
|
|
|
452
451
|
describe 'rendering' do
|
|
453
452
|
it 'can\'t render the content without host since we don\'t have variable value in classification' do
|
|
@@ -497,91 +496,4 @@ class InputTemplateRendererTest < ActiveSupport::TestCase
|
|
|
497
496
|
end
|
|
498
497
|
end
|
|
499
498
|
end
|
|
500
|
-
|
|
501
|
-
context 'renderer for template with puppet parameter input used' do
|
|
502
|
-
let(:template) { FactoryBot.build(:job_template, :template => 'echo "This is WebServer with nginx <%= input("nginx_version") -%>" > /etc/motd') }
|
|
503
|
-
let(:renderer) { InputTemplateRenderer.new(template) }
|
|
504
|
-
|
|
505
|
-
context 'with matching input defined' do
|
|
506
|
-
before do
|
|
507
|
-
renderer.template.template_inputs<< FactoryBot.build(:template_input,
|
|
508
|
-
:name => 'nginx_version',
|
|
509
|
-
:input_type => 'puppet_parameter',
|
|
510
|
-
:puppet_parameter_name => 'version',
|
|
511
|
-
:puppet_class_name => 'nginx')
|
|
512
|
-
end
|
|
513
|
-
let(:result) { renderer.render }
|
|
514
|
-
|
|
515
|
-
describe 'rendering' do
|
|
516
|
-
it 'can\'t render the content without host since we don\'t have host so no classification' do
|
|
517
|
-
assert_not result
|
|
518
|
-
end
|
|
519
|
-
|
|
520
|
-
it 'registers an error' do
|
|
521
|
-
result # let is lazy
|
|
522
|
-
_(renderer.error_message).wont_be_nil
|
|
523
|
-
_(renderer.error_message).wont_be_empty
|
|
524
|
-
end
|
|
525
|
-
|
|
526
|
-
context 'with host specified' do
|
|
527
|
-
let(:environment) { FactoryBot.create(:environment) }
|
|
528
|
-
before { renderer.host = FactoryBot.create(:host, :environment => environment) }
|
|
529
|
-
|
|
530
|
-
describe 'rendering' do
|
|
531
|
-
it 'can\'t render the content without host since we don\'t have puppet parameter in classification' do
|
|
532
|
-
assert_not result
|
|
533
|
-
end
|
|
534
|
-
|
|
535
|
-
it 'registers an error' do
|
|
536
|
-
result # let is lazy
|
|
537
|
-
_(renderer.error_message).wont_be_nil
|
|
538
|
-
_(renderer.error_message).wont_be_empty
|
|
539
|
-
end
|
|
540
|
-
end
|
|
541
|
-
|
|
542
|
-
describe 'preview' do
|
|
543
|
-
it 'should render preview' do
|
|
544
|
-
_(renderer.preview).must_equal 'echo "This is WebServer with nginx $PUPPET_PARAMETER_INPUT[nginx_version]" > /etc/motd'
|
|
545
|
-
end
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
context 'with existing puppet parameter with matching override' do
|
|
549
|
-
let(:puppet_class) do
|
|
550
|
-
puppetclass = FactoryBot.create(:puppetclass, :environments => [environment], :name => 'nginx')
|
|
551
|
-
puppetclass.update_attribute(:hosts, [renderer.host])
|
|
552
|
-
puppetclass
|
|
553
|
-
end
|
|
554
|
-
let(:lookup_key) do
|
|
555
|
-
FactoryBot.create(:puppetclass_lookup_key, :as_smart_class_param,
|
|
556
|
-
:key => 'version',
|
|
557
|
-
:puppetclass => puppet_class,
|
|
558
|
-
:path => 'fqdn',
|
|
559
|
-
:override => true,
|
|
560
|
-
:overrides => {"fqdn=#{renderer.host.fqdn}" => '1.4.7'})
|
|
561
|
-
end
|
|
562
|
-
|
|
563
|
-
describe 'rendering' do
|
|
564
|
-
it 'renders the value from puppet parameter' do
|
|
565
|
-
lookup_key
|
|
566
|
-
_(result).must_equal 'echo "This is WebServer with nginx 1.4.7" > /etc/motd'
|
|
567
|
-
end
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
describe 'preview' do
|
|
571
|
-
it 'should render preview' do
|
|
572
|
-
lookup_key
|
|
573
|
-
_(renderer.preview).must_equal 'echo "This is WebServer with nginx 1.4.7" > /etc/motd'
|
|
574
|
-
end
|
|
575
|
-
end
|
|
576
|
-
end
|
|
577
|
-
end
|
|
578
|
-
|
|
579
|
-
describe 'preview' do
|
|
580
|
-
it 'should render preview' do
|
|
581
|
-
_(renderer.preview).must_equal 'echo "This is WebServer with nginx $PUPPET_PARAMETER_INPUT[nginx_version]" > /etc/motd'
|
|
582
|
-
end
|
|
583
|
-
end
|
|
584
|
-
end
|
|
585
|
-
end
|
|
586
|
-
end
|
|
587
499
|
end
|
|
@@ -270,10 +270,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
270
270
|
composer.pattern_template_invocations.first
|
|
271
271
|
end
|
|
272
272
|
|
|
273
|
-
before do
|
|
274
|
-
Setting::RemoteExecution.load_defaults
|
|
275
|
-
end
|
|
276
|
-
|
|
277
273
|
context 'when overridable and provided' do
|
|
278
274
|
let(:overridable) { true }
|
|
279
275
|
let(:invocation_effective_user) { 'invocation user' }
|
|
@@ -350,12 +346,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
350
346
|
end
|
|
351
347
|
|
|
352
348
|
describe '#available_bookmarks' do
|
|
353
|
-
it 'obeys authorization' do
|
|
354
|
-
composer
|
|
355
|
-
Bookmark.expects(:authorized).with(:view_bookmarks).returns(Bookmark.where({}))
|
|
356
|
-
composer.available_bookmarks
|
|
357
|
-
end
|
|
358
|
-
|
|
359
349
|
context 'there are hostgroups and hosts bookmark' do
|
|
360
350
|
let(:hostgroups) { Bookmark.create(:name => 'hostgroups', :query => 'name = x', :controller => 'hostgroups') }
|
|
361
351
|
let(:hosts) { Bookmark.create(:name => 'hosts', :query => 'name = x', :controller => 'hosts') }
|
|
@@ -376,8 +366,10 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
376
366
|
let(:host) { FactoryBot.create(:host) }
|
|
377
367
|
|
|
378
368
|
it 'obeys authorization' do
|
|
369
|
+
fake_scope = mock
|
|
379
370
|
composer.stubs(:displayed_search_query => "name = #{host.name}")
|
|
380
|
-
Host.expects(:
|
|
371
|
+
Host.expects(:execution_scope).returns(fake_scope)
|
|
372
|
+
fake_scope.expects(:authorized).with(:view_hosts, Host).returns(Host.where({}))
|
|
381
373
|
composer.targeted_hosts_count
|
|
382
374
|
end
|
|
383
375
|
|
|
@@ -864,7 +856,9 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
864
856
|
it 'marks targeting as resolved if static' do
|
|
865
857
|
created = JobInvocationComposer.from_job_invocation(job_invocation).job_invocation
|
|
866
858
|
assert created.targeting.resolved?
|
|
867
|
-
|
|
859
|
+
created.targeting.save
|
|
860
|
+
created.targeting.reload
|
|
861
|
+
assert_equal job_invocation.template_invocations_host_ids, created.targeting.targeting_hosts.pluck(:host_id)
|
|
868
862
|
end
|
|
869
863
|
|
|
870
864
|
it 'takes randomized_ordering from the original job invocation when rerunning failed' do
|
|
@@ -874,6 +868,17 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
874
868
|
composer = JobInvocationComposer.from_job_invocation(job_invocation, :host_ids => host_ids)
|
|
875
869
|
assert composer.job_invocation.targeting.randomized_ordering
|
|
876
870
|
end
|
|
871
|
+
|
|
872
|
+
it 'works with invalid hosts' do
|
|
873
|
+
host = job_invocation.targeting.hosts.first
|
|
874
|
+
::Host::Managed.any_instance.stubs(:valid?).returns(false)
|
|
875
|
+
composer = JobInvocationComposer.from_job_invocation(job_invocation, {})
|
|
876
|
+
targeting = composer.compose.job_invocation.targeting
|
|
877
|
+
targeting.save!
|
|
878
|
+
targeting.reload
|
|
879
|
+
assert targeting.valid?
|
|
880
|
+
assert_equal targeting.hosts.pluck(:id), [host.id]
|
|
881
|
+
end
|
|
877
882
|
end
|
|
878
883
|
|
|
879
884
|
describe '.for_feature' do
|
|
@@ -918,12 +923,7 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
918
923
|
|
|
919
924
|
context 'with template in setting present' do
|
|
920
925
|
before do
|
|
921
|
-
|
|
922
|
-
:setting,
|
|
923
|
-
:name => 'remote_execution_form_job_template',
|
|
924
|
-
:category => 'Setting::RemoteExecution',
|
|
925
|
-
:value => setting_template.name
|
|
926
|
-
)
|
|
926
|
+
Setting[:remote_execution_form_job_template] = setting_template.name
|
|
927
927
|
end
|
|
928
928
|
|
|
929
929
|
it 'should resolve category to the setting value' do
|
|
@@ -19,7 +19,7 @@ class JobReportTemplateTest < ActiveSupport::TestCase
|
|
|
19
19
|
it 'in settings includes only report templates with job_id input' do
|
|
20
20
|
FactoryBot.create(:report_template, name: 'Template 1')
|
|
21
21
|
job_invocation_template
|
|
22
|
-
templates =
|
|
22
|
+
templates = ForemanRemoteExecution.job_invocation_report_templates_select
|
|
23
23
|
|
|
24
24
|
assert_include templates, 'Job Invocation Report Template'
|
|
25
25
|
end
|
|
@@ -28,15 +28,16 @@ class JobReportTemplateTest < ActiveSupport::TestCase
|
|
|
28
28
|
describe 'task reporting' do
|
|
29
29
|
let(:fake_outputs) do
|
|
30
30
|
[
|
|
31
|
-
{ 'output_type' => 'stderr', 'output' => "error"
|
|
32
|
-
{ 'output_type' => 'stdout', 'output' => "output"
|
|
33
|
-
{ 'output_type' => '
|
|
31
|
+
{ 'output_type' => 'stderr', 'output' => "error" },
|
|
32
|
+
{ 'output_type' => 'stdout', 'output' => "output" },
|
|
33
|
+
{ 'output_type' => 'debug', 'output' => "debug" },
|
|
34
34
|
]
|
|
35
35
|
end
|
|
36
|
-
let(:fake_task) { FakeTask.new(result: 'success', action_continuous_output: fake_outputs) }
|
|
36
|
+
let(:fake_task) { FakeTask.new(result: 'success', action_continuous_output: fake_outputs, :ended_at => Time.new(2020, 12, 1, 0, 0, 0).utc) }
|
|
37
|
+
let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
|
|
38
|
+
let(:host) { job_invocation.template_invocations.first.host }
|
|
37
39
|
|
|
38
40
|
it 'should render task outputs' do
|
|
39
|
-
job_invocation = FactoryBot.create(:job_invocation, :with_task)
|
|
40
41
|
JobInvocation.any_instance.expects(:sub_task_for_host).returns(fake_task)
|
|
41
42
|
|
|
42
43
|
input = job_invocation_template.template_inputs.first
|
|
@@ -44,13 +45,15 @@ class JobReportTemplateTest < ActiveSupport::TestCase
|
|
|
44
45
|
result = ReportComposer.new(composer_params).render
|
|
45
46
|
|
|
46
47
|
# parsing the CSV result
|
|
47
|
-
CSV.parse(result.strip, headers: true)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
rows = CSV.parse(result.strip, headers: true)
|
|
49
|
+
assert_equal 1, rows.count
|
|
50
|
+
row = rows.first
|
|
51
|
+
assert_equal host.name, row['Host']
|
|
52
|
+
assert_equal 'success', row['Result']
|
|
53
|
+
assert_equal 'error', row['stderr']
|
|
54
|
+
assert_equal 'output', row['stdout']
|
|
55
|
+
assert_equal 'debug', row['debug']
|
|
56
|
+
assert_kind_of Time, Time.zone.parse(row['Finished']), 'Parsing of time column failed'
|
|
54
57
|
end
|
|
55
58
|
end
|
|
56
59
|
end
|