foreman_remote_execution 3.1.0 → 3.3.2

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 (94) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintrc +5 -30
  3. data/.github/workflows/ci.yml +100 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop_todo.yml +3 -0
  6. data/Gemfile +1 -0
  7. data/app/controllers/api/v2/job_invocations_controller.rb +22 -1
  8. data/app/controllers/api/v2/template_invocations_controller.rb +4 -1
  9. data/app/helpers/job_invocations_chart_helper.rb +2 -0
  10. data/app/helpers/job_invocations_helper.rb +6 -1
  11. data/app/helpers/remote_execution_helper.rb +39 -30
  12. data/app/lib/actions/remote_execution/run_host_job.rb +3 -2
  13. data/app/lib/proxy_api/remote_execution_ssh.rb +6 -0
  14. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +1 -1
  15. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +63 -0
  16. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +4 -0
  17. data/app/models/host_status/execution_status.rb +9 -1
  18. data/app/models/remote_execution_provider.rb +5 -0
  19. data/app/services/default_proxy_proxy_selector.rb +18 -0
  20. data/app/views/api/v2/job_invocations/main.json.rabl +8 -2
  21. data/app/views/job_invocations/_card_results.html.erb +1 -0
  22. data/app/views/job_invocations/_card_user_input.html.erb +1 -1
  23. data/app/views/job_invocations/_tab_hosts.html.erb +3 -23
  24. data/app/views/job_invocations/_tab_overview.html.erb +1 -1
  25. data/app/views/job_invocations/_user_input.html.erb +1 -1
  26. data/app/views/job_invocations/show.html.erb +1 -7
  27. data/app/views/job_invocations/show.json.erb +4 -0
  28. data/config/routes.rb +2 -1
  29. data/db/seeds.d/70-job_templates.rb +1 -1
  30. data/foreman_remote_execution.gemspec +5 -6
  31. data/lib/foreman_remote_execution/engine.rb +2 -0
  32. data/lib/foreman_remote_execution/version.rb +1 -1
  33. data/locale/action_names.rb +3 -3
  34. data/locale/de/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  35. data/locale/de/foreman_remote_execution.po +65 -16
  36. data/locale/en/foreman_remote_execution.po +63 -15
  37. data/locale/en_GB/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  38. data/locale/en_GB/foreman_remote_execution.po +64 -15
  39. data/locale/es/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  40. data/locale/es/foreman_remote_execution.po +66 -17
  41. data/locale/foreman_remote_execution.pot +193 -148
  42. data/locale/fr/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  43. data/locale/fr/foreman_remote_execution.po +66 -17
  44. data/locale/ja/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  45. data/locale/ja/foreman_remote_execution.po +66 -17
  46. data/locale/ko/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  47. data/locale/ko/foreman_remote_execution.po +65 -15
  48. data/locale/pt_BR/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  49. data/locale/pt_BR/foreman_remote_execution.po +66 -17
  50. data/locale/ru/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  51. data/locale/ru/foreman_remote_execution.po +65 -18
  52. data/locale/zh_CN/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  53. data/locale/zh_CN/foreman_remote_execution.po +66 -17
  54. data/locale/zh_TW/LC_MESSAGES/foreman_remote_execution.mo +0 -0
  55. data/locale/zh_TW/foreman_remote_execution.po +65 -16
  56. data/package.json +16 -33
  57. data/test/functional/api/v2/job_invocations_controller_test.rb +42 -14
  58. data/test/functional/job_invocations_controller_test.rb +12 -0
  59. data/test/models/orchestration/ssh_test.rb +56 -0
  60. data/test/unit/concerns/host_extensions_test.rb +7 -0
  61. data/test/unit/remote_execution_provider_test.rb +4 -1
  62. data/webpack/__mocks__/foremanReact/common/I18n.js +1 -0
  63. data/webpack/__mocks__/foremanReact/components/common/ActionButtons/ActionButtons.js +3 -0
  64. data/webpack/__mocks__/foremanReact/constants.js +3 -0
  65. data/webpack/index.js +9 -22
  66. data/webpack/react_app/components/TargetingHosts/TargetingHosts.js +52 -0
  67. data/webpack/react_app/components/TargetingHosts/TargetingHostsActions.js +8 -0
  68. data/webpack/react_app/components/TargetingHosts/TargetingHostsConsts.js +1 -0
  69. data/webpack/react_app/components/TargetingHosts/TargetingHostsSelectors.js +12 -0
  70. data/webpack/react_app/components/TargetingHosts/__tests__/HostItem.test.js +6 -0
  71. data/webpack/react_app/components/TargetingHosts/__tests__/HostStatus.test.js +6 -0
  72. data/webpack/react_app/components/TargetingHosts/__tests__/TargetingHosts.test.js +6 -0
  73. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostItem.test.js.snap +31 -0
  74. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/HostStatus.test.js.snap +12 -0
  75. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHosts.test.js.snap +81 -0
  76. data/webpack/react_app/components/TargetingHosts/__tests__/fixtures.js +43 -0
  77. data/webpack/react_app/components/TargetingHosts/components/HostItem.js +39 -0
  78. data/webpack/react_app/components/TargetingHosts/components/HostStatus.js +54 -0
  79. data/webpack/react_app/components/TargetingHosts/index.js +37 -0
  80. data/webpack/react_app/components/jobInvocations/AggregateStatus/index.js +10 -0
  81. data/webpack/react_app/components/jobInvocations/AggregateStatus/index.test.js +6 -3
  82. data/webpack/react_app/components/jobInvocations/index.js +19 -7
  83. data/webpack/react_app/redux/actions/jobInvocations/index.js +12 -8
  84. data/webpack/react_app/redux/consts.js +1 -2
  85. data/webpack/react_app/redux/reducers/jobInvocations/index.fixtures.js +8 -40
  86. data/webpack/react_app/redux/reducers/jobInvocations/index.test.js +17 -11
  87. data/webpack/test_setup.js +2 -1
  88. metadata +31 -14
  89. data/.hound.yml +0 -19
  90. data/.travis.yml +0 -6
  91. data/app/views/job_invocations/_host_actions_td.html.erb +0 -3
  92. data/app/views/job_invocations/_host_name_td.html.erb +0 -8
  93. data/app/views/job_invocations/_host_status_td.html.erb +0 -1
  94. data/app/views/job_invocations/show.js.erb +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 36a15d0142caa911a59ff7d9797ceab5dabb0e601f605c0fee3d547d1be168f5
4
- data.tar.gz: d41aba8d661cae6ef74cfee2bd9e007a37f218c3056e9d9b8ae3a1de5f243bc6
3
+ metadata.gz: d093d837d0009e477549fba4700f3e0090045ba23f8b2c50fb05439fee53e403
4
+ data.tar.gz: 54dffc56caa3fa1bb72bb159b152d267d2e348b64b57322ea74cdf0ad9289cc1
5
5
  SHA512:
6
- metadata.gz: 20d61e7658cda93e3f24340ae6caa65c1c7868f37e699d7e6a9da524f2acaef9cccdf86cd47a7d2a1386037ad5aac0900935f3c3b2b4706926f16e60245f1b64
7
- data.tar.gz: 66e0c4b3af2d1feab711fe90b2af6a5ce08d4159501a1725a98243647a022431d871081bf2d1e82d46f8490c41589a1df6f15f2ee6f13391fb10df240bd41fe4
6
+ metadata.gz: e3524503031c73f86a0f3ed0b6c8dd39499f85df47052eca941f455552665b93ff041e5d6df9870cfb95ac1219017a561a483556306905bdc005caf252a5e088
7
+ data.tar.gz: 537da427406ce7c794d5e589e7e82f8c6fbda33a3a4231129905b69171421f41e98563c0496d9c7cc748f675d200729ec52f4d9067a8f7879972922358a2a868
data/.eslintrc CHANGED
@@ -1,32 +1,7 @@
1
1
  {
2
- "root": true,
3
- "extends": ["airbnb-base", "./node_modules/@theforeman/vendor-dev/eslint.extends.js"],
4
- "plugins": [
5
- "react"
6
- ],
7
- "env": {
8
- "browser": true,
9
- "es6": true,
10
- "node": true,
11
- "jasmine": true,
12
- "jest": true
13
- },
14
- "parser": "babel-eslint",
15
- "rules": {
16
- "react/jsx-uses-vars": "error",
17
- "react/jsx-uses-react": "error",
18
- "no-unused-vars": [
19
- "error",
20
- {
21
- "vars": "all",
22
- "args": "none"
23
- }
24
- ],
25
- "no-underscore-dangle": "off",
26
- "no-use-before-define": "off",
27
- "import/prefer-default-export": "off",
28
- // Import rules off for now due to HoundCI issue
29
- "import/no-unresolved": "off",
30
- "import/extensions": "off"
31
- }
2
+ "plugins": ["@theforeman/foreman"],
3
+ "extends": [
4
+ "plugin:@theforeman/foreman/core",
5
+ "plugin:@theforeman/foreman/plugins"
6
+ ]
32
7
  }
@@ -0,0 +1,100 @@
1
+ name: CI
2
+ on: [pull_request]
3
+ env:
4
+ RAILS_ENV: test
5
+ DATABASE_URL: postgresql://postgres:@localhost/test
6
+ DATABASE_CLEANER_ALLOW_REMOTE_DATABASE_URL: true
7
+ jobs:
8
+ rubocop:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v2
12
+ - name: Setup Ruby
13
+ uses: ruby/setup-ruby@v1
14
+ with:
15
+ ruby-version: 2.6
16
+ - name: Setup
17
+ run: |
18
+ gem install bundler
19
+ bundle install --jobs=3 --retry=3
20
+ - name: Run rubocop
21
+ run: bundle exec rubocop
22
+ test_ruby:
23
+ runs-on: ubuntu-latest
24
+ needs: rubocop
25
+ services:
26
+ postgres:
27
+ image: postgres:12.1
28
+ ports: ['5432:5432']
29
+ options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ foreman-core-branch: [2.1-stable, develop]
34
+ ruby-version: [2.5, 2.6]
35
+ node-version: [12]
36
+ steps:
37
+ - run: sudo apt-get install build-essential libcurl4-openssl-dev zlib1g-dev libpq-dev
38
+ - uses: actions/checkout@v2
39
+ with:
40
+ repository: theforeman/foreman
41
+ ref: ${{ matrix.foreman-core-branch }}
42
+ - uses: actions/checkout@v2
43
+ with:
44
+ path: foreman_remote_execution
45
+ - name: Setup Ruby
46
+ uses: ruby/setup-ruby@v1
47
+ with:
48
+ ruby-version: ${{ matrix.ruby-version }}
49
+ - name: Setup Node
50
+ uses: actions/setup-node@v1
51
+ with:
52
+ node-version: ${{ matrix.node-version }}
53
+ - uses: actions/cache@v1
54
+ with:
55
+ path: vendor/bundle
56
+ key: ${{ runner.os }}-fgems-${{ matrix.ruby-version }}-${{ hashFiles('Gemfile.lock') }}
57
+ restore-keys: |
58
+ ${{ runner.os }}-fgems-${{ matrix.ruby-version }}-
59
+ - name: Setup Bundler
60
+ run: |
61
+ echo "gem 'foreman_remote_execution', path: './foreman_remote_execution'" > bundler.d/foreman_remote_execution.local.rb
62
+ gem install bundler
63
+ bundle config set without journald development console libvirt
64
+ bundle config set path vendor/bundle
65
+ - name: Prepare test env
66
+ run: |
67
+ bundle install --jobs=3 --retry=3
68
+ bundle exec rake db:create
69
+ bundle exec rake db:migrate
70
+ - name: Run plugin tests
71
+ run: |
72
+ bundle exec rake test:foreman_remote_execution
73
+ bundle exec rake test TEST="test/unit/foreman/access_permissions_test.rb"
74
+ test_js:
75
+ runs-on: ubuntu-latest
76
+ needs: rubocop
77
+ strategy:
78
+ fail-fast: false
79
+ matrix:
80
+ ruby-version: [2.6]
81
+ node-version: [10, 12]
82
+ steps:
83
+ - uses: actions/checkout@v2
84
+ - name: Setup Ruby
85
+ uses: ruby/setup-ruby@v1
86
+ with:
87
+ ruby-version: ${{ matrix.ruby-version }}
88
+ - name: Setup Node
89
+ uses: actions/setup-node@v1
90
+ with:
91
+ node-version: ${{ matrix.node-version }}
92
+ - name: Nmp install
93
+ run: |
94
+ npm install
95
+ - name: Run plugin linter
96
+ run: |
97
+ npm run lint
98
+ - name: Run plugin tests
99
+ run: |
100
+ npm run test
data/.gitignore CHANGED
@@ -14,3 +14,4 @@ locale/*/*.po.time_stamp
14
14
  Gemfile.lock
15
15
  node_modules/
16
16
  package-lock.json
17
+ coverage/
@@ -6,6 +6,9 @@
6
6
  # Note that changes in the inspected code, or installation of new
7
7
  # versions of RuboCop, may require this file to be generated again.
8
8
 
9
+ Minitest/GlobalExpectations:
10
+ Enabled: false
11
+
9
12
  # Offense count: 2
10
13
  # Cop supports --auto-correct.
11
14
  # Configuration parameters: TreatCommentsAsGroupSeparators, Include.
data/Gemfile CHANGED
@@ -2,6 +2,7 @@ source 'http://rubygems.org'
2
2
 
3
3
  gemspec :name => 'foreman_remote_execution'
4
4
 
5
+ gem 'rubocop', '~> 0.80.0'
5
6
  gem 'rubocop-minitest'
6
7
  gem 'rubocop-performance'
7
8
  gem 'rubocop-rails'
@@ -18,7 +18,17 @@ module Api
18
18
 
19
19
  api :GET, '/job_invocations/:id', N_('Show job invocation')
20
20
  param :id, :identifier, :required => true
21
+ param :host_status, String, required: false, allow_blank: true, desc: N_('Show Job status for the hosts.')
21
22
  def show
23
+ @hosts = @job_invocation.targeting.hosts.authorized(:view_hosts, Host)
24
+ @template_invocations = @job_invocation.template_invocations
25
+ .where(host: @hosts)
26
+ .includes(:input_values)
27
+
28
+ if params[:host_status]
29
+ template_invocations = @template_invocations.includes(:run_host_job_task).to_a
30
+ @host_statuses = Hash[template_invocations.map { |ti| [ti.host_id, template_invocation_status(ti)] }]
31
+ end
22
32
  end
23
33
 
24
34
  def_param_group :job_invocation do
@@ -146,7 +156,7 @@ module Api
146
156
  end
147
157
 
148
158
  def find_host
149
- @host = Host.authorized(:view_hosts).find(params['host_id'])
159
+ @host = @nested_obj.targeting.hosts.authorized(:view_hosts, Host).find(params['host_id'])
150
160
  rescue ActiveRecord::RecordNotFound
151
161
  not_found({ :error => { :message => (_("Host with id '%{id}' was not found") % { :id => params['host_id'] }) } })
152
162
  end
@@ -204,6 +214,17 @@ module Api
204
214
  def parent_scope
205
215
  resource_class.where(nil)
206
216
  end
217
+
218
+ def template_invocation_status(template_invocation)
219
+ task = template_invocation.try(:run_host_job_task)
220
+ parent_task = @job_invocation.task
221
+
222
+ return(parent_task.result == 'cancelled' ? 'cancelled' : 'N/A') if task.nil?
223
+ return task.state if task.state == 'running' || task.state == 'planned'
224
+ return 'error' if task.result == 'warning'
225
+
226
+ task.result
227
+ end
207
228
  end
208
229
  end
209
230
  end
@@ -26,7 +26,10 @@ module Api
26
26
  private
27
27
 
28
28
  def resource_scope_for_template_invocations
29
- @job_invocation.template_invocations.search_for(*search_options)
29
+ @job_invocation.template_invocations
30
+ .includes(:host)
31
+ .where(host: Host.authorized(:view_hosts, Host))
32
+ .search_for(*search_options)
30
33
  end
31
34
 
32
35
  def find_job_invocation
@@ -37,6 +37,8 @@ module JobInvocationsChartHelper
37
37
  _('running %{percent}%%') % {:percent => percent}
38
38
  when HostStatus::ExecutionStatus::OK
39
39
  _('succeeded')
40
+ when HostStatus::ExecutionStatus::CANCELLED
41
+ _('cancelled')
40
42
  when HostStatus::ExecutionStatus::ERROR
41
43
  _('failed')
42
44
  else
@@ -29,7 +29,7 @@ module JobInvocationsHelper
29
29
  end
30
30
 
31
31
  def preview_hosts(template_invocation)
32
- hosts = template_invocation.targeting.hosts.take(20)
32
+ hosts = template_invocation.targeting.hosts.authorized(:view_hosts, Host).take(20)
33
33
  hosts.map do |host|
34
34
  collapsed_preview(host) +
35
35
  render(:partial => 'job_invocations/user_input',
@@ -55,4 +55,9 @@ module JobInvocationsHelper
55
55
  def show_job_location(location)
56
56
  location.presence || _('Any Location')
57
57
  end
58
+
59
+ def input_safe_value(input)
60
+ template_input = input.template_input
61
+ template_input.respond_to?(:hidden_value) && template_input.hidden_value ? '*' * 5 : input.value
62
+ end
58
63
  end
@@ -13,40 +13,36 @@ module RemoteExecutionHelper
13
13
  end
14
14
  end
15
15
 
16
- def template_invocation_status(task)
17
- if task.nil?
18
- icon_text('question', 'N/A', :kind => 'fa')
19
- elsif task.state == 'running'
20
- icon_text('running', _('running'), :kind => 'pficon')
21
- elsif task.state == 'planned'
22
- icon_text('build', _('planned'), :kind => 'pficon')
23
- else
24
- case task.result
25
- when 'warning', 'error'
26
- icon_text('error-circle-o', _('failed'), :kind => 'pficon')
27
- when 'cancelled'
28
- icon_text('warning-triangle-o', _('cancelled'), :kind => 'pficon')
29
- when 'success'
30
- icon_text('ok', _('success'), :kind => 'pficon')
31
- else
32
- task.result
33
- end
34
- end
16
+ def template_invocation_status(task, parent_task)
17
+ return(parent_task.result == 'cancelled' ? _('cancelled') : 'N/A') if task.nil?
18
+ return task.state if task.state == 'running' || task.state == 'planned'
19
+ return _('error') if task.result == 'warning'
20
+
21
+ task.result
35
22
  end
36
23
 
37
24
  def template_invocation_actions(task, host, job_invocation, template_invocation)
25
+ links = []
38
26
  host_task = template_invocation.try(:run_host_job_task)
39
- [
40
- display_link_if_authorized(_('Host detail'), hash_for_host_path(host).merge(:auth_object => host, :permission => :view_hosts, :authorizer => job_hosts_authorizer)),
41
- display_link_if_authorized(_('Rerun on %s') % host.name, hash_for_rerun_job_invocation_path(:id => job_invocation, :host_ids => [ host.id ], :authorizer => job_hosts_authorizer)),
42
- if host_task.present?
43
- display_link_if_authorized(
44
- _('Host task'),
45
- hash_for_foreman_tasks_task_path(host_task)
46
- .merge(:auth_object => host_task, :permission => :view_foreman_tasks)
47
- )
48
- end,
49
- ]
27
+
28
+ if authorized_for(hash_for_host_path(host).merge(auth_object: host, permission: :view_hosts, authorizer: job_hosts_authorizer))
29
+ links << { title: _('Host detail'),
30
+ action: { href: host_path(host), 'data-method': 'get', id: "#{host.name}-actions-detail" } }
31
+ end
32
+
33
+ if authorized_for(hash_for_rerun_job_invocation_path(id: job_invocation, host_ids: [ host.id ], authorizer: job_hosts_authorizer))
34
+ links << { title: (_('Rerun on %s') % host.name),
35
+ action: { href: rerun_job_invocation_path(job_invocation, host_ids: [ host.id ]),
36
+ 'data-method': 'get', id: "#{host.name}-actions-rerun" } }
37
+ end
38
+
39
+ if host_task.present? && authorized_for(hash_for_foreman_tasks_task_path(host_task).merge(auth_object: host_task, permission: :view_foreman_tasks))
40
+ links << { title: _('Host task'),
41
+ action: { href: foreman_tasks_task_path(host_task),
42
+ 'data-method': 'get', id: "#{host.name}-actions-task" } }
43
+ end
44
+
45
+ links
50
46
  end
51
47
 
52
48
  def remote_execution_provider_for(template_invocation)
@@ -233,4 +229,17 @@ module RemoteExecutionHelper
233
229
 
234
230
  task.execution_plan.actions[1].try(:input).try(:[], 'script')
235
231
  end
232
+
233
+ def targeting_hosts(job_invocation, hosts)
234
+ hosts.map do |host|
235
+ template_invocation = job_invocation.template_invocations.find { |template_inv| template_inv.host_id == host.id }
236
+ task = template_invocation.try(:run_host_job_task)
237
+ link_authorized = !task.nil? && authorized_for(hash_for_template_invocation_path(:id => template_invocation).merge(:auth_object => host, :permission => :view_hosts, :authorizer => job_hosts_authorizer))
238
+
239
+ { name: host.name,
240
+ link: link_authorized ? template_invocation_path(:id => template_invocation) : '',
241
+ status: template_invocation_status(task, job_invocation.task),
242
+ actions: template_invocation_actions(task, host, job_invocation, template_invocation) }
243
+ end
244
+ end
236
245
  end
@@ -29,6 +29,9 @@ module Actions
29
29
 
30
30
  raise _('Could not use any template used in the job invocation') if template_invocation.blank?
31
31
 
32
+ provider = template_invocation.template.provider
33
+ proxy_selector = provider.required_proxy_selector_for(template_invocation.template) || proxy_selector
34
+
32
35
  provider_type = template_invocation.template.provider_type.to_s
33
36
  proxy = determine_proxy!(proxy_selector, provider_type, host)
34
37
 
@@ -36,8 +39,6 @@ module Actions
36
39
  script = renderer.render
37
40
  raise _('Failed rendering template: %s') % renderer.error_message unless script
38
41
 
39
- provider = template_invocation.template.provider
40
-
41
42
  additional_options = { :hostname => provider.find_ip_or_hostname(host),
42
43
  :script => script,
43
44
  :execution_timeout_interval => job_invocation.execution_timeout_interval,
@@ -10,5 +10,11 @@ module ::ProxyAPI
10
10
  rescue => e
11
11
  raise ProxyException.new(url, e, N_('Unable to fetch public key'))
12
12
  end
13
+
14
+ def drop_from_known_hosts(hostname)
15
+ delete('known_hosts/' + hostname)
16
+ rescue => e
17
+ raise ProxyException.new(url, e, N_('Unable to remove host from known hosts'))
18
+ end
13
19
  end
14
20
  end
@@ -49,7 +49,7 @@ module ForemanRemoteExecution
49
49
  keys = remote_execution_ssh_keys
50
50
  source = 'global'
51
51
  if keys.present?
52
- value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| v || [] }
52
+ value, safe_value = params.fetch('remote_execution_ssh_keys', {}).values_at(:value, :safe_value).map { |v| [v].flatten.compact }
53
53
  params['remote_execution_ssh_keys'] = {:value => value + keys, :safe_value => safe_value + keys, :source => source}
54
54
  end
55
55
  [:remote_execution_ssh_user, :remote_execution_effective_user_method,
@@ -0,0 +1,63 @@
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 RestClient::ResourceNotFound => e
19
+ # ignore 404 when known_hosts entry is missing or the module was not enabled
20
+ Foreman::Logging.exception "Proxy failed to delete SSH known_hosts for #{name}, #{ip}", e, :level => :error
21
+ rescue => e
22
+ Rails.logger.warn e.message
23
+ return false
24
+ end
25
+ true
26
+ end
27
+
28
+ def ssh_destroy
29
+ logger.debug "Scheduling SSH known_hosts cleanup"
30
+
31
+ host, _kind, _target = host_kind_target
32
+ proxies = host.remote_execution_proxies('SSH').values
33
+ proxies.flatten.uniq.each do |proxy|
34
+ queue.create(id: queue_id(proxy.id), name: _("Remove SSH known hosts for %s") % self,
35
+ priority: 200, action: [self, :drop_from_known_hosts, proxy.id])
36
+ end
37
+ end
38
+
39
+ def queue_ssh_destroy
40
+ should_drop_from_known_hosts? && ssh_destroy
41
+ end
42
+
43
+ def should_drop_from_known_hosts?
44
+ host, = host_kind_target
45
+ host && !host.new_record? && host.build && host.changes.key?('build')
46
+ end
47
+
48
+ private
49
+
50
+ def host_kind_target
51
+ if self.is_a?(::Host::Base)
52
+ [self, 'host', name]
53
+ else
54
+ [self.host, 'interface', ip]
55
+ end
56
+ end
57
+
58
+ def queue_id(proxy_id)
59
+ _, kind, id = host_kind_target
60
+ "ssh_remove_known_hosts_#{kind}_#{id}_#{proxy_id}"
61
+ end
62
+ end
63
+ end