foreman_remote_execution 7.1.0 → 7.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b7c8e05850731a1f10e902deaa2e6797b869bb3363607af339e6a683225fa78a
4
- data.tar.gz: b5a2001957310aeb3a7353efa65592bae1dbb3fcabb68677516220674cea94b2
3
+ metadata.gz: 893050d86f8ab746da3173f25cc9a062454bf9262316f0a6fbb5f89a6282c15f
4
+ data.tar.gz: 02e623b7202ec926f0d744cbfadbbe5f86a7950cd3665c91a3b7ec74a2b02cd2
5
5
  SHA512:
6
- metadata.gz: 03daea7eb15788a97f841d80fddeb3c40c89e79bba550ca7ef9a2eab5ad6646ec13de8d134490a5fb5171d8aeb937e792a88f62588a468613a71948cb0212ba3
7
- data.tar.gz: e1ed8ff83ae15794ca4bcca7bdeb7cd7fef930f6c41394538e14fafa5fcb5f738535a3e1096bd6ee1856f3b95a4299ebdb6cf50aafa94ea233724599cea67395
6
+ metadata.gz: c488b0d6edc98ce77e66edefc2c0f298c323bfd0aa6f0d46a3dbbc4b4642bbe9ac8f2112c237460095f47c5494d5ad7112482475fc886d596e9698fe0cac128f
7
+ data.tar.gz: eb73a3df7acbfa047699d349fcb03393850b5ddded2ed31b3cd30e38b15d3a516e608a17e33f29237271e012c9de69bb7929801de4f8605d8a4b3b59590b73e8
@@ -9,6 +9,7 @@ module ForemanRemoteExecution
9
9
  update_api(:create) do
10
10
  param :registration_command, Hash do
11
11
  param :remote_execution_interface, String, desc: N_("Identifier of the Host interface for Remote execution")
12
+ param :setup_remote_execution_pull, :bool, desc: N_("Set 'host_registration_remote_execution_pull' parameter for the host. If it is set to true, pull provider client will be deployed on the host")
12
13
  end
13
14
  end
14
15
  end
@@ -6,6 +6,7 @@ module ForemanRemoteExecution
6
6
 
7
7
  update_api(:global, :host) do
8
8
  param :remote_execution_interface, String, desc: N_("Identifier of the Host interface for Remote execution")
9
+ param :setup_remote_execution_pull, :bool, desc: N_("Set 'host_registration_remote_execution_pull' parameter for the host. If it is set to true, pull provider client will be deployed on the host")
9
10
  end
10
11
  end
11
12
 
@@ -13,10 +14,17 @@ module ForemanRemoteExecution
13
14
 
14
15
  def host_setup_extension
15
16
  remote_execution_interface
17
+ remote_execution_pull
16
18
  reset_host_known_keys! unless @host.new_record?
17
19
  super
18
20
  end
19
21
 
22
+ def remote_execution_pull
23
+ HostParameter.where(host: @host, name: 'host_registration_remote_execution_pull').destroy_all
24
+
25
+ setup_host_param('host_registration_remote_execution_pull', ActiveRecord::Type::Boolean.new.deserialize(params['setup_remote_execution_pull']))
26
+ end
27
+
20
28
  def remote_execution_interface
21
29
  return unless params['remote_execution_interface'].present?
22
30
 
@@ -50,7 +50,8 @@ class HostStatus::ExecutionStatus < HostStatus::Status
50
50
  end
51
51
 
52
52
  def status_link
53
- job_invocation = last_stopped_task.parent_task.job_invocations.first
53
+ job_invocation = last_stopped_task&.parent_task&.job_invocations&.first
54
+ return unless job_invocation
54
55
  return nil unless User.current.can?(:view_job_invocations, job_invocation)
55
56
 
56
57
  Rails.application.routes.url_helpers.job_invocation_path(job_invocation)
@@ -25,7 +25,7 @@ class JobInvocation < ApplicationRecord
25
25
  validates :job_category, :presence => true
26
26
  validates_associated :targeting, :all_template_invocations
27
27
 
28
- scoped_search :on => :id, :complete_value => true
28
+ scoped_search :on => :id, :complete_value => true, :validator => ScopedSearch::Validators::INTEGER
29
29
  scoped_search :on => :job_category, :complete_value => true
30
30
  scoped_search :on => :description, :complete_value => true
31
31
 
@@ -62,14 +62,16 @@ class JobInvocation < ApplicationRecord
62
62
 
63
63
  has_many :targeted_hosts, :through => :targeting, :source => :hosts
64
64
  scoped_search :on => 'targeted_host_id', :rename => 'targeted_host_id', :operators => ['= '],
65
- :complete_value => false, :only_explicit => true, :ext_method => :search_by_targeted_host
65
+ :complete_value => false, :only_explicit => true, :ext_method => :search_by_targeted_host,
66
+ :validator => ScopedSearch::Validators::INTEGER
66
67
 
67
68
  scoped_search :on => 'pattern_template_name', :rename => 'pattern_template_name', :operators => ['= '],
68
69
  :complete_value => false, :only_explicit => true, :ext_method => :search_by_pattern_template
69
70
 
70
71
  scoped_search :relation => :recurring_logic, :on => 'purpose', :rename => 'recurring_logic.purpose'
71
72
 
72
- scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id'
73
+ scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring_logic.id',
74
+ :validator => ScopedSearch::Validators::INTEGER
73
75
 
74
76
  scoped_search :relation => :recurring_logic, :on => 'id', :rename => 'recurring',
75
77
  :ext_method => :search_by_recurring_logic, :only_explicit => true,
@@ -219,7 +219,7 @@ class JobInvocationComposer
219
219
  def format_datetime(datetime)
220
220
  return datetime if datetime.blank?
221
221
 
222
- Time.parse(datetime).in_time_zone.strftime('%Y-%m-%d %H:%M')
222
+ Time.zone.parse(datetime).strftime('%Y-%m-%d %H:%M')
223
223
  end
224
224
  end
225
225
 
@@ -28,6 +28,10 @@ class RemoteExecutionProvider
28
28
  providers.keys.map(&:to_s)
29
29
  end
30
30
 
31
+ def provider_proxy_features
32
+ providers.values.map(&:proxy_feature).flatten.uniq.compact
33
+ end
34
+
31
35
  def proxy_command_options(template_invocation, host)
32
36
  {:proxy_operation_name => proxy_operation_name}.merge(proxy_command_provider_inputs(template_invocation))
33
37
  end
@@ -7,7 +7,7 @@ class RemoteExecutionProxySelector < ::ForemanTasks::ProxySelector
7
7
  return proxies if capability.nil?
8
8
 
9
9
  proxies.reduce({}) do |acc, (strategy, possible_proxies)|
10
- acc.merge(strategy => possible_proxies.select { |proxy| proxy.has_capability?(capability) })
10
+ acc.merge(strategy => possible_proxies.select { |proxy| proxy.has_capability?(provider, capability) })
11
11
  end
12
12
  end
13
13
  end
@@ -1,5 +1,5 @@
1
1
  <div class="tab-pane" id="rex_proxies">
2
2
  <%= fields_for :subnet do |f| %>
3
- <%= multiple_selects f, :remote_execution_proxies, SmartProxy.authorized.with_features(*RemoteExecutionProvider.provider_names).distinct, @subnet.remote_execution_proxy_ids, {:label => _("Proxies"), :help_inline => _("Select as many remote execution proxies as applicable for this subnet. When multiple proxies with the same provider are added, actions will be load balanced among them.")} %>
3
+ <%= multiple_selects f, :remote_execution_proxies, SmartProxy.authorized.with_features(*RemoteExecutionProvider.provider_proxy_features).distinct, @subnet.remote_execution_proxy_ids, {:label => _("Proxies"), :help_inline => _("Select as many remote execution proxies as applicable for this subnet. When multiple proxies with the same provider are added, actions will be load balanced among them.")} %>
4
4
  <% end %>
5
5
  </div>
@@ -275,6 +275,7 @@ module ForemanRemoteExecution
275
275
 
276
276
  # Extend Registration module
277
277
  extend_allowed_registration_vars :remote_execution_interface
278
+ extend_allowed_registration_vars :setup_remote_execution_pull
278
279
  ForemanTasks.dynflow.eager_load_actions!
279
280
  extend_observable_events(
280
281
  ::Dynflow::Action.descendants.select do |klass|
@@ -1,3 +1,3 @@
1
1
  module ForemanRemoteExecution
2
- VERSION = '7.1.0'.freeze
2
+ VERSION = '7.2.1'.freeze
3
3
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'test_plugin_helper'
4
+
5
+ class ApiParamsTest < ActiveSupport::TestCase
6
+ describe '#format_datetime' do
7
+ let(:params) { JobInvocationComposer::ApiParams.allocate }
8
+
9
+ it 'leaves empty string as is' do
10
+ assert_equal params.send(:format_datetime, ''), ''
11
+ end
12
+
13
+ it 'honors explicitly supplied time zone' do
14
+ in_time_zone(ActiveSupport::TimeZone['America/New_York']) do
15
+ assert_equal '2022-07-08 08:53', params.send(:format_datetime, '2022-07-08 12:53:20 UTC')
16
+ end
17
+ end
18
+
19
+ it 'implicitly honors current user\'s time zone' do
20
+ in_time_zone(ActiveSupport::TimeZone['America/New_York']) do
21
+ assert_equal '2022-07-08 12:53', params.send(:format_datetime, '2022-07-08 12:53:20')
22
+ end
23
+ end
24
+ end
25
+
26
+ def in_time_zone(zone)
27
+ old_tz = Time.zone
28
+ Time.zone = zone
29
+ yield
30
+ ensure
31
+ Time.zone = old_tz
32
+ end
33
+ end
@@ -33,6 +33,10 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
33
33
  it 'accepts strings' do
34
34
  RemoteExecutionProvider.provider_for('SSH').must_equal SSHExecutionProvider
35
35
  end
36
+
37
+ it 'returns a default one if unknown value is provided' do
38
+ RemoteExecutionProvider.provider_for('WinRM').must_equal ScriptExecutionProvider
39
+ end
36
40
  end
37
41
 
38
42
  describe '.provider_names' do
@@ -52,6 +56,28 @@ class RemoteExecutionProviderTest < ActiveSupport::TestCase
52
56
  end
53
57
  end
54
58
 
59
+ describe '.provider_proxy_features' do
60
+ it 'returns correct values' do
61
+ RemoteExecutionProvider.stubs(:providers).returns(
62
+ :SSH => SSHExecutionProvider,
63
+ :script => ScriptExecutionProvider
64
+ )
65
+
66
+ features = RemoteExecutionProvider.provider_proxy_features
67
+ _(features).must_include 'SSH'
68
+ _(features).must_include 'Script'
69
+ RemoteExecutionProvider.unstub(:providers)
70
+ end
71
+
72
+ it 'can deal with non-arrays' do
73
+ provider = OpenStruct.new(proxy_feature: 'Testing')
74
+ RemoteExecutionProvider.stubs(:providers).returns(:testing => provider)
75
+ features = RemoteExecutionProvider.provider_proxy_features
76
+ _(features).must_include 'Testing'
77
+ RemoteExecutionProvider.unstub(:providers)
78
+ end
79
+ end
80
+
55
81
  describe '.host_setting' do
56
82
  let(:host) { FactoryBot.create(:host) }
57
83
 
@@ -14,6 +14,7 @@ const HostKebabItems = () => {
14
14
  if (!consoleUrl) return null;
15
15
  return (
16
16
  <DropdownItem
17
+ ouiaId="web-console-dropdown-item"
17
18
  icon={<CodeIcon />}
18
19
  href={consoleUrl}
19
20
  target="_blank"
@@ -27,6 +27,7 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
27
27
  <DropdownItem
28
28
  href={foremanUrl(`${JOB_BASE_URL}${name}`)}
29
29
  key="link-to-all"
30
+ ouiaId="link-to-all-dropdown-item"
30
31
  >
31
32
  {__('View all jobs')}
32
33
  </DropdownItem>,
@@ -35,24 +36,28 @@ const RecentJobsCard = ({ hostDetails: { name, id } }) => {
35
36
  `${JOB_BASE_URL}${name}+and+status+%3D+failed+or+status%3D+succeeded`
36
37
  )}
37
38
  key="link-to-finished"
39
+ ouiaId="link-to-finished-dropdown-item"
38
40
  >
39
41
  {__('View finished jobs')}
40
42
  </DropdownItem>,
41
43
  <DropdownItem
42
44
  href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+running`)}
43
45
  key="link-to-running"
46
+ ouiaId="link-to-running-dropdown-item"
44
47
  >
45
48
  {__('View running jobs')}
46
49
  </DropdownItem>,
47
50
  <DropdownItem
48
51
  href={foremanUrl(`${JOB_BASE_URL}${name}+and+status+%3D+queued`)}
49
52
  key="link-to-scheduled"
53
+ ouiaId="link-to-scheduled-dropdown-item"
50
54
  >
51
55
  {__('View scheduled jobs')}
52
56
  </DropdownItem>,
53
57
  ]}
54
58
  >
55
59
  <Tabs
60
+ ouiaId="tabs"
56
61
  mountOnEnter
57
62
  unmountOnExit
58
63
  activeKey={activeTab}
@@ -1,15 +1,7 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
- import {
4
- DataList,
5
- DataListItem,
6
- DataListItemRow,
7
- DataListItemCells,
8
- DataListCell,
9
- DataListWrapModifier,
10
- Text,
11
- Bullseye,
12
- } from '@patternfly/react-core';
3
+ import { Text, Bullseye } from '@patternfly/react-core';
4
+ import { TableComposable, Tr, Tbody, Td } from '@patternfly/react-table';
13
5
  import { STATUS } from 'foremanReact/constants';
14
6
 
15
7
  import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
@@ -33,55 +25,55 @@ const RecentJobsTable = ({ status, hostId }) => {
33
25
  } = useAPI('get', jobsUrl, RECENT_JOBS_KEY);
34
26
 
35
27
  return (
36
- <DataList aria-label="recent-jobs-table" isCompact>
37
- <SkeletonLoader
38
- skeletonProps={{ count: 3 }}
39
- status={responseStatus || STATUS.PENDING}
40
- emptyState={
41
- <Bullseye>
42
- <Text style={{ marginTop: '20px' }} component="p">
43
- {__('No results found')}
44
- </Text>
45
- </Bullseye>
46
- }
47
- >
48
- {jobs?.length &&
49
- jobs.map(
50
- ({
51
- status: jobStatus,
52
- status_label: label,
53
- id,
54
- start_at: startAt,
55
- description,
56
- }) => (
57
- <DataListItem key={id}>
58
- <DataListItemRow>
59
- <DataListItemCells
60
- dataListCells={[
61
- <DataListCell
62
- wrapModifier={DataListWrapModifier.truncate}
63
- key={`name-${id}`}
64
- >
65
- <a href={foremanUrl(`/job_invocations/${id}`)}>
66
- {description}
67
- </a>
68
- </DataListCell>,
69
- <DataListCell key={`date-${id}`}>
70
- <RelativeDateTime date={startAt} />
71
- </DataListCell>,
72
- <DataListCell key={`status-${id}`}>
73
- <JobStatusIcon status={jobStatus}>
74
- {label}
75
- </JobStatusIcon>
76
- </DataListCell>,
77
- ]}
78
- />
79
- </DataListItemRow>
80
- </DataListItem>
81
- )
82
- )}
83
- </SkeletonLoader>
84
- </DataList>
28
+ <SkeletonLoader
29
+ skeletonProps={{ count: 3 }}
30
+ status={responseStatus || STATUS.PENDING}
31
+ emptyState={
32
+ <Bullseye>
33
+ <Text
34
+ ouiaId="no-results-text"
35
+ style={{ marginTop: '20px' }}
36
+ component="p"
37
+ >
38
+ {__('No results found')}
39
+ </Text>
40
+ </Bullseye>
41
+ }
42
+ >
43
+ {!!jobs?.length && (
44
+ <TableComposable
45
+ aria-label="recent-jobs-table"
46
+ variant="compact"
47
+ borders="compactBorderless"
48
+ >
49
+ <Tbody>
50
+ {jobs.map(
51
+ ({
52
+ status: jobStatus,
53
+ status_label: label,
54
+ id,
55
+ start_at: startAt,
56
+ description,
57
+ }) => (
58
+ <Tr key={id}>
59
+ <Td modifier="truncate" key={`name-${id}`}>
60
+ <a href={foremanUrl(`/job_invocations/${id}`)}>
61
+ {description}
62
+ </a>
63
+ </Td>
64
+ <Td modifier="truncate" key={`date-${id}`}>
65
+ <RelativeDateTime date={startAt} />
66
+ </Td>
67
+ <Td modifier="truncate" key={`status-${id}`}>
68
+ <JobStatusIcon status={jobStatus}>{label}</JobStatusIcon>
69
+ </Td>
70
+ </Tr>
71
+ )
72
+ )}
73
+ </Tbody>
74
+ </TableComposable>
75
+ )}
76
+ </SkeletonLoader>
85
77
  );
86
78
  };
87
79
 
@@ -0,0 +1,73 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import LabelIcon from 'foremanReact/components/common/LabelIcon';
6
+
7
+ import {
8
+ FormGroup,
9
+ FormSelectOption,
10
+ FormSelect,
11
+ } from '@patternfly/react-core';
12
+
13
+ const options = (value = '') => {
14
+ const defaultValue = value ? __('yes') : __('no');
15
+ const defaultLabel = `${__('Inherit from host parameter')} (${defaultValue})`;
16
+
17
+ return (
18
+ <>
19
+ <FormSelectOption key={0} value="" label={defaultLabel} />
20
+ <FormSelectOption key={1} value label={__('Yes (override)')} />
21
+ <FormSelectOption key={2} value={false} label={__('No (override)')} />
22
+ </>
23
+ );
24
+ };
25
+
26
+ const RexPull = ({ isLoading, onChange, pluginValues, configParams }) => (
27
+ <FormGroup
28
+ label={__('REX pull mode')}
29
+ isRequired
30
+ labelIcon={
31
+ <LabelIcon
32
+ text={__(
33
+ 'Setup remote execution pull mode. If set to `Yes`, pull provider client will be deployed on the registered host. The inherited value is based on the `host_registration_remote_execution_pull` parameter. It can be inherited e.g. from host group, operating system, organization. When overridden, the selected value will be stored on host parameter level.'
34
+ )}
35
+ />
36
+ }
37
+ fieldId="registration_setup_remote_execution_pull"
38
+ >
39
+ <FormSelect
40
+ value={pluginValues.setupRemoteExecutionPull}
41
+ onChange={setupRemoteExecutionPull =>
42
+ onChange({ setupRemoteExecutionPull })
43
+ }
44
+ className="without_select2"
45
+ id="registration_setup_remote_execution_pull"
46
+ isDisabled={isLoading}
47
+ isRequired
48
+ >
49
+ {/* eslint-disable-next-line camelcase */
50
+ options(configParams?.host_registration_remote_execution_pull)}
51
+ </FormSelect>
52
+ </FormGroup>
53
+ );
54
+
55
+ RexPull.propTypes = {
56
+ onChange: PropTypes.func,
57
+ isLoading: PropTypes.bool,
58
+ pluginValues: PropTypes.shape({
59
+ setupRemoteExecutionPull: PropTypes.bool,
60
+ }),
61
+ configParams: PropTypes.shape({
62
+ host_registration_remote_execution_pull: PropTypes.bool,
63
+ }),
64
+ };
65
+
66
+ RexPull.defaultProps = {
67
+ onChange: undefined,
68
+ isLoading: false,
69
+ pluginValues: {},
70
+ configParams: {},
71
+ };
72
+
73
+ export default RexPull;
@@ -3,6 +3,7 @@ import { addGlobalFill } from 'foremanReact/components/common/Fill/GlobalFill';
3
3
 
4
4
  import FeaturesDropdown from '../components/FeaturesDropdown';
5
5
  import RexInterface from '../components/RegistrationExtension/RexInterface';
6
+ import RexPull from '../components/RegistrationExtension/RexPull';
6
7
  import RecentJobsCard from '../components/RecentJobsCard';
7
8
  import KebabItems from '../components/HostKebab/KebabItems';
8
9
 
@@ -25,6 +26,12 @@ const fills = [
25
26
  component: props => <RexInterface {...props} />,
26
27
  weight: 500,
27
28
  },
29
+ {
30
+ slot: 'registrationAdvanced',
31
+ name: 'pull',
32
+ component: props => <RexPull {...props} />,
33
+ weight: 500,
34
+ },
28
35
  {
29
36
  slot: '_rex-host-features',
30
37
  name: '_rex-host-features',
@@ -34,11 +41,11 @@ const fills = [
34
41
  ];
35
42
 
36
43
  const registerFills = () => {
37
- fills.forEach(({ slot, id, component: Component, weight, metadata }) =>
44
+ fills.forEach(({ slot, name, component: Component, weight, metadata }) =>
38
45
  addGlobalFill(
39
46
  slot,
40
- id,
41
- <Component key={`rex-fill-${id}`} />,
47
+ `rex-${name}`,
48
+ <Component key={`rex-${name}`} />,
42
49
  weight,
43
50
  metadata
44
51
  )
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: foreman_remote_execution
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.1.0
4
+ version: 7.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Remote Execution team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-13 00:00:00.000000000 Z
11
+ date: 2022-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: deface
@@ -389,6 +389,7 @@ files:
389
389
  - test/test_plugin_helper.rb
390
390
  - test/unit/actions/run_host_job_test.rb
391
391
  - test/unit/actions/run_hosts_job_test.rb
392
+ - test/unit/api_params_test.rb
392
393
  - test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb
393
394
  - test/unit/concerns/host_extensions_test.rb
394
395
  - test/unit/concerns/nic_extensions_test.rb
@@ -491,6 +492,7 @@ files:
491
492
  - webpack/react_app/components/RecentJobsCard/index.js
492
493
  - webpack/react_app/components/RecentJobsCard/styles.scss
493
494
  - webpack/react_app/components/RegistrationExtension/RexInterface.js
495
+ - webpack/react_app/components/RegistrationExtension/RexPull.js
494
496
  - webpack/react_app/components/RegistrationExtension/__tests__/RexInterface.test.js
495
497
  - webpack/react_app/components/RegistrationExtension/__tests__/__snapshots__/RexInterface.test.js.snap
496
498
  - webpack/react_app/components/TargetingHosts/TargetingHosts.js
@@ -544,7 +546,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
544
546
  - !ruby/object:Gem::Version
545
547
  version: '0'
546
548
  requirements: []
547
- rubygems_version: 3.1.4
549
+ rubygems_version: 3.3.20
548
550
  signing_key:
549
551
  specification_version: 4
550
552
  summary: A plugin bringing remote execution to the Foreman, completing the config
@@ -571,6 +573,7 @@ test_files:
571
573
  - test/test_plugin_helper.rb
572
574
  - test/unit/actions/run_host_job_test.rb
573
575
  - test/unit/actions/run_hosts_job_test.rb
576
+ - test/unit/api_params_test.rb
574
577
  - test/unit/concerns/foreman_tasks_cleaner_extensions_test.rb
575
578
  - test/unit/concerns/host_extensions_test.rb
576
579
  - test/unit/concerns/nic_extensions_test.rb