foreman_remote_execution 4.6.0 → 4.7.0
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/app/controllers/job_invocations_controller.rb +1 -1
- data/app/controllers/ui_job_wizard_controller.rb +7 -0
- data/app/graphql/types/job_invocation.rb +16 -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 +5 -1
- data/app/lib/actions/remote_execution/run_hosts_job.rb +1 -1
- data/app/models/concerns/foreman_remote_execution/host_extensions.rb +2 -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 +9 -6
- data/app/models/job_invocation_composer.rb +4 -4
- data/app/models/job_template.rb +1 -1
- data/app/models/remote_execution_feature.rb +5 -1
- data/app/models/setting/remote_execution.rb +2 -2
- data/app/models/targeting.rb +5 -1
- data/app/views/job_invocations/index.html.erb +1 -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/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
- data/foreman_remote_execution.gemspec +2 -3
- data/lib/foreman_remote_execution/engine.rb +3 -2
- data/lib/foreman_remote_execution/version.rb +1 -1
- data/test/graphql/queries/job_invocation_query_test.rb +31 -0
- data/test/graphql/queries/job_invocations_query_test.rb +35 -0
- data/test/unit/concerns/host_extensions_test.rb +4 -4
- data/test/unit/input_template_renderer_test.rb +1 -89
- data/test/unit/job_invocation_composer_test.rb +15 -13
- data/test/unit/job_invocation_test.rb +1 -1
- data/webpack/JobWizard/JobWizard.js +28 -8
- data/webpack/JobWizard/JobWizard.scss +39 -0
- data/webpack/JobWizard/JobWizardConstants.js +10 -0
- data/webpack/JobWizard/JobWizardSelectors.js +9 -0
- data/webpack/JobWizard/__tests__/fixtures.js +104 -2
- data/webpack/JobWizard/__tests__/integration.test.js +13 -85
- data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +21 -4
- data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
- data/webpack/JobWizard/steps/AdvancedFields/Fields.js +73 -59
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +135 -16
- data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
- data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -51
- data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
- data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
- data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
- data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
- data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
- data/webpack/JobWizard/steps/Schedule/index.js +41 -0
- data/webpack/JobWizard/steps/form/FormHelpers.js +1 -0
- data/webpack/JobWizard/steps/form/Formatter.js +149 -0
- data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
- data/webpack/JobWizard/steps/form/SelectField.js +14 -2
- data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
- data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
- data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +43 -0
- data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +72 -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 +25 -16
- data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
- 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/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +0 -249
- 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
|
@@ -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
|
|
@@ -131,23 +131,23 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
|
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
context 'fallback strategy' do
|
|
134
|
-
let(:host) { FactoryBot.build(:host, :
|
|
134
|
+
let(:host) { FactoryBot.build(:host, :with_tftp_subnet) }
|
|
135
135
|
|
|
136
136
|
context 'enabled' do
|
|
137
137
|
before do
|
|
138
138
|
Setting[:remote_execution_fallback_proxy] = true
|
|
139
|
-
host.
|
|
139
|
+
host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
it 'returns a fallback proxy' do
|
|
143
|
-
host.remote_execution_proxies(provider)[:fallback].must_include host.
|
|
143
|
+
host.remote_execution_proxies(provider)[:fallback].must_include host.subnet.tftp
|
|
144
144
|
end
|
|
145
145
|
end
|
|
146
146
|
|
|
147
147
|
context 'disabled' do
|
|
148
148
|
before do
|
|
149
149
|
Setting[:remote_execution_fallback_proxy] = false
|
|
150
|
-
host.
|
|
150
|
+
host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
it 'returns no proxy' do
|
|
@@ -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
|
|
@@ -350,12 +350,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
350
350
|
end
|
|
351
351
|
|
|
352
352
|
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
353
|
context 'there are hostgroups and hosts bookmark' do
|
|
360
354
|
let(:hostgroups) { Bookmark.create(:name => 'hostgroups', :query => 'name = x', :controller => 'hostgroups') }
|
|
361
355
|
let(:hosts) { Bookmark.create(:name => 'hosts', :query => 'name = x', :controller => 'hosts') }
|
|
@@ -864,7 +858,9 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
864
858
|
it 'marks targeting as resolved if static' do
|
|
865
859
|
created = JobInvocationComposer.from_job_invocation(job_invocation).job_invocation
|
|
866
860
|
assert created.targeting.resolved?
|
|
867
|
-
|
|
861
|
+
created.targeting.save
|
|
862
|
+
created.targeting.reload
|
|
863
|
+
assert_equal job_invocation.template_invocations_host_ids, created.targeting.targeting_hosts.pluck(:host_id)
|
|
868
864
|
end
|
|
869
865
|
|
|
870
866
|
it 'takes randomized_ordering from the original job invocation when rerunning failed' do
|
|
@@ -874,6 +870,17 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
874
870
|
composer = JobInvocationComposer.from_job_invocation(job_invocation, :host_ids => host_ids)
|
|
875
871
|
assert composer.job_invocation.targeting.randomized_ordering
|
|
876
872
|
end
|
|
873
|
+
|
|
874
|
+
it 'works with invalid hosts' do
|
|
875
|
+
host = job_invocation.targeting.hosts.first
|
|
876
|
+
::Host::Managed.any_instance.stubs(:valid?).returns(false)
|
|
877
|
+
composer = JobInvocationComposer.from_job_invocation(job_invocation, {})
|
|
878
|
+
targeting = composer.compose.job_invocation.targeting
|
|
879
|
+
targeting.save!
|
|
880
|
+
targeting.reload
|
|
881
|
+
assert targeting.valid?
|
|
882
|
+
assert_equal targeting.hosts.pluck(:id), [host.id]
|
|
883
|
+
end
|
|
877
884
|
end
|
|
878
885
|
|
|
879
886
|
describe '.for_feature' do
|
|
@@ -918,12 +925,7 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
|
|
|
918
925
|
|
|
919
926
|
context 'with template in setting present' do
|
|
920
927
|
before do
|
|
921
|
-
|
|
922
|
-
:setting,
|
|
923
|
-
:name => 'remote_execution_form_job_template',
|
|
924
|
-
:category => 'Setting::RemoteExecution',
|
|
925
|
-
:value => setting_template.name
|
|
926
|
-
)
|
|
928
|
+
Setting[:remote_execution_form_job_template] = setting_template.name
|
|
927
929
|
end
|
|
928
930
|
|
|
929
931
|
it 'should resolve category to the setting value' do
|
|
@@ -10,7 +10,7 @@ class JobInvocationTest < ActiveSupport::TestCase
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
it 'is able to perform search through job invocations' do
|
|
13
|
-
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).
|
|
13
|
+
found_jobs = JobInvocation.search_for(%{job_category = "#{job_invocation.job_category}"}).paginate(:page => 1).order('job_invocations.id DESC')
|
|
14
14
|
_(found_jobs).must_equal [job_invocation]
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
1
2
|
import React, { useState, useEffect, useCallback } from 'react';
|
|
2
3
|
import { useDispatch, useSelector } from 'react-redux';
|
|
3
4
|
import { Wizard } from '@patternfly/react-core';
|
|
@@ -8,6 +9,7 @@ import CategoryAndTemplate from './steps/CategoryAndTemplate/';
|
|
|
8
9
|
import { AdvancedFields } from './steps/AdvancedFields/AdvancedFields';
|
|
9
10
|
import { JOB_TEMPLATE } from './JobWizardConstants';
|
|
10
11
|
import { selectTemplateError } from './JobWizardSelectors';
|
|
12
|
+
import Schedule from './steps/Schedule/';
|
|
11
13
|
import './JobWizard.scss';
|
|
12
14
|
|
|
13
15
|
export const JobWizard = () => {
|
|
@@ -16,13 +18,31 @@ export const JobWizard = () => {
|
|
|
16
18
|
const [advancedValues, setAdvancedValues] = useState({});
|
|
17
19
|
const dispatch = useDispatch();
|
|
18
20
|
|
|
19
|
-
const setDefaults = useCallback(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
const setDefaults = useCallback(
|
|
22
|
+
({
|
|
23
|
+
data: {
|
|
24
|
+
advanced_template_inputs,
|
|
25
|
+
effective_user,
|
|
26
|
+
job_template: { executionTimeoutInterval, description_format },
|
|
27
|
+
},
|
|
28
|
+
}) => {
|
|
29
|
+
const advancedTemplateValues = {};
|
|
30
|
+
const advancedInputs = advanced_template_inputs;
|
|
31
|
+
if (advancedInputs) {
|
|
32
|
+
advancedInputs.forEach(input => {
|
|
33
|
+
advancedTemplateValues[input.name] = input?.default || '';
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
setAdvancedValues(currentAdvancedValues => ({
|
|
37
|
+
...currentAdvancedValues,
|
|
38
|
+
effectiveUserValue: effective_user?.value || '',
|
|
39
|
+
timeoutToKill: executionTimeoutInterval || '',
|
|
40
|
+
templateValues: advancedTemplateValues,
|
|
41
|
+
description: description_format || '',
|
|
42
|
+
}));
|
|
43
|
+
},
|
|
44
|
+
[]
|
|
45
|
+
);
|
|
26
46
|
useEffect(() => {
|
|
27
47
|
if (jobTemplateID) {
|
|
28
48
|
dispatch(
|
|
@@ -72,7 +92,7 @@ export const JobWizard = () => {
|
|
|
72
92
|
},
|
|
73
93
|
{
|
|
74
94
|
name: __('Schedule'),
|
|
75
|
-
component: <
|
|
95
|
+
component: <Schedule />,
|
|
76
96
|
canJumpTo: isTemplate,
|
|
77
97
|
},
|
|
78
98
|
{
|
|
@@ -10,5 +10,44 @@
|
|
|
10
10
|
.advanced-fields-title {
|
|
11
11
|
margin-bottom: 10px;
|
|
12
12
|
}
|
|
13
|
+
#advanced-fields-job-template {
|
|
14
|
+
.foreman-search-field {
|
|
15
|
+
// Giving pf3 search bar a pf4 look
|
|
16
|
+
.search-bar {
|
|
17
|
+
display: block;
|
|
18
|
+
}
|
|
19
|
+
.input-group-btn {
|
|
20
|
+
display: none;
|
|
21
|
+
}
|
|
22
|
+
li {
|
|
23
|
+
font-size: 16px;
|
|
24
|
+
}
|
|
25
|
+
input {
|
|
26
|
+
font-size: 16px;
|
|
27
|
+
height: 36px;
|
|
28
|
+
}
|
|
29
|
+
.foreman-autocomplete .autocomplete-focus-shortcut {
|
|
30
|
+
top: 8px;
|
|
31
|
+
font-size: 16px;
|
|
32
|
+
}
|
|
33
|
+
.foreman-autocomplete .autocomplete-aux {
|
|
34
|
+
top: 8px;
|
|
35
|
+
font-size: 16px;
|
|
36
|
+
.autocomplete-clear-button {
|
|
37
|
+
font-size: 16px;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.schedule-tab {
|
|
45
|
+
input[type='radio'],
|
|
46
|
+
input[type='checkbox'] {
|
|
47
|
+
margin: 0;
|
|
48
|
+
}
|
|
49
|
+
.advanced-scheduling-button {
|
|
50
|
+
text-align: start;
|
|
51
|
+
}
|
|
13
52
|
}
|
|
14
53
|
}
|
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
|
1
2
|
import { foremanUrl } from 'foremanReact/common/helpers';
|
|
2
3
|
|
|
3
4
|
export const JOB_TEMPLATES = 'JOB_TEMPLATES';
|
|
4
5
|
export const JOB_CATEGORIES = 'JOB_CATEGORIES';
|
|
5
6
|
export const JOB_TEMPLATE = 'JOB_TEMPLATE';
|
|
6
7
|
export const templatesUrl = foremanUrl('/api/v2/job_templates');
|
|
8
|
+
|
|
9
|
+
export const repeatTypes = {
|
|
10
|
+
noRepeat: __('Does not repeat'),
|
|
11
|
+
cronline: __('Cronline'),
|
|
12
|
+
monthly: __('Monthly'),
|
|
13
|
+
weekly: __('Weekly'),
|
|
14
|
+
daily: __('Daily'),
|
|
15
|
+
hourly: __('Hourly'),
|
|
16
|
+
};
|
|
@@ -36,3 +36,12 @@ export const selectTemplateError = state =>
|
|
|
36
36
|
|
|
37
37
|
export const selectJobTemplate = state =>
|
|
38
38
|
selectAPIResponse(state, JOB_TEMPLATE);
|
|
39
|
+
|
|
40
|
+
export const selectEffectiveUser = state =>
|
|
41
|
+
selectAPIResponse(state, JOB_TEMPLATE).effective_user;
|
|
42
|
+
|
|
43
|
+
export const selectAdvancedTemplateInputs = state =>
|
|
44
|
+
selectAPIResponse(state, JOB_TEMPLATE).advanced_template_inputs || [];
|
|
45
|
+
|
|
46
|
+
export const selectTemplateInputs = state =>
|
|
47
|
+
selectAPIResponse(state, JOB_TEMPLATE).template_inputs || [];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import configureMockStore from 'redux-mock-store';
|
|
2
|
+
|
|
3
|
+
export const jobTemplate = {
|
|
2
4
|
id: 178,
|
|
3
|
-
name: '
|
|
5
|
+
name: 'template1',
|
|
4
6
|
template:
|
|
5
7
|
"---\n- hosts: all\n tasks:\n - shell:\n cmd: |\n<%= indent(10) { input('command') } %>\n register: out\n - debug: var=out",
|
|
6
8
|
snippet: false,
|
|
@@ -23,4 +25,104 @@ export const jobTemplateResponse = {
|
|
|
23
25
|
overridable: true,
|
|
24
26
|
current_user: false,
|
|
25
27
|
},
|
|
28
|
+
advanced_template_inputs: [
|
|
29
|
+
{
|
|
30
|
+
name: 'adv plain hidden',
|
|
31
|
+
required: true,
|
|
32
|
+
input_type: 'user',
|
|
33
|
+
description: 'some Description',
|
|
34
|
+
advanced: true,
|
|
35
|
+
value_type: 'plain',
|
|
36
|
+
resource_type: 'ansible_roles',
|
|
37
|
+
default: 'Default val',
|
|
38
|
+
hidden_value: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'adv plain select',
|
|
42
|
+
required: false,
|
|
43
|
+
input_type: 'user',
|
|
44
|
+
options: 'option 1\r\noption 2\r\noption 3\r\noption 4',
|
|
45
|
+
advanced: true,
|
|
46
|
+
value_type: 'plain',
|
|
47
|
+
resource_type: 'ansible_roles',
|
|
48
|
+
default: '',
|
|
49
|
+
hidden_value: false,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'adv search',
|
|
53
|
+
required: false,
|
|
54
|
+
options: '',
|
|
55
|
+
advanced: true,
|
|
56
|
+
value_type: 'search',
|
|
57
|
+
resource_type: 'foreman_tasks/tasks',
|
|
58
|
+
default: '',
|
|
59
|
+
hidden_value: false,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'adv date',
|
|
63
|
+
required: false,
|
|
64
|
+
options: '',
|
|
65
|
+
advanced: true,
|
|
66
|
+
value_type: 'date',
|
|
67
|
+
resource_type: 'ansible_roles',
|
|
68
|
+
default: '',
|
|
69
|
+
hidden_value: false,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
template_inputs: [
|
|
73
|
+
{
|
|
74
|
+
name: 'plain hidden',
|
|
75
|
+
required: true,
|
|
76
|
+
input_type: 'user',
|
|
77
|
+
description: 'some Description',
|
|
78
|
+
advanced: false,
|
|
79
|
+
value_type: 'plain',
|
|
80
|
+
resource_type: 'ansible_roles',
|
|
81
|
+
default: 'Default val',
|
|
82
|
+
hidden_value: true,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export const jobCategories = ['Ansible Commands', 'Puppet', 'Services'];
|
|
88
|
+
|
|
89
|
+
export const testSetup = (selectors, api) => {
|
|
90
|
+
jest.spyOn(api, 'get');
|
|
91
|
+
jest.spyOn(selectors, 'selectJobTemplate');
|
|
92
|
+
jest.spyOn(selectors, 'selectJobTemplates');
|
|
93
|
+
jest.spyOn(selectors, 'selectJobCategories');
|
|
94
|
+
jest.spyOn(selectors, 'selectJobCategoriesStatus');
|
|
95
|
+
|
|
96
|
+
selectors.selectJobCategories.mockImplementation(() => jobCategories);
|
|
97
|
+
selectors.selectJobTemplates.mockImplementation(() => [
|
|
98
|
+
jobTemplate,
|
|
99
|
+
{ ...jobTemplate, id: 2, name: 'template2' },
|
|
100
|
+
]);
|
|
101
|
+
const mockStore = configureMockStore([]);
|
|
102
|
+
const store = mockStore({});
|
|
103
|
+
return store;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const mockTemplate = selectors => {
|
|
107
|
+
selectors.selectJobTemplate.mockImplementation(() => jobTemplate);
|
|
108
|
+
selectors.selectJobCategoriesStatus.mockImplementation(() => 'RESOLVED');
|
|
109
|
+
};
|
|
110
|
+
export const mockApi = api => {
|
|
111
|
+
api.get.mockImplementation(({ handleSuccess, ...action }) => {
|
|
112
|
+
if (action.key === 'JOB_CATEGORIES') {
|
|
113
|
+
handleSuccess &&
|
|
114
|
+
handleSuccess({ data: { job_categories: jobCategories } });
|
|
115
|
+
} else if (action.key === 'JOB_TEMPLATE') {
|
|
116
|
+
handleSuccess &&
|
|
117
|
+
handleSuccess({
|
|
118
|
+
data: jobTemplateResponse,
|
|
119
|
+
});
|
|
120
|
+
} else if (action.key === 'JOB_TEMPLATES') {
|
|
121
|
+
handleSuccess &&
|
|
122
|
+
handleSuccess({
|
|
123
|
+
data: { results: [jobTemplate] },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return { type: 'get', ...action };
|
|
127
|
+
});
|
|
26
128
|
};
|