foreman_remote_execution 4.6.0 → 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby_ci.yml +7 -0
  3. data/.rubocop_todo.yml +1 -0
  4. data/app/controllers/api/v2/job_invocations_controller.rb +16 -1
  5. data/app/controllers/job_invocations_controller.rb +1 -1
  6. data/app/controllers/ui_job_wizard_controller.rb +21 -2
  7. data/app/graphql/mutations/job_invocations/create.rb +43 -0
  8. data/app/graphql/types/job_invocation.rb +16 -0
  9. data/app/graphql/types/job_invocation_input.rb +13 -0
  10. data/app/graphql/types/recurrence_input.rb +8 -0
  11. data/app/graphql/types/scheduling_input.rb +6 -0
  12. data/app/graphql/types/targeting_enum.rb +7 -0
  13. data/app/helpers/concerns/foreman_remote_execution/hosts_helper_extensions.rb +5 -1
  14. data/app/helpers/remote_execution_helper.rb +9 -3
  15. data/app/lib/actions/remote_execution/run_host_job.rb +10 -1
  16. data/app/lib/actions/remote_execution/run_hosts_job.rb +58 -4
  17. data/app/mailers/rex_job_mailer.rb +15 -0
  18. data/app/models/concerns/foreman_remote_execution/host_extensions.rb +10 -0
  19. data/app/models/concerns/foreman_remote_execution/smart_proxy_extensions.rb +6 -0
  20. data/app/models/host_proxy_invocation.rb +4 -0
  21. data/app/models/host_status/execution_status.rb +3 -3
  22. data/app/models/job_invocation.rb +12 -5
  23. data/app/models/job_invocation_composer.rb +25 -17
  24. data/app/models/job_template.rb +1 -1
  25. data/app/models/remote_execution_feature.rb +5 -1
  26. data/app/models/remote_execution_provider.rb +18 -2
  27. data/app/models/rex_mail_notification.rb +13 -0
  28. data/app/models/targeting.rb +7 -3
  29. data/app/services/ui_notifications/remote_execution_jobs/base_job_finish.rb +2 -1
  30. data/app/views/dashboard/_latest-jobs.html.erb +21 -0
  31. data/app/views/job_invocations/index.html.erb +1 -1
  32. data/app/views/job_invocations/refresh.js.erb +1 -0
  33. data/app/views/rex_job_mailer/job_finished.html.erb +24 -0
  34. data/app/views/rex_job_mailer/job_finished.text.erb +9 -0
  35. data/app/views/template_invocations/show.html.erb +2 -1
  36. data/app/views/templates/ssh/module_action.erb +1 -0
  37. data/app/views/templates/ssh/power_action.erb +2 -0
  38. data/app/views/templates/ssh/puppet_run_once.erb +1 -0
  39. data/config/routes.rb +1 -0
  40. data/db/migrate/2021051713291621250977_add_host_proxy_invocations.rb +12 -0
  41. data/db/migrate/20210816100932_rex_setting_category_to_dsl.rb +5 -0
  42. data/db/seeds.d/50-notification_blueprints.rb +14 -0
  43. data/db/seeds.d/95-mail_notifications.rb +24 -0
  44. data/foreman_remote_execution.gemspec +2 -3
  45. data/lib/foreman_remote_execution/engine.rb +114 -8
  46. data/lib/foreman_remote_execution/version.rb +1 -1
  47. data/package.json +9 -7
  48. data/test/functional/api/v2/job_invocations_controller_test.rb +20 -0
  49. data/test/functional/cockpit_controller_test.rb +0 -1
  50. data/test/graphql/mutations/job_invocations/create.rb +58 -0
  51. data/test/graphql/queries/job_invocation_query_test.rb +31 -0
  52. data/test/graphql/queries/job_invocations_query_test.rb +35 -0
  53. data/test/helpers/remote_execution_helper_test.rb +0 -1
  54. data/test/unit/actions/run_host_job_test.rb +21 -0
  55. data/test/unit/actions/run_hosts_job_test.rb +99 -4
  56. data/test/unit/concerns/host_extensions_test.rb +40 -7
  57. data/test/unit/input_template_renderer_test.rb +1 -89
  58. data/test/unit/job_invocation_composer_test.rb +18 -18
  59. data/test/unit/job_invocation_report_template_test.rb +16 -13
  60. data/test/unit/job_invocation_test.rb +1 -1
  61. data/test/unit/job_template_effective_user_test.rb +0 -4
  62. data/test/unit/remote_execution_provider_test.rb +46 -4
  63. data/test/unit/targeting_test.rb +68 -1
  64. data/webpack/JobWizard/JobWizard.js +158 -24
  65. data/webpack/JobWizard/JobWizard.scss +93 -1
  66. data/webpack/JobWizard/JobWizardConstants.js +54 -0
  67. data/webpack/JobWizard/JobWizardSelectors.js +41 -0
  68. data/webpack/JobWizard/__tests__/fixtures.js +188 -3
  69. data/webpack/JobWizard/__tests__/integration.test.js +41 -106
  70. data/webpack/JobWizard/__tests__/validation.test.js +141 -0
  71. data/webpack/JobWizard/autofill.js +38 -0
  72. data/webpack/JobWizard/index.js +7 -0
  73. data/webpack/JobWizard/steps/AdvancedFields/AdvancedFields.js +41 -10
  74. data/webpack/JobWizard/steps/AdvancedFields/DescriptionField.js +90 -0
  75. data/webpack/JobWizard/steps/AdvancedFields/Fields.js +116 -55
  76. data/webpack/JobWizard/steps/AdvancedFields/__tests__/AdvancedFields.test.js +354 -16
  77. data/webpack/JobWizard/steps/AdvancedFields/__tests__/__snapshots__/AdvancedFields.test.js.snap +79 -246
  78. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.js +5 -2
  79. data/webpack/JobWizard/steps/CategoryAndTemplate/CategoryAndTemplate.test.js +123 -51
  80. data/webpack/JobWizard/steps/CategoryAndTemplate/index.js +3 -2
  81. data/webpack/JobWizard/steps/HostsAndInputs/HostPreviewModal.js +62 -0
  82. data/webpack/JobWizard/steps/HostsAndInputs/HostSearch.js +54 -0
  83. data/webpack/JobWizard/steps/HostsAndInputs/SelectAPI.js +33 -0
  84. data/webpack/JobWizard/steps/HostsAndInputs/SelectGQL.js +52 -0
  85. data/webpack/JobWizard/steps/HostsAndInputs/SelectedChips.js +100 -0
  86. data/webpack/JobWizard/steps/HostsAndInputs/TemplateInputs.js +23 -0
  87. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/HostsAndInputs.test.js +151 -0
  88. data/webpack/JobWizard/steps/HostsAndInputs/__tests__/TemplateInputs.test.js +53 -0
  89. data/webpack/JobWizard/steps/HostsAndInputs/buildHostQuery.js +18 -0
  90. data/webpack/JobWizard/steps/HostsAndInputs/hostgroups.gql +8 -0
  91. data/webpack/JobWizard/steps/HostsAndInputs/hosts.gql +8 -0
  92. data/webpack/JobWizard/steps/HostsAndInputs/index.js +214 -0
  93. data/webpack/JobWizard/steps/ReviewDetails/index.js +193 -0
  94. data/webpack/JobWizard/steps/Schedule/PurposeField.js +31 -0
  95. data/webpack/JobWizard/steps/Schedule/QueryType.js +51 -0
  96. data/webpack/JobWizard/steps/Schedule/RepeatCron.js +53 -0
  97. data/webpack/JobWizard/steps/Schedule/RepeatDaily.js +37 -0
  98. data/webpack/JobWizard/steps/Schedule/RepeatHour.js +54 -0
  99. data/webpack/JobWizard/steps/Schedule/RepeatMonth.js +46 -0
  100. data/webpack/JobWizard/steps/Schedule/RepeatOn.js +125 -0
  101. data/webpack/JobWizard/steps/Schedule/RepeatWeek.js +70 -0
  102. data/webpack/JobWizard/steps/Schedule/ScheduleType.js +28 -0
  103. data/webpack/JobWizard/steps/Schedule/StartEndDates.js +106 -0
  104. data/webpack/JobWizard/steps/Schedule/__tests__/Schedule.test.js +402 -0
  105. data/webpack/JobWizard/steps/Schedule/__tests__/StartEndDates.test.js +32 -0
  106. data/webpack/JobWizard/steps/Schedule/index.js +178 -0
  107. data/webpack/JobWizard/steps/form/DateTimePicker.js +126 -0
  108. data/webpack/JobWizard/steps/form/FormHelpers.js +5 -0
  109. data/webpack/JobWizard/steps/form/Formatter.js +181 -0
  110. data/webpack/JobWizard/steps/form/NumberInput.js +36 -0
  111. data/webpack/JobWizard/steps/form/ResourceSelect.js +29 -0
  112. data/webpack/JobWizard/steps/form/SearchSelect.js +121 -0
  113. data/webpack/JobWizard/steps/form/SelectField.js +28 -5
  114. data/webpack/JobWizard/steps/form/WizardTitle.js +14 -0
  115. data/webpack/JobWizard/steps/form/__tests__/SelectSearch.test.js +33 -0
  116. data/webpack/JobWizard/submit.js +120 -0
  117. data/webpack/JobWizard/validation.js +53 -0
  118. data/webpack/__mocks__/foremanReact/Root/Context/ForemanContext/index.js +2 -0
  119. data/webpack/__mocks__/foremanReact/common/I18n.js +2 -0
  120. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteActions.js +1 -0
  121. data/webpack/__mocks__/foremanReact/components/AutoComplete/AutoCompleteConstants.js +1 -0
  122. data/webpack/__mocks__/foremanReact/components/SearchBar.js +18 -1
  123. data/webpack/__mocks__/foremanReact/routes/RouterSelector.js +1 -0
  124. data/webpack/helpers.js +1 -0
  125. data/webpack/react_app/components/RecentJobsCard/JobStatusIcon.js +43 -0
  126. data/webpack/react_app/components/RecentJobsCard/RecentJobsCard.js +73 -66
  127. data/webpack/react_app/components/RecentJobsCard/RecentJobsTable.js +98 -0
  128. data/webpack/react_app/components/RecentJobsCard/constants.js +11 -0
  129. data/webpack/react_app/components/RecentJobsCard/styles.scss +11 -0
  130. data/webpack/react_app/components/TargetingHosts/__tests__/__snapshots__/TargetingHostsPage.test.js.snap +1 -0
  131. data/webpack/react_app/extend/fillRecentJobsCard.js +1 -1
  132. metadata +71 -16
  133. data/app/models/concerns/foreman_remote_execution/orchestration/ssh.rb +0 -70
  134. data/app/models/setting/remote_execution.rb +0 -88
  135. data/test/models/orchestration/ssh_test.rb +0 -56
  136. data/webpack/JobWizard/__tests__/JobWizard.test.js +0 -13
  137. data/webpack/JobWizard/__tests__/__snapshots__/JobWizard.test.js.snap +0 -32
  138. data/webpack/JobWizard/steps/CategoryAndTemplate/__snapshots__/CategoryAndTemplate.test.js.snap +0 -113
  139. data/webpack/JobWizard/steps/form/__tests__/GroupedSelectField.test.js +0 -38
  140. data/webpack/JobWizard/steps/form/__tests__/SelectField.test.js +0 -23
  141. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/GroupedSelectField.test.js.snap +0 -37
  142. data/webpack/JobWizard/steps/form/__tests__/__snapshots__/SelectField.test.js.snap +0 -23
  143. data/webpack/react_app/components/RecentJobsCard/styles.css +0 -15
data/package.json CHANGED
@@ -21,18 +21,20 @@
21
21
  },
22
22
  "devDependencies": {
23
23
  "@babel/core": "^7.7.0",
24
- "@theforeman/builder": "^4.14.0",
25
- "@theforeman/eslint-plugin-foreman": "^4.14.0",
26
- "@theforeman/stories": "^4.14.0",
27
- "@theforeman/test": "^4.14.0",
28
- "@theforeman/vendor-dev": "^4.14.0",
24
+ "@theforeman/builder": "^8.16.0",
25
+ "@theforeman/eslint-plugin-foreman": "^8.16.0",
26
+ "@theforeman/stories": "^8.16.0",
27
+ "@theforeman/test": "^8.16.0",
28
+ "@theforeman/vendor-dev": "^8.16.0",
29
29
  "babel-eslint": "^10.0.0",
30
30
  "eslint": "^6.8.0",
31
31
  "prettier": "^1.19.1",
32
32
  "@patternfly/react-catalog-view-extension": "^4.8.126",
33
- "redux-mock-store": "^1.2.2"
33
+ "redux-mock-store": "^1.2.2",
34
+ "graphql-tag": "^2.11.0",
35
+ "graphql": "^15.5.0"
34
36
  },
35
37
  "peerDependencies": {
36
- "@theforeman/vendor": "^8.3.0"
38
+ "@theforeman/vendor": "^8.16.0"
37
39
  }
38
40
  }
@@ -90,6 +90,16 @@ module Api
90
90
  assert_response :success
91
91
  end
92
92
 
93
+ test 'should create with a scheduled recurrence' do
94
+ @attrs[:scheduling] = { start_at: (Time.now + 1.hour) }
95
+ @attrs[:recurrence] = { cron_line: '5 * * * *' }
96
+ post :create, params: { job_invocation: @attrs }
97
+ invocation = ActiveSupport::JSON.decode(@response.body)
98
+ assert_equal 'recurring', invocation['mode']
99
+ assert invocation['start_at']
100
+ assert_response :success
101
+ end
102
+
93
103
  context 'with_feature' do
94
104
  setup do
95
105
  @feature = FactoryBot.create(:remote_execution_feature,
@@ -181,6 +191,11 @@ module Api
181
191
  host_id: FactoryBot.create(:host).id }
182
192
  assert_response :missing
183
193
  end
194
+
195
+ test 'should not break when taxonomy parameters are provided' do
196
+ get :output, params: { :job_invocation_id => @invocation.id, :host_id => host.id, :organization_id => host.organization_id, :location_id => host.location_id }
197
+ assert_response :success
198
+ end
184
199
  end
185
200
 
186
201
  describe '#outputs' do
@@ -233,6 +248,11 @@ module Api
233
248
  assert_response :success
234
249
  end
235
250
 
251
+ test 'should not break when taxonomy parameters are provided' do
252
+ get :raw_output, params: { :job_invocation_id => @invocation.id, :host_id => host.id, :organization_id => host.organization_id, :location_id => host.location_id }
253
+ assert_response :success
254
+ end
255
+
236
256
  test 'should provide raw output for delayed task' do
237
257
  start_time = Time.now
238
258
  JobInvocation.any_instance
@@ -2,7 +2,6 @@ require 'test_plugin_helper'
2
2
 
3
3
  class CockpitControllerTest < ActionController::TestCase
4
4
  def setup
5
- Setting::RemoteExecution.load_defaults
6
5
  as_admin do
7
6
  @host = FactoryBot.create(:host)
8
7
  end
@@ -0,0 +1,58 @@
1
+ require 'test_plugin_helper'
2
+
3
+ module Mutations
4
+ module JobInvocations
5
+ class CreateMutationTest < ActiveSupport::TestCase
6
+ let(:host) { FactoryBot.create(:host) }
7
+ let(:job_template) { FactoryBot.create(:job_template, :with_input) }
8
+ let(:cron_line) { '5 * * * *' }
9
+ let(:purpose) { 'test' }
10
+ let(:variables) do
11
+ {
12
+ jobInvocation: {
13
+ hostIds: [host.id],
14
+ jobTemplateId: job_template.id,
15
+ targetingType: 'static_query',
16
+ inputs: { job_template.template_inputs.first.name => "bar" },
17
+ recurrence: {
18
+ cronLine: cron_line,
19
+ purpose: purpose,
20
+ },
21
+ },
22
+ }
23
+ end
24
+
25
+ let(:query) do
26
+ <<-GRAPHQL
27
+ mutation CreateJobInvocation($jobInvocation: JobInvocationInput!) {
28
+ createJobInvocation(input: { jobInvocation: $jobInvocation }) {
29
+ jobInvocation {
30
+ id
31
+ description
32
+ recurringLogic {
33
+ cronLine
34
+ purpose
35
+ }
36
+ }
37
+ }
38
+ }
39
+ GRAPHQL
40
+ end
41
+
42
+ context 'with admin user' do
43
+ let(:user) { FactoryBot.create(:user, :admin) }
44
+ let(:context) { { current_user: user } }
45
+
46
+ test 'create a job invocation' do
47
+ assert_difference('JobInvocation.count', +1) do
48
+ result = ForemanGraphqlSchema.execute(query, variables: variables, context: context)
49
+ assert_empty result['errors']
50
+ assert_empty result['data']['createJobInvocation']['jobInvocation']['errors']
51
+ assert_equal cron_line, result['data']['createJobInvocation']['jobInvocation']['recurringLogic']['cronLine']
52
+ assert_equal purpose, result['data']['createJobInvocation']['jobInvocation']['recurringLogic']['purpose']
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -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
@@ -26,7 +26,6 @@ class RemoteExecutionHelperTest < ActionView::TestCase
26
26
 
27
27
  describe 'test correct setting' do
28
28
  it 'should found correct template from setting' do
29
- Setting::RemoteExecution.load_defaults
30
29
  template_name = 'Job Invocation Report Template'
31
30
  setting_key = 'remote_execution_job_invocation_report_template'
32
31
  template = FactoryBot.create(:report_template, name: template_name)
@@ -36,6 +36,27 @@ module ForemanRemoteExecution
36
36
  end
37
37
  end
38
38
 
39
+ describe '#verify_permission' do
40
+ let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
41
+ let(:template_invocation) { job_invocation.template_invocations.first }
42
+
43
+ before { job_invocation }
44
+
45
+ it 'raises an exception when run against an infrastructure host' do
46
+ template_invocation.host = FactoryBot.create(:host, :with_infrastructure_facet)
47
+
48
+ setup_user('view', 'hosts')
49
+ setup_user('view', 'job_templates')
50
+ setup_user('create', 'template_invocations')
51
+
52
+ action = Actions::RemoteExecution::RunHostJob.allocate
53
+ exception = assert_raises do
54
+ action.send(:verify_permissions, template_invocation.host, template_invocation)
55
+ end
56
+ _(exception.message).must_include "infrastructure host #{template_invocation.host.name}"
57
+ end
58
+ end
59
+
39
60
  describe '#finalize' do
40
61
  let(:host) { FactoryBot.create(:host, :with_execution) }
41
62
 
@@ -5,6 +5,16 @@ module ForemanRemoteExecution
5
5
  class RunHostsJobTest < ActiveSupport::TestCase
6
6
  include Dynflow::Testing
7
7
 
8
+ # Adding run_step_id wich is needed in RunHostsJob as a quick fix
9
+ # it will be added to dynflow in the future see https://github.com/Dynflow/dynflow/pull/391
10
+ # rubocop:disable Style/ClassAndModuleChildren
11
+ class Dynflow::Testing::DummyPlannedAction
12
+ def run_step_id
13
+ Dynflow::Testing.get_id
14
+ end
15
+ end
16
+ # rubocop:enable Style/ClassAndModuleChildren
17
+
8
18
  let(:host) { FactoryBot.create(:host, :with_execution) }
9
19
  let(:proxy) { host.remote_execution_proxies('SSH')[:subnet].first }
10
20
  let(:targeting) { FactoryBot.create(:targeting, :search_query => "name = #{host.name}", :user => User.current) }
@@ -94,6 +104,22 @@ module ForemanRemoteExecution
94
104
  planned # To make the expectations happy
95
105
  end
96
106
 
107
+ describe '#proxy_batch_size' do
108
+ it 'defaults to Setting[foreman_tasks_proxy_batch_size]' do
109
+ Setting.expects(:[]).with('foreman_tasks_proxy_batch_size').returns(14)
110
+ planned
111
+ _(planned.proxy_batch_size).must_equal 14
112
+ end
113
+
114
+ it 'gets the provider value' do
115
+ provider = mock('provider')
116
+ provider.expects(:proxy_batch_size).returns(15)
117
+ JobTemplate.any_instance.expects(:provider).returns(provider)
118
+
119
+ _(planned.proxy_batch_size).must_equal 15
120
+ end
121
+ end
122
+
97
123
  describe 'concurrency control' do
98
124
  let(:level) { 5 }
99
125
  let(:span) { 60 }
@@ -127,10 +153,79 @@ module ForemanRemoteExecution
127
153
  end
128
154
 
129
155
  describe 'notifications' do
130
- it 'creates notification on sucess run' do
131
- FactoryBot.create(:notification_blueprint, :name => 'rex_job_succeeded')
132
- assert_difference 'NotificationRecipient.where(:user_id => targeting.user.id).count' do
133
- finalize_action planned
156
+ it 'creates drawer notification on succeess' do
157
+ blueprint = planned.job_invocation.build_notification
158
+ blueprint.expects(:deliver!)
159
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
160
+ planned.notify_on_success(nil)
161
+ end
162
+
163
+ it 'creates drawer notification on failure' do
164
+ blueprint = planned.job_invocation.build_notification
165
+ blueprint.expects(:deliver!)
166
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
167
+ planned.notify_on_failure(nil)
168
+ end
169
+
170
+ describe 'ignoring drawer notification' do
171
+ before do
172
+ blueprint = planned.job_invocation.build_notification
173
+ blueprint.expects(:deliver!)
174
+ planned.job_invocation.expects(:build_notification).returns(blueprint)
175
+ end
176
+
177
+ let(:mail) do
178
+ object = mock
179
+ object.stubs(:deliver_now)
180
+ object
181
+ end
182
+
183
+ describe 'for user subscribed to all' do
184
+ before do
185
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::ALL_JOBS))
186
+ end
187
+
188
+ it 'sends the mail notification on success' do
189
+ RexJobMailer.expects(:job_finished).returns(mail)
190
+ planned.notify_on_success(nil)
191
+ end
192
+
193
+ it 'sends the mail notification on failure' do
194
+ RexJobMailer.expects(:job_finished).returns(mail)
195
+ planned.notify_on_failure(nil)
196
+ end
197
+ end
198
+
199
+ describe 'for user subscribed to failures' do
200
+ before do
201
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::FAILED_JOBS))
202
+ end
203
+
204
+ it 'it does not send the mail notification on success' do
205
+ RexJobMailer.expects(:job_finished).never
206
+ planned.notify_on_success(nil)
207
+ end
208
+
209
+ it 'sends the mail notification on failure' do
210
+ RexJobMailer.expects(:job_finished).returns(mail)
211
+ planned.notify_on_failure(nil)
212
+ end
213
+ end
214
+
215
+ describe 'for user subscribed to successful jobs' do
216
+ before do
217
+ planned.expects(:mail_notification_preference).returns(UserMailNotification.new(:interval => RexMailNotification::SUCCEEDED_JOBS))
218
+ end
219
+
220
+ it 'sends the mail notification on success' do
221
+ RexJobMailer.expects(:job_finished).returns(mail)
222
+ planned.notify_on_success(nil)
223
+ end
224
+
225
+ it 'does not send the mail notification on failure' do
226
+ RexJobMailer.expects(:job_finished).never
227
+ planned.notify_on_failure(nil)
228
+ end
134
229
  end
135
230
  end
136
231
  end
@@ -1,9 +1,6 @@
1
1
  require 'test_plugin_helper'
2
2
 
3
3
  class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
4
- before do
5
- Setting::RemoteExecution.load_defaults
6
- end
7
4
  let(:provider) { 'SSH' }
8
5
 
9
6
  before { User.current = FactoryBot.build(:user, :admin) }
@@ -131,23 +128,23 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
131
128
  end
132
129
 
133
130
  context 'fallback strategy' do
134
- let(:host) { FactoryBot.build(:host, :with_puppet) }
131
+ let(:host) { FactoryBot.build(:host, :with_tftp_subnet) }
135
132
 
136
133
  context 'enabled' do
137
134
  before do
138
135
  Setting[:remote_execution_fallback_proxy] = true
139
- host.puppet_proxy.features << FactoryBot.create(:feature, :ssh)
136
+ host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
140
137
  end
141
138
 
142
139
  it 'returns a fallback proxy' do
143
- host.remote_execution_proxies(provider)[:fallback].must_include host.puppet_proxy
140
+ host.remote_execution_proxies(provider)[:fallback].must_include host.subnet.tftp
144
141
  end
145
142
  end
146
143
 
147
144
  context 'disabled' do
148
145
  before do
149
146
  Setting[:remote_execution_fallback_proxy] = false
150
- host.puppet_proxy.features << FactoryBot.create(:feature, :ssh)
147
+ host.subnet.tftp.features << FactoryBot.create(:feature, :ssh)
151
148
  end
152
149
 
153
150
  it 'returns no proxy' do
@@ -182,4 +179,40 @@ class ForemanRemoteExecutionHostExtensionsTest < ActiveSupport::TestCase
182
179
  end
183
180
  end
184
181
  end
182
+
183
+ describe '#execution_scope' do
184
+ let(:host) { FactoryBot.create(:host) }
185
+ let(:infra_host) { FactoryBot.create(:host, :with_infrastructure_facet) }
186
+
187
+ before do
188
+ host
189
+ infra_host
190
+ end
191
+
192
+ context 'without infrastructure host permission' do
193
+ it 'omits the infrastructure host' do
194
+ setup_user('view', 'hosts')
195
+
196
+ hosts = ::Host::Managed.execution_scope
197
+ hosts.must_include host
198
+ hosts.wont_include infra_host
199
+ end
200
+ end
201
+
202
+ context 'with infrastructure host permission' do
203
+ it 'finds the host as admin' do
204
+ assert User.current.admin?
205
+ hosts = ::Host::Managed.execution_scope
206
+ hosts.must_include host
207
+ hosts.must_include infra_host
208
+ end
209
+
210
+ it 'finds the host as user with needed permissions' do
211
+ setup_user('execute_jobs_on', 'infrastructure_hosts')
212
+ hosts = ::Host::Managed.execution_scope
213
+ hosts.must_include host
214
+ hosts.must_include infra_host
215
+ end
216
+ end
217
+ end
185
218
  end
@@ -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
- let(:environment) { FactoryBot.create(:environment) }
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
@@ -270,10 +270,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
270
270
  composer.pattern_template_invocations.first
271
271
  end
272
272
 
273
- before do
274
- Setting::RemoteExecution.load_defaults
275
- end
276
-
277
273
  context 'when overridable and provided' do
278
274
  let(:overridable) { true }
279
275
  let(:invocation_effective_user) { 'invocation user' }
@@ -350,12 +346,6 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
350
346
  end
351
347
 
352
348
  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
349
  context 'there are hostgroups and hosts bookmark' do
360
350
  let(:hostgroups) { Bookmark.create(:name => 'hostgroups', :query => 'name = x', :controller => 'hostgroups') }
361
351
  let(:hosts) { Bookmark.create(:name => 'hosts', :query => 'name = x', :controller => 'hosts') }
@@ -376,8 +366,10 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
376
366
  let(:host) { FactoryBot.create(:host) }
377
367
 
378
368
  it 'obeys authorization' do
369
+ fake_scope = mock
379
370
  composer.stubs(:displayed_search_query => "name = #{host.name}")
380
- Host.expects(:authorized).with(:view_hosts, Host).returns(Host.where({}))
371
+ Host.expects(:execution_scope).returns(fake_scope)
372
+ fake_scope.expects(:authorized).with(:view_hosts, Host).returns(Host.where({}))
381
373
  composer.targeted_hosts_count
382
374
  end
383
375
 
@@ -864,7 +856,9 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
864
856
  it 'marks targeting as resolved if static' do
865
857
  created = JobInvocationComposer.from_job_invocation(job_invocation).job_invocation
866
858
  assert created.targeting.resolved?
867
- assert_equal job_invocation.template_invocations_host_ids, created.targeting.host_ids
859
+ created.targeting.save
860
+ created.targeting.reload
861
+ assert_equal job_invocation.template_invocations_host_ids, created.targeting.targeting_hosts.pluck(:host_id)
868
862
  end
869
863
 
870
864
  it 'takes randomized_ordering from the original job invocation when rerunning failed' do
@@ -874,6 +868,17 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
874
868
  composer = JobInvocationComposer.from_job_invocation(job_invocation, :host_ids => host_ids)
875
869
  assert composer.job_invocation.targeting.randomized_ordering
876
870
  end
871
+
872
+ it 'works with invalid hosts' do
873
+ host = job_invocation.targeting.hosts.first
874
+ ::Host::Managed.any_instance.stubs(:valid?).returns(false)
875
+ composer = JobInvocationComposer.from_job_invocation(job_invocation, {})
876
+ targeting = composer.compose.job_invocation.targeting
877
+ targeting.save!
878
+ targeting.reload
879
+ assert targeting.valid?
880
+ assert_equal targeting.hosts.pluck(:id), [host.id]
881
+ end
877
882
  end
878
883
 
879
884
  describe '.for_feature' do
@@ -918,12 +923,7 @@ class JobInvocationComposerTest < ActiveSupport::TestCase
918
923
 
919
924
  context 'with template in setting present' do
920
925
  before do
921
- FactoryBot.create(
922
- :setting,
923
- :name => 'remote_execution_form_job_template',
924
- :category => 'Setting::RemoteExecution',
925
- :value => setting_template.name
926
- )
926
+ Setting[:remote_execution_form_job_template] = setting_template.name
927
927
  end
928
928
 
929
929
  it 'should resolve category to the setting value' do
@@ -19,7 +19,7 @@ class JobReportTemplateTest < ActiveSupport::TestCase
19
19
  it 'in settings includes only report templates with job_id input' do
20
20
  FactoryBot.create(:report_template, name: 'Template 1')
21
21
  job_invocation_template
22
- templates = Setting::RemoteExecution.job_invocation_report_templates_select
22
+ templates = ForemanRemoteExecution.job_invocation_report_templates_select
23
23
 
24
24
  assert_include templates, 'Job Invocation Report Template'
25
25
  end
@@ -28,15 +28,16 @@ class JobReportTemplateTest < ActiveSupport::TestCase
28
28
  describe 'task reporting' do
29
29
  let(:fake_outputs) do
30
30
  [
31
- { 'output_type' => 'stderr', 'output' => "error", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
32
- { 'output_type' => 'stdout', 'output' => "output", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
33
- { 'output_type' => 'stdebug', 'output' => "debug", 'timestamp' => Time.new(2020, 12, 1, 0, 0, 0).utc },
31
+ { 'output_type' => 'stderr', 'output' => "error" },
32
+ { 'output_type' => 'stdout', 'output' => "output" },
33
+ { 'output_type' => 'debug', 'output' => "debug" },
34
34
  ]
35
35
  end
36
- let(:fake_task) { FakeTask.new(result: 'success', action_continuous_output: fake_outputs) }
36
+ let(:fake_task) { FakeTask.new(result: 'success', action_continuous_output: fake_outputs, :ended_at => Time.new(2020, 12, 1, 0, 0, 0).utc) }
37
+ let(:job_invocation) { FactoryBot.create(:job_invocation, :with_task) }
38
+ let(:host) { job_invocation.template_invocations.first.host }
37
39
 
38
40
  it 'should render task outputs' do
39
- job_invocation = FactoryBot.create(:job_invocation, :with_task)
40
41
  JobInvocation.any_instance.expects(:sub_task_for_host).returns(fake_task)
41
42
 
42
43
  input = job_invocation_template.template_inputs.first
@@ -44,13 +45,15 @@ class JobReportTemplateTest < ActiveSupport::TestCase
44
45
  result = ReportComposer.new(composer_params).render
45
46
 
46
47
  # parsing the CSV result
47
- CSV.parse(result.strip, headers: true).each_with_index do |row, i|
48
- row_hash = row.to_h
49
- assert_equal 'success', row_hash['result']
50
- assert_equal fake_outputs[i]['output_type'], row_hash['type']
51
- assert_equal fake_outputs[i]['output'], row_hash['message']
52
- assert_kind_of Time, Time.zone.parse(row_hash['time']), 'Parsing of time column failed'
53
- end
48
+ rows = CSV.parse(result.strip, headers: true)
49
+ assert_equal 1, rows.count
50
+ row = rows.first
51
+ assert_equal host.name, row['Host']
52
+ assert_equal 'success', row['Result']
53
+ assert_equal 'error', row['stderr']
54
+ assert_equal 'output', row['stdout']
55
+ assert_equal 'debug', row['debug']
56
+ assert_kind_of Time, Time.zone.parse(row['Finished']), 'Parsing of time column failed'
54
57
  end
55
58
  end
56
59
  end