foreman_remote_execution 4.4.0 → 4.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/api/v2/job_invocations_controller.rb +13 -24
  3. data/app/controllers/job_invocations_controller.rb +1 -1
  4. data/app/controllers/job_templates_controller.rb +4 -4
  5. data/app/controllers/ui_job_wizard_controller.rb +19 -0
  6. data/app/helpers/job_invocations_helper.rb +2 -2
  7. data/app/helpers/remote_execution_helper.rb +13 -9
  8. data/app/lib/actions/remote_execution/run_host_job.rb +36 -6
  9. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +7 -5
  10. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  11. data/app/models/host_proxy_invocation.rb +4 -0
  12. data/app/models/host_status/execution_status.rb +5 -5
  13. data/app/models/job_invocation.rb +31 -12
  14. data/app/models/job_invocation_composer.rb +61 -19
  15. data/app/models/remote_execution_provider.rb +1 -1
  16. data/app/models/setting/remote_execution.rb +2 -2
  17. data/app/models/ssh_execution_provider.rb +4 -4
  18. data/app/models/targeting.rb +5 -1
  19. data/app/overrides/execution_interface.rb +8 -8
  20. data/app/overrides/subnet_proxies.rb +6 -6
  21. data/app/views/job_invocations/index.html.erb +1 -1
  22. data/app/views/templates/ssh/module_action.erb +1 -0
  23. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  24. data/config/routes.rb +1 -0
  25. data/db/migrate/20180110104432_rename_template_invocation_permission.rb +1 -1
  26. data/db/migrate/20190111153330_remove_remote_execution_without_proxy_setting.rb +4 -4
  27. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  28. data/extra/cockpit/foreman-cockpit-session +6 -6
  29. data/lib/foreman_remote_execution/engine.rb +11 -8
  30. data/lib/foreman_remote_execution/version.rb +1 -1
  31. data/package.json +2 -1
  32. data/test/functional/api/v2/job_invocations_controller_test.rb +14 -1
  33. data/test/unit/job_invocation_composer_test.rb +59 -2
  34. data/test/unit/job_invocation_test.rb +1 -1
  35. data/webpack/JobWizard/JobWizard.js +80 -19
  36. data/webpack/JobWizard/JobWizard.scss +42 -1
  37. data/webpack/JobWizard/JobWizardConstants.js +11 -0
  38. data/webpack/JobWizard/JobWizardSelectors.js +27 -1
  39. data/webpack/JobWizard/__tests__/__snapshots__/integration.test.js.snap +43 -0
  40. data/webpack/JobWizard/__tests__/fixtures.js +128 -0
  41. data/webpack/JobWizard/__tests__/integration.test.js +84 -0
  42. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +110 -0
  43. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +67 -0
  44. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +195 -0
  45. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +144 -0
  46. data/webpack/JobWizard/steps/AdvancedFields/__tests__/DescriptionField.test.js +23 -0
  47. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +34 -2
  48. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +122 -44
  49. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +9 -1
  50. data/webpack/JobWizard/steps/Schedule/QueryType.js +48 -0
  51. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +61 -0
  52. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +25 -0
  53. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +51 -0
  54. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +22 -0
  55. data/webpack/JobWizard/steps/Schedule/index.js +41 -0
  56. data/webpack/JobWizard/steps/form/FormHelpers.js +20 -0
  57. data/webpack/JobWizard/steps/form/Formatter.js +149 -0
  58. data/webpack/JobWizard/steps/form/GroupedSelectField.js +3 -0
  59. data/webpack/JobWizard/steps/form/NumberInput.js +33 -0
  60. data/webpack/JobWizard/steps/form/SelectField.js +24 -3
  61. data/webpack/JobWizard/steps/form/__tests__/Formatter.test.js.example +76 -0
  62. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  63. data/webpack/__mocks__/foremanReact/redux/API/APISelectors.js +21 -2
  64. data/webpack/global_index.js +5 -3
  65. data/webpack/index.js +3 -0
  66. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +1 -5
  67. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHostsSelectors.test.js +8 -3
  68. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  69. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsSelectors.test.js.snap +7 -2
  70. data/webpack/react_app/extend/{fills.js → fillRecentJobsCard.js} +7 -6
  71. data/webpack/react_app/extend/fillregistrationAdvanced.js +11 -0
  72. data/webpack/react_app/extend/reducers.js +2 -1
  73. metadata +24 -14
  74. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  75. data/test/models/orchestration/ssh_test.rb +0 -56
  76. data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -20
  77. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -83
  78. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -64
  79. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
  80. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
  81. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -36
  82. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -22
  83. data/webpack/fills_index.js +0 -11
@@ -1,70 +0,0 @@
1
- module ForemanRemoteExecution
2
- module Orchestration::SSH
3
- extend ActiveSupport::Concern
4
-
5
- included do
6
- before_destroy :ssh_destroy
7
- after_validation :queue_ssh_destroy
8
- register_rebuild(:queue_ssh_destroy, N_("SSH_#{self.to_s.split('::').first}"))
9
- end
10
-
11
- def drop_from_known_hosts(proxy_id)
12
- _, _, target = host_kind_target
13
- return true if target.nil?
14
-
15
- proxy = ::SmartProxy.find(proxy_id)
16
- begin
17
- proxy.drop_host_from_known_hosts(target)
18
- rescue ::ProxyAPI::ProxyException => e
19
- if e.wrapped_exception.is_a?(RestClient::NotFound)
20
- # ignore 404 when known_hosts entry is missing or the module was not enabled
21
- Foreman::Logging.exception "Proxy failed to delete SSH known_hosts for #{name}, #{ip}", e, :level => :error
22
- else
23
- raise e
24
- end
25
- rescue => e
26
- Rails.logger.warn e.message
27
- return false
28
- end
29
- true
30
- end
31
-
32
- def ssh_destroy
33
- logger.debug "Scheduling SSH known_hosts cleanup"
34
-
35
- host, _kind, _target = host_kind_target
36
- # #remote_execution_proxies may not be defined on the host object in some case
37
- # for example Host::Discovered does not have it defined, even though these hosts
38
- # have Nic::Managed interfaces associated with them
39
- proxies = (host.try(:remote_execution_proxies, 'SSH') || {}).values
40
- proxies.flatten.uniq.each do |proxy|
41
- queue.create(id: queue_id(proxy.id), name: _("Remove SSH known hosts for %s") % self,
42
- priority: 200, action: [self, :drop_from_known_hosts, proxy.id])
43
- end
44
- end
45
-
46
- def queue_ssh_destroy
47
- should_drop_from_known_hosts? && ssh_destroy
48
- end
49
-
50
- def should_drop_from_known_hosts?
51
- host, = host_kind_target
52
- host && !host.new_record? && host.build && host.changes.key?('build')
53
- end
54
-
55
- private
56
-
57
- def host_kind_target
58
- if self.is_a?(::Host::Base)
59
- [self, 'host', name]
60
- else
61
- [self.host, 'interface', ip]
62
- end
63
- end
64
-
65
- def queue_id(proxy_id)
66
- _, kind, id = host_kind_target
67
- "ssh_remove_known_hosts_#{kind}_#{id}_#{proxy_id}"
68
- end
69
- end
70
- end
@@ -1,56 +0,0 @@
1
- require 'test_plugin_helper'
2
-
3
- class SSHOrchestrationTest < ActiveSupport::TestCase
4
- let(:host) { FactoryBot.create(:host, :managed, :with_subnet) }
5
- let(:proxy) { FactoryBot.create(:smart_proxy, :ssh) }
6
- let(:interface) { host.interfaces.first }
7
-
8
- before { interface.subnet.remote_execution_proxies = [proxy] }
9
-
10
- it 'attempts to drop IP address and hostname from smart proxies on destroy' do
11
- host.stubs(:skip_orchestration?).returns false
12
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(interface.ip)
13
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
14
- host.destroy
15
- end
16
-
17
- it 'attempts to drop IP address and hostname from smart proxies on rebuild' do
18
- host.stubs(:skip_orchestration?).returns false
19
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(interface.ip)
20
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
21
-
22
- host.build = true
23
- host.save!
24
-
25
- ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
26
- "ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
27
- _(host.queue.task_ids).must_equal ids
28
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
29
- end
30
-
31
- it 'does not fail on 404 from the smart proxy' do
32
- host.stubs(:skip_orchestration?).returns false
33
- ::ProxyAPI::RemoteExecutionSSH.any_instance.expects(:delete).raises(RestClient::ResourceNotFound).twice
34
- host.build = true
35
- host.save!
36
- ids = ["ssh_remove_known_hosts_interface_#{interface.ip}_#{proxy.id}",
37
- "ssh_remove_known_hosts_host_#{host.name}_#{proxy.id}"]
38
- _(host.queue.task_ids).must_equal ids
39
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
40
- end
41
-
42
- it 'does not trigger the removal when creating a new host' do
43
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).never
44
- host = Host::Managed.new(:name => 'test', :ip => '127.0.0.1')
45
- host.stubs(:skip_orchestration?).returns false
46
- _(host.queue.task_ids).must_equal []
47
- end
48
-
49
- it 'does not call to the proxy when target is nil' do
50
- host.stubs(:skip_orchestration?).returns false
51
- SmartProxy.any_instance.expects(:drop_host_from_known_hosts).with(host.name)
52
- host.interfaces.first.stubs(:ip)
53
- host.destroy
54
- _(host.queue.items.map(&:status)).must_equal %w(completed completed)
55
- end
56
- end
@@ -1,20 +0,0 @@
1
- import React from 'react';
2
- import * as patternfly from '@patternfly/react-core';
3
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
4
- import JobWizardPage from '../index';
5
- import { JobWizard } from '../JobWizard';
6
-
7
- jest.spyOn(patternfly, 'Wizard');
8
- patternfly.Wizard.mockImplementation(props => <div>{props}</div>);
9
- const fixtures = {
10
- 'renders ': {},
11
- };
12
- describe('JobWizardPage', () => {
13
- describe('rendering', () =>
14
- testComponentSnapshotsWithFixtures(JobWizardPage, fixtures));
15
- });
16
-
17
- describe('JobWizard', () => {
18
- describe('rendering', () =>
19
- testComponentSnapshotsWithFixtures(JobWizard, fixtures));
20
- });
@@ -1,83 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`JobWizard rendering renders 1`] = `
4
- <mockConstructor
5
- className="job-wizard"
6
- height="70vh"
7
- navAriaLabel="Run Job steps"
8
- onClose={[Function]}
9
- steps={
10
- Array [
11
- Object {
12
- "component": <ConnectedCategoryAndTemplate
13
- category=""
14
- jobTemplate={null}
15
- setCategory={[Function]}
16
- setJobTemplate={[Function]}
17
- />,
18
- "name": "Category and template",
19
- },
20
- Object {
21
- "canJumpTo": false,
22
- "component": <p>
23
- TargetHosts
24
- </p>,
25
- "name": "Target hosts",
26
- },
27
- Object {
28
- "canJumpTo": false,
29
- "component": <p>
30
- AdvancedFields
31
- </p>,
32
- "name": "Advanced fields",
33
- },
34
- Object {
35
- "canJumpTo": false,
36
- "component": <p>
37
- Schedule
38
- </p>,
39
- "name": "Schedule",
40
- },
41
- Object {
42
- "canJumpTo": false,
43
- "component": <p>
44
- ReviewDetails
45
- </p>,
46
- "name": "Review details",
47
- "nextButtonText": "Run",
48
- },
49
- ]
50
- }
51
- />
52
- `;
53
-
54
- exports[`JobWizardPage rendering renders 1`] = `
55
- <PageLayout
56
- breadcrumbOptions={
57
- Object {
58
- "breadcrumbItems": Array [
59
- Object {
60
- "caption": "Jobs",
61
- "url": "/jobs",
62
- },
63
- Object {
64
- "caption": "Run job",
65
- },
66
- ],
67
- }
68
- }
69
- header="Run job"
70
- searchable={false}
71
- >
72
- <Title
73
- headingLevel="h2"
74
- size="2xl"
75
- >
76
- Run job
77
- </Title>
78
- <Divider
79
- component="div"
80
- />
81
- <JobWizard />
82
- </PageLayout>
83
- `;
@@ -1,64 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`CategoryAndTemplate rendering renders with props 1`] = `
4
- <Fragment>
5
- <Title
6
- headingLevel="h2"
7
- >
8
- Category And Template
9
- </Title>
10
- <Text
11
- component="p"
12
- >
13
- All fields are required.
14
- </Text>
15
- <Form>
16
- <SelectField
17
- fieldId="job_category"
18
- label="Job category"
19
- options={
20
- Array [
21
- "Commands",
22
- "Ansible Playbook",
23
- "Ansible Galaxy",
24
- "Ansible Roles Installation",
25
- ]
26
- }
27
- setValue={[Function]}
28
- value="I am a category"
29
- />
30
- <GroupedSelectField
31
- fieldId="job_template"
32
- groups={
33
- Array [
34
- Object {
35
- "groupLabel": "SSH",
36
- "options": Array [
37
- Object {
38
- "label": "ab Run Command - SSH Default clone",
39
- "value": 190,
40
- },
41
- ],
42
- },
43
- Object {
44
- "groupLabel": "Ansible",
45
- "options": Array [
46
- Object {
47
- "label": "Ansible Roles - Ansible Default",
48
- "value": 168,
49
- },
50
- Object {
51
- "label": "Ansible Roles - Install from git",
52
- "value": 170,
53
- },
54
- ],
55
- },
56
- ]
57
- }
58
- label="Job template"
59
- selected="ab Run Command - SSH Default clone"
60
- setSelected={[MockFunction]}
61
- />
62
- </Form>
63
- </Fragment>
64
- `;
@@ -1,38 +0,0 @@
1
- import React from 'react';
2
- import * as patternfly from '@patternfly/react-core';
3
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
4
- import { GroupedSelectField } from '../GroupedSelectField';
5
-
6
- jest.spyOn(patternfly, 'Select');
7
- jest.spyOn(patternfly, 'SelectOption');
8
- patternfly.Select.mockImplementation(props => <div>{props}</div>);
9
- patternfly.SelectOption.mockImplementation(props => <div>{props}</div>);
10
-
11
- const fixtures = {
12
- 'renders with props': {
13
- label: 'grouped select',
14
- fieldId: 'field-id',
15
- groups: [
16
- {
17
- groupLabel: 'Ansible',
18
- options: [
19
- {
20
- label: 'Ansible Roles - Ansible Default',
21
- value: 168,
22
- },
23
- {
24
- label: 'Ansible Roles - Install from git',
25
- value: 170,
26
- },
27
- ],
28
- },
29
- ],
30
- selected: 170,
31
- setSelected: jest.fn(),
32
- },
33
- };
34
-
35
- describe('GroupedSelectField', () => {
36
- describe('rendering', () =>
37
- testComponentSnapshotsWithFixtures(GroupedSelectField, fixtures));
38
- });
@@ -1,23 +0,0 @@
1
- import React from 'react';
2
- import * as patternfly from '@patternfly/react-core';
3
- import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
4
- import { SelectField } from '../SelectField';
5
-
6
- jest.spyOn(patternfly, 'Select');
7
- jest.spyOn(patternfly, 'SelectOption');
8
- patternfly.Select.mockImplementation(props => <div>{props}</div>);
9
- patternfly.SelectOption.mockImplementation(props => <div>{props}</div>);
10
- const fixtures = {
11
- 'renders with props': {
12
- label: 'grouped select',
13
- fieldId: 'field-id',
14
- options: ['Commands'],
15
- value: 'Commands',
16
- setValue: jest.fn(),
17
- },
18
- };
19
-
20
- describe('SelectField', () => {
21
- describe('rendering', () =>
22
- testComponentSnapshotsWithFixtures(SelectField, fixtures));
23
- });
@@ -1,36 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`GroupedSelectField rendering renders with props 1`] = `
4
- <FormGroup
5
- fieldId="field-id"
6
- label="grouped select"
7
- >
8
- <mockConstructor
9
- className="without_select2"
10
- isGrouped={true}
11
- isOpen={false}
12
- onClear={[Function]}
13
- onFilter={[Function]}
14
- onSelect={[Function]}
15
- onToggle={[Function]}
16
- selections={170}
17
- variant="typeahead"
18
- >
19
- <SelectGroup
20
- key="0"
21
- label="Ansible"
22
- >
23
- <mockConstructor
24
- key="0"
25
- onClick={[Function]}
26
- value="Ansible Roles - Ansible Default"
27
- />
28
- <mockConstructor
29
- key="1"
30
- onClick={[Function]}
31
- value="Ansible Roles - Install from git"
32
- />
33
- </SelectGroup>
34
- </mockConstructor>
35
- </FormGroup>
36
- `;
@@ -1,22 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`SelectField rendering renders with props 1`] = `
4
- <FormGroup
5
- fieldId="field-id"
6
- label="grouped select"
7
- >
8
- <mockConstructor
9
- className="without_select2"
10
- isOpen={false}
11
- maxHeight="45vh"
12
- onSelect={[Function]}
13
- onToggle={[Function]}
14
- selections="Commands"
15
- >
16
- <mockConstructor
17
- key="0"
18
- value="Commands"
19
- />
20
- </mockConstructor>
21
- </FormGroup>
22
- `;
@@ -1,11 +0,0 @@
1
- import React from 'react';
2
- import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
-
4
- import RexInterface from './react_app/components/RegistrationExtension/RexInterface';
5
-
6
- addGlobalFill(
7
- 'registrationAdvanced',
8
- 'foreman-remote-exectuion-rex-interface',
9
- <RexInterface key="registration-rex-interface" />,
10
- 100
11
- );