foreman_remote_execution 4.4.0 → 4.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
- );