foreman_rh_cloud 13.0.9 → 13.0.10

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 (114) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/inventory_upload/report_actions.rb +8 -1
  3. data/app/controllers/foreman_inventory_upload/accounts_controller.rb +82 -7
  4. data/app/controllers/foreman_inventory_upload/api/tasks_controller.rb +110 -0
  5. data/app/controllers/foreman_inventory_upload/reports_controller.rb +41 -17
  6. data/app/controllers/foreman_inventory_upload/uploads_controller.rb +0 -9
  7. data/config/routes.rb +4 -2
  8. data/db/migrate/20251209163012_drop_task_output_tables.foreman_rh_cloud.rb +24 -0
  9. data/lib/foreman_inventory_upload/async/generate_all_reports_job.rb +1 -1
  10. data/lib/foreman_inventory_upload/async/host_inventory_report_job.rb +39 -0
  11. data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +28 -57
  12. data/lib/foreman_rh_cloud/plugin.rb +1 -0
  13. data/lib/foreman_rh_cloud/version.rb +1 -1
  14. data/lib/tasks/rh_cloud_inventory.rake +4 -2
  15. data/package.json +1 -1
  16. data/test/controllers/accounts_controller_test.rb +10 -11
  17. data/test/controllers/insights_cloud/api/cloud_request_controller_test.rb +1 -2
  18. data/test/jobs/host_inventory_report_job_test.rb +161 -97
  19. data/test/jobs/single_host_report_job_test.rb +36 -54
  20. data/test/jobs/upload_report_direct_job_test.rb +132 -132
  21. data/test/unit/rh_cloud_permissions_test.rb +2 -0
  22. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +6 -6
  23. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js +49 -34
  24. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListActions.js +2 -2
  25. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js +2 -2
  26. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.js +15 -9
  27. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +6 -5
  28. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.fixtures.js +2 -2
  29. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.js +10 -14
  30. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatusHelper.js +9 -4
  31. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/__snapshots__/ListItemStatus.test.js.snap +4 -4
  32. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountList.test.js.snap +15 -9
  33. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +7 -7
  34. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +6 -6
  35. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +12 -12
  36. data/webpack/ForemanInventoryUpload/Components/Dashboard/Dashboard.js +36 -132
  37. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/Dashboard.test.js +60 -17
  38. data/webpack/ForemanInventoryUpload/Components/Dashboard/index.js +1 -34
  39. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/integration.test.js.snap +0 -1
  40. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +1 -26
  41. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +2 -2
  42. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +22 -9
  43. data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +67 -4
  44. data/webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js +140 -0
  45. data/webpack/ForemanInventoryUpload/Components/TaskHistory/index.js +1 -0
  46. data/webpack/ForemanInventoryUpload/Components/TaskHistory/taskHistory.scss +40 -0
  47. data/webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js +340 -0
  48. data/webpack/ForemanInventoryUpload/Components/TaskProgress/index.js +1 -0
  49. data/webpack/ForemanInventoryUpload/Components/TaskProgress/taskProgress.scss +8 -0
  50. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +2 -2
  51. data/webpack/ForemanInventoryUpload/ForemanInventoryUploadReducers.js +0 -2
  52. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap +1 -1
  53. data/webpack/ForemanRhCloudPages.js +0 -1
  54. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +1 -2
  55. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +2 -4
  56. data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +14 -0
  57. data/webpack/__tests__/ForemanRhCloudHelpers.test.js +5 -1
  58. metadata +10 -61
  59. data/app/models/task_output_line.rb +0 -2
  60. data/app/models/task_output_status.rb +0 -2
  61. data/lib/foreman_inventory_upload/async/generate_report_job.rb +0 -61
  62. data/lib/foreman_inventory_upload/async/progress_output.rb +0 -38
  63. data/lib/foreman_inventory_upload/async/shell_process.rb +0 -77
  64. data/test/controllers/reports_controller_test.rb +0 -21
  65. data/test/controllers/uploads_controller_test.rb +0 -21
  66. data/test/jobs/generate_report_job_test.rb +0 -146
  67. data/test/unit/shell_process_job_test.rb +0 -29
  68. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardActions.js +0 -88
  69. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardConstants.js +0 -9
  70. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardReducer.js +0 -68
  71. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardSelectors.js +0 -17
  72. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardActions.test.js +0 -51
  73. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardIntegration.test.js +0 -17
  74. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardReducer.test.js +0 -64
  75. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardSelectors.test.js +0 -46
  76. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/Dashboard.test.js.snap +0 -36
  77. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardActions.test.js.snap +0 -76
  78. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardReducer.test.js.snap +0 -44
  79. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardSelectors.test.js.snap +0 -42
  80. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.fixtures.js +0 -0
  81. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.js +0 -55
  82. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModalHelper.js +0 -0
  83. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/FullScreenModal.test.js +0 -13
  84. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/__snapshots__/FullScreenModal.test.js.snap +0 -65
  85. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/fullScreenModal.scss +0 -20
  86. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/index.js +0 -1
  87. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.fixtures.js +0 -18
  88. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.js +0 -65
  89. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerateHelper.js +0 -0
  90. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/ReportGenerate.test.js +0 -14
  91. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/__snapshots__/ReportGenerate.test.js.snap +0 -47
  92. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/index.js +0 -1
  93. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/reportGenerate.scss +0 -0
  94. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.fixtures.js +0 -18
  95. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.js +0 -46
  96. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUploadHelper.js +0 -0
  97. data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/ReportUpload.test.js +0 -14
  98. data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/__snapshots__/ReportUpload.test.js.snap +0 -47
  99. data/webpack/ForemanInventoryUpload/Components/ReportUpload/index.js +0 -1
  100. data/webpack/ForemanInventoryUpload/Components/ReportUpload/reportUpload.scss +0 -0
  101. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.fixtures.js +0 -0
  102. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.js +0 -31
  103. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBodyHelper.js +0 -0
  104. data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/TabBody.test.js +0 -13
  105. data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/__snapshots__/TabBody.test.js.snap +0 -19
  106. data/webpack/ForemanInventoryUpload/Components/TabBody/index.js +0 -1
  107. data/webpack/ForemanInventoryUpload/Components/TabBody/tabBody.scss +0 -5
  108. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.fixtures.js +0 -10
  109. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.js +0 -110
  110. data/webpack/ForemanInventoryUpload/Components/Terminal/TerminalHelper.js +0 -6
  111. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/Terminal.test.js +0 -34
  112. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/__snapshots__/Terminal.test.js.snap +0 -98
  113. data/webpack/ForemanInventoryUpload/Components/Terminal/index.js +0 -1
  114. data/webpack/ForemanInventoryUpload/Components/Terminal/terminal.scss +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d818ad6b4142546fe8d3b8c0e54f49600ed30915f6b22f142bc1861938c5dba
4
- data.tar.gz: 3d0e310b2cf587243c64623713af21e164c87e87e21a5edeee556f16613ae8fc
3
+ metadata.gz: ae4a5664771227e232b4a38812dbf04fe704f1be85f10105873543cb2cc4b1c2
4
+ data.tar.gz: 69720759a2e2365af0b9c7e94c82e0f3adb15202d65ddf454e7beefa694cb30d
5
5
  SHA512:
6
- metadata.gz: ae35c83c6c45af99a891a3ff243d10f7f7616891facecab5f6afa34287f2282eb3c0175680537bb0a17b62352ea42b43d1e1d4365a517f6086c3e1d82fbd725b
7
- data.tar.gz: f9e14d6356e65f5649b3c4a2f57ef381539aa296e12fbbe7c0e5fcc28c47e855bd9ad8e1ab3a9ca739319b9c9882e41de9d578244be2f1fcbd83ca8d1c4160de
6
+ metadata.gz: f8c2edc1f86b1db29db4f8209d26fb3466795d1f31b7854cfd7dc826ae35f29b7557dc7388ebf0ab2a4217c9ae4399f765e75afe1c7278676ab6a25e28091d3b
7
+ data.tar.gz: 725d9f97783c10cbe2d69345ce8647c94f5fbf12d1cffd5e087f7a5cacc528423c253047ae3e97e0b3af307db1e995ee660848bbe839d44d989f4d5d746d5085
@@ -11,7 +11,14 @@ module InventoryUpload
11
11
  end
12
12
 
13
13
  def start_report_generation(organization_id, disconnected)
14
- ForemanTasks.async_task(ForemanInventoryUpload::Async::GenerateReportJob, ForemanInventoryUpload.generated_reports_folder, organization_id, disconnected)
14
+ upload = !disconnected
15
+ ForemanTasks.async_task(
16
+ ForemanInventoryUpload::Async::HostInventoryReportJob,
17
+ ForemanInventoryUpload.generated_reports_folder,
18
+ organization_id,
19
+ "", # hosts_filter
20
+ upload
21
+ )
15
22
  end
16
23
 
17
24
  def report_file(organization_id)
@@ -6,15 +6,20 @@ module ForemanInventoryUpload
6
6
 
7
7
  accounts = Hash[
8
8
  labels.map do |id, label|
9
- generate_report_status = status_for(id, ForemanInventoryUpload::Async::GenerateReportJob)
10
- upload_report_status = status_for(id, ForemanInventoryUpload::Async::UploadReportDirectJob)
9
+ generate_task = latest_task_for(id, ForemanInventoryUpload::Async::HostInventoryReportJob)
10
+
11
+ # Check sub-action completion status
12
+ generated_status = sub_action_status(generate_task, 'GenerateHostReport')
13
+ uploaded_status = sub_action_status(generate_task, 'UploadReportDirectJob')
14
+
11
15
  report_file_paths = ForemanInventoryUpload.report_file_paths(id)
12
16
 
13
17
  [
14
18
  label,
15
19
  {
16
- generate_report_status: generate_report_status,
17
- upload_report_status: upload_report_status,
20
+ generated_status: generated_status,
21
+ uploaded_status: uploaded_status,
22
+ generate_task: task_json(generate_task),
18
23
  report_file_paths: report_file_paths,
19
24
  id: id,
20
25
  },
@@ -30,9 +35,79 @@ module ForemanInventoryUpload
30
35
 
31
36
  private
32
37
 
33
- def status_for(label, job_class)
34
- label = job_class.output_label(label)
35
- ForemanInventoryUpload::Async::ProgressOutput.get(label)&.status
38
+ def controller_permission
39
+ 'foreman_rh_cloud'
40
+ end
41
+
42
+ def latest_task_for(org_id, job_class)
43
+ ForemanTasks::Task
44
+ .for_action_types([job_class.name])
45
+ .joins(:links)
46
+ .where(foreman_tasks_links: {
47
+ resource_type: 'Organization',
48
+ resource_id: org_id,
49
+ })
50
+ .with_duration
51
+ .order('started_at DESC')
52
+ .first
53
+ end
54
+
55
+ def sub_action_status(task, action_class_name)
56
+ return nil unless task
57
+
58
+ # If task is still running, return the state
59
+ return task.state unless task.state == 'stopped'
60
+
61
+ # For GenerateHostReport: always show status if task completed (generation always runs)
62
+ if action_class_name == 'GenerateHostReport'
63
+ return task.result
64
+ end
65
+
66
+ # For UploadReportDirectJob: only show status if task had upload enabled
67
+ if action_class_name == 'UploadReportDirectJob'
68
+ main_action = task.main_action
69
+ return nil unless main_action.respond_to?(:input)
70
+
71
+ # Check if upload was enabled for this task
72
+ return nil unless main_action.input[:upload]
73
+
74
+ # Return the task result
75
+ return task.result
76
+ end
77
+
78
+ nil
79
+ rescue StandardError => e
80
+ Rails.logger.warn("Failed to get sub-action status for #{action_class_name} in task #{task.id}: #{e.message}")
81
+ nil
82
+ end
83
+
84
+ def task_json(task)
85
+ return nil unless task
86
+
87
+ {
88
+ id: task.id,
89
+ label: task.label,
90
+ state: task.state,
91
+ result: task.result,
92
+ progress: task.progress,
93
+ started_at: task.started_at,
94
+ ended_at: task.ended_at,
95
+ duration: task.try(:duration)&.to_f,
96
+ report_file_path: task_report_file_path(task),
97
+ }
98
+ end
99
+
100
+ def task_report_file_path(task)
101
+ return nil unless task&.state == 'stopped'
102
+
103
+ # Get the main action from the task
104
+ main_action = task.main_action
105
+ return nil unless main_action.respond_to?(:report_file_path)
106
+
107
+ main_action.report_file_path
108
+ rescue StandardError => e
109
+ Rails.logger.warn("Failed to get report file path for task #{task.id}: #{e.message}")
110
+ nil
36
111
  end
37
112
  end
38
113
  end
@@ -0,0 +1,110 @@
1
+ module ForemanInventoryUpload
2
+ module Api
3
+ class TasksController < ::Api::V2::BaseController
4
+ ACTION_TYPES = [
5
+ 'ForemanInventoryUpload::Async::HostInventoryReportJob',
6
+ 'ForemanInventoryUpload::Async::UploadReportDirectJob',
7
+ ].freeze
8
+
9
+ # GET /foreman_inventory_upload/api/tasks/current
10
+ # Returns current running/active tasks for inventory operations
11
+ def current
12
+ organization_id = validate_organization_id if params[:organization_id].present?
13
+ return if performed?
14
+
15
+ tasks = ForemanTasks::Task
16
+ .active
17
+ .for_action_types(ACTION_TYPES)
18
+ .with_duration
19
+
20
+ if organization_id.present?
21
+ tasks = tasks.joins(:links)
22
+ .where(foreman_tasks_links: {
23
+ resource_type: 'Organization',
24
+ resource_id: organization_id,
25
+ })
26
+ end
27
+
28
+ render json: {
29
+ tasks: tasks.map { |task| task_json(task) },
30
+ }
31
+ end
32
+
33
+ # GET /foreman_inventory_upload/api/tasks/history
34
+ # Returns recent task history for inventory operations
35
+ def history
36
+ organization_id = validate_organization_id if params[:organization_id].present?
37
+ return if performed?
38
+
39
+ limit = validated_limit
40
+
41
+ tasks = ForemanTasks::Task
42
+ .for_action_types(ACTION_TYPES)
43
+ .with_duration
44
+ .order('started_at DESC')
45
+ .limit(limit)
46
+
47
+ if organization_id.present?
48
+ tasks = tasks.joins(:links)
49
+ .where(foreman_tasks_links: {
50
+ resource_type: 'Organization',
51
+ resource_id: organization_id,
52
+ })
53
+ end
54
+
55
+ render json: {
56
+ tasks: tasks.map { |task| task_json(task) },
57
+ }
58
+ end
59
+
60
+ private
61
+
62
+ def controller_permission
63
+ 'foreman_rh_cloud'
64
+ end
65
+
66
+ def task_json(task)
67
+ {
68
+ id: task.id,
69
+ label: task.label,
70
+ action: task.action,
71
+ state: task.state,
72
+ result: task.result,
73
+ progress: task.progress,
74
+ started_at: task.started_at,
75
+ ended_at: task.ended_at,
76
+ duration: task.duration&.to_f,
77
+ humanized: task.humanized,
78
+ report_file_path: task_report_file_path(task),
79
+ }
80
+ end
81
+
82
+ def task_report_file_path(task)
83
+ return nil unless task.state == 'stopped' && task.result == 'success'
84
+ return nil unless task.action == 'ForemanInventoryUpload::Async::HostInventoryReportJob'
85
+
86
+ task.main_action&.output&.[]('report_file')
87
+ end
88
+
89
+ def validate_organization_id
90
+ org_id = params[:organization_id].to_i
91
+ if org_id.zero?
92
+ render json: { message: 'Invalid organization_id parameter' }, status: :bad_request
93
+ return nil
94
+ end
95
+
96
+ begin
97
+ Organization.find(org_id).id
98
+ rescue ActiveRecord::RecordNotFound
99
+ render json: { message: "Organization with id #{org_id} not found" }, status: :not_found
100
+ nil
101
+ end
102
+ end
103
+
104
+ def validated_limit
105
+ limit = params[:limit]&.to_i || 10
106
+ limit.clamp(1, 100)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -4,30 +4,54 @@ module ForemanInventoryUpload
4
4
  class ReportsController < ::ApplicationController
5
5
  include InventoryUpload::ReportActions
6
6
 
7
- def last
8
- label = ForemanInventoryUpload::Async::GenerateReportJob.output_label(params[:organization_id])
9
- output = ForemanInventoryUpload::Async::ProgressOutput.get(label)&.full_output
10
- task_label = ForemanInventoryUpload::Async::GenerateAllReportsJob.name
11
- scheduled = ForemanTasks::Task.where(
12
- :label => task_label,
13
- :state => 'scheduled'
14
- ).first&.start_at || nil
7
+ def generate
8
+ organization_id = validate_organization_id
9
+ return if performed?
10
+
11
+ disconnected = boolean_param(:disconnected)
12
+
13
+ task = start_report_generation(organization_id, disconnected)
15
14
 
16
15
  render json: {
17
- output: output,
18
- scheduled: scheduled,
16
+ id: task.id,
17
+ humanized: {
18
+ action: task.action,
19
+ },
19
20
  }, status: :ok
20
21
  end
21
22
 
22
- def generate
23
- organization_id = params[:organization_id]
24
- disconnected = params[:disconnected]
23
+ private
25
24
 
26
- start_report_generation(organization_id, disconnected)
25
+ def controller_permission
26
+ 'foreman_rh_cloud'
27
+ end
27
28
 
28
- render json: {
29
- action_status: 'success',
30
- }, status: :ok
29
+ def action_permission
30
+ case params[:action]
31
+ when 'generate'
32
+ 'generate'
33
+ else
34
+ super
35
+ end
36
+ end
37
+
38
+ def validate_organization_id
39
+ org_id = params[:organization_id].to_i
40
+ if org_id.zero?
41
+ render json: { message: 'Invalid organization_id parameter' }, status: :bad_request
42
+ return nil
43
+ end
44
+
45
+ begin
46
+ Organization.find(org_id).id
47
+ rescue ActiveRecord::RecordNotFound
48
+ render json: { message: "Organization with id #{org_id} not found" }, status: :not_found
49
+ nil
50
+ end
51
+ end
52
+
53
+ def boolean_param(key)
54
+ Foreman::Cast.to_bool(params[key]) || false
31
55
  end
32
56
  end
33
57
  end
@@ -5,15 +5,6 @@ module ForemanInventoryUpload
5
5
 
6
6
  before_action :require_non_iop_smart_proxy, only: [:enable_cloud_connector]
7
7
 
8
- def last
9
- label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(params[:organization_id])
10
- output = ForemanInventoryUpload::Async::ProgressOutput.get(label)&.full_output
11
-
12
- render json: {
13
- output: output,
14
- }, status: :ok
15
- end
16
-
17
8
  def download_file
18
9
  filename, file = report_file(params[:organization_id])
19
10
 
data/config/routes.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  Rails.application.routes.draw do
2
2
  namespace :foreman_inventory_upload do
3
- get ':organization_id/reports/last', to: 'reports#last', constraints: { organization_id: %r{[^\/]+} }
4
3
  post ':organization_id/reports', to: 'reports#generate', constraints: { organization_id: %r{[^\/]+} }
5
- get ':organization_id/uploads/last', to: 'uploads#last', constraints: { organization_id: %r{[^\/]+} }
6
4
  get ':organization_id/uploads/file', to: 'uploads#download_file', constraints: { organization_id: %r{[^\/]+} }
7
5
  get 'missing_hosts', to: 'missing_hosts#index'
8
6
  get 'accounts', to: 'accounts#index'
@@ -76,6 +74,10 @@ Rails.application.routes.draw do
76
74
  post 'enable_connector', to: 'inventory#enable_cloud_connector'
77
75
  post 'cloud_request', to: 'cloud_request#update'
78
76
  get 'advisor_engine_config', to: 'advisor_engine_config#show'
77
+
78
+ # Inventory upload task endpoints
79
+ get 'inventory_upload/tasks/current', to: 'foreman_inventory_upload/api/tasks#current'
80
+ get 'inventory_upload/tasks/history', to: 'foreman_inventory_upload/api/tasks#history'
79
81
  end
80
82
 
81
83
  namespace 'advisor_engine' do
@@ -0,0 +1,24 @@
1
+ class DropTaskOutputTables < ActiveRecord::Migration[6.0]
2
+ def up
3
+ drop_table :task_output_lines
4
+ drop_table :task_output_statuses
5
+ end
6
+
7
+ def down
8
+ create_table :task_output_lines do |t|
9
+ t.string :label
10
+ t.string :line
11
+ t.timestamps
12
+
13
+ t.index :label
14
+ end
15
+
16
+ create_table :task_output_statuses do |t|
17
+ t.string :label
18
+ t.string :status
19
+ t.timestamps
20
+
21
+ t.index :label, unique: true
22
+ end
23
+ end
24
+ end
@@ -42,7 +42,7 @@ module ForemanInventoryUpload
42
42
  end
43
43
 
44
44
  def plan_generate_report(folder, organization, disconnected)
45
- plan_action(ForemanInventoryUpload::Async::GenerateReportJob, folder, organization.id, disconnected)
45
+ plan_action(ForemanInventoryUpload::Async::HostInventoryReportJob, folder, organization.id, '', !disconnected)
46
46
  end
47
47
 
48
48
  def logger
@@ -1,7 +1,21 @@
1
1
  module ForemanInventoryUpload
2
2
  module Async
3
3
  class HostInventoryReportJob < ::Actions::EntryAction
4
+ def resource_locks
5
+ :link
6
+ end
7
+
4
8
  def plan(base_folder, organization_id, hosts_filter = "", upload = true)
9
+ organization = Organization.find(organization_id)
10
+ action_subject(organization)
11
+
12
+ plan_self(
13
+ base_folder: base_folder,
14
+ organization_id: organization_id,
15
+ hosts_filter: hosts_filter,
16
+ upload: upload
17
+ )
18
+
5
19
  sequence do
6
20
  plan_action(
7
21
  GenerateHostReport,
@@ -31,9 +45,34 @@ module ForemanInventoryUpload
31
45
  _("Host inventory report job")
32
46
  end
33
47
 
48
+ def organization
49
+ Organization.find(input[:organization_id])
50
+ end
51
+
34
52
  def organization_id
35
53
  input[:organization_id]
36
54
  end
55
+
56
+ def report_file_path
57
+ filename = ForemanInventoryUpload.facts_archive_name(input[:organization_id], input[:hosts_filter])
58
+
59
+ if input[:upload]
60
+ # For upload tasks, check: done folder (uploaded), uploads folder (queued), generated_reports folder (failed upload)
61
+ [
62
+ ForemanInventoryUpload.done_file_path(filename),
63
+ ForemanInventoryUpload.uploads_file_path(filename),
64
+ File.join(input[:base_folder], filename),
65
+ ].find { |path| File.exist?(path) }
66
+ else
67
+ # For generate-only tasks, only check generated_reports folder
68
+ generated_path = File.join(input[:base_folder], filename)
69
+ File.exist?(generated_path) ? generated_path : nil
70
+ end
71
+ end
72
+
73
+ def rescue_strategy_for_self
74
+ Dynflow::Action::Rescue::Fail
75
+ end
37
76
  end
38
77
  end
39
78
  end
@@ -42,16 +42,15 @@ module ForemanInventoryUpload
42
42
  "upload_for_#{label}"
43
43
  end
44
44
 
45
+ def resource_locks
46
+ :link
47
+ end
48
+
45
49
  def plan(filename, organization_id)
46
- # NOTE: This implementation assumes a single organization will not trigger multiple
47
- # concurrent uploads. The instance_label is derived from organization_id alone, which
48
- # means concurrent uploads for the same org would share ProgressOutput storage.
49
- # This matches the pattern in GenerateReportJob. A full fix for thread-safety
50
- # requires UI changes to display multiple concurrent tasks per org (tracked for PR #2).
51
- label = UploadReportDirectJob.output_label(organization_id)
52
- clear_task_output(label)
50
+ organization = Organization.find(organization_id)
51
+ action_subject(organization)
52
+
53
53
  plan_self(
54
- instance_label: label,
55
54
  filename: filename,
56
55
  organization_id: organization_id
57
56
  )
@@ -59,21 +58,12 @@ module ForemanInventoryUpload
59
58
 
60
59
  def try_execute
61
60
  if content_disconnected?
62
- progress_output do |progress_output|
63
- progress_output.write_line("Report was not moved and upload was canceled because connection to Insights is not enabled. Report location: #{filename}.")
64
- progress_output.status = "Task aborted, exit 1"
65
- done!
66
- end
61
+ logger.info("Upload canceled: connection to Insights is not enabled. Report location: #{filename}")
67
62
  return
68
63
  end
69
64
 
70
65
  unless organization.owner_details&.dig('upstreamConsumer', 'idCert')
71
66
  logger.info("Skipping organization '#{organization}', no candlepin certificate defined.")
72
- progress_output do |progress_output|
73
- progress_output.write_line("Skipping organization #{organization}, no candlepin certificate defined.")
74
- progress_output.status = "Task aborted, exit 1"
75
- done!
76
- end
77
67
  return
78
68
  end
79
69
 
@@ -81,31 +71,13 @@ module ForemanInventoryUpload
81
71
  cer_file.write(certificate[:cert])
82
72
  cer_file.write(certificate[:key])
83
73
  cer_file.flush
84
- upload_report(cer_file.path)
74
+ upload_file(cer_file.path)
85
75
  end
86
76
 
77
+ move_to_done_folder
87
78
  done!
88
79
  end
89
80
 
90
- def upload_report(cer_path)
91
- progress_output do |progress_output|
92
- progress_output.write_line("Uploading report for organization #{organization.label}...")
93
- progress_output.status = "Running upload"
94
-
95
- begin
96
- upload_file(cer_path)
97
- progress_output.write_line("Upload completed successfully")
98
- move_to_done_folder
99
- progress_output.write_line("Uploaded file moved to done/ folder")
100
- progress_output.status = "pid #{Process.pid} exit 0"
101
- rescue StandardError => e
102
- progress_output.write_line("Upload failed: #{e.message}")
103
- progress_output.status = "pid #{Process.pid} exit 1"
104
- raise
105
- end
106
- end
107
- end
108
-
109
81
  def upload_file(cer_path)
110
82
  cert_content = File.read(cer_path)
111
83
 
@@ -137,7 +109,10 @@ module ForemanInventoryUpload
137
109
  def move_to_done_folder
138
110
  FileUtils.mkdir_p(ForemanInventoryUpload.done_folder)
139
111
  done_file = ForemanInventoryUpload.done_file_path(File.basename(filename))
140
- FileUtils.mv(filename, done_file)
112
+ if File.exist?(done_file)
113
+ logger.warn("Destination file #{done_file} already exists. Overwriting with new report.")
114
+ end
115
+ FileUtils.mv(filename, done_file, force: true)
141
116
  logger.debug("Moved #{filename} to #{done_file}")
142
117
  end
143
118
 
@@ -154,9 +129,20 @@ module ForemanInventoryUpload
154
129
  end
155
130
 
156
131
  def foreman_certificate
132
+ cert_path = Setting[:ssl_certificate]
133
+ key_path = Setting[:ssl_priv_key]
134
+
135
+ unless cert_path && File.readable?(cert_path)
136
+ raise "SSL certificate file not found or not readable: #{cert_path}"
137
+ end
138
+
139
+ unless key_path && File.readable?(key_path)
140
+ raise "SSL private key file not found or not readable: #{key_path}"
141
+ end
142
+
157
143
  {
158
- cert: File.read(Setting[:ssl_certificate]),
159
- key: File.read(Setting[:ssl_priv_key]),
144
+ cert: File.read(cert_path),
145
+ key: File.read(key_path),
160
146
  }
161
147
  end
162
148
 
@@ -169,20 +155,10 @@ module ForemanInventoryUpload
169
155
  end
170
156
 
171
157
  def content_disconnected?
158
+ return false if ForemanRhCloud.with_iop_smart_proxy?
172
159
  !Setting[:subscription_connection_enabled]
173
160
  end
174
161
 
175
- def progress_output
176
- progress_output = ProgressOutput.register(instance_label)
177
- yield(progress_output)
178
- ensure
179
- progress_output.close
180
- end
181
-
182
- def instance_label
183
- input[:instance_label]
184
- end
185
-
186
162
  def logger
187
163
  Foreman::Logging.logger('background')
188
164
  end
@@ -190,11 +166,6 @@ module ForemanInventoryUpload
190
166
  def rescue_strategy_for_self
191
167
  Dynflow::Action::Rescue::Fail
192
168
  end
193
-
194
- def clear_task_output(label)
195
- TaskOutputLine.where(label: label).delete_all
196
- TaskOutputStatus.where(label: label).delete_all
197
- end
198
169
  end
199
170
  end
200
171
  end
@@ -44,6 +44,7 @@ module ForemanRhCloud
44
44
  'foreman_inventory_upload/reports': [:last],
45
45
  'foreman_inventory_upload/uploads': [:auto_upload, :show_auto_upload, :download_file, :last],
46
46
  'foreman_inventory_upload/tasks': [:show],
47
+ 'foreman_inventory_upload/api/tasks': [:current, :history],
47
48
  'foreman_inventory_upload/cloud_status': [:index],
48
49
  'foreman_inventory_upload/uploads_settings': [:index],
49
50
  'foreman_inventory_upload/missing_hosts': [:index],
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '13.0.9'.freeze
2
+ VERSION = '13.0.10'.freeze
3
3
  end
@@ -12,9 +12,11 @@ namespace :rh_cloud_inventory do
12
12
  User.as_anonymous_admin do
13
13
  organizations.each do |organization|
14
14
  ForemanTasks.async_task(
15
- ForemanInventoryUpload::Async::GenerateReportJob,
15
+ ForemanInventoryUpload::Async::HostInventoryReportJob,
16
16
  ForemanInventoryUpload.generated_reports_folder,
17
- organization.id
17
+ organization.id,
18
+ '', # hosts_filter
19
+ true # upload
18
20
  )
19
21
  puts "Generated and uploaded inventory report for organization '#{organization.name}'"
20
22
  end
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "13.0.9",
3
+ "version": "13.0.10",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -5,22 +5,21 @@ class AccountsControllerTest < ActionController::TestCase
5
5
 
6
6
  include FolderIsolation
7
7
 
8
- test 'Returns statuses for each process type' do
8
+ test 'Returns statuses for each organization' do
9
9
  test_org = FactoryBot.create(:organization)
10
10
 
11
- generate_label = ForemanInventoryUpload::Async::GenerateReportJob.output_label(test_org.id)
12
- generate_output = ForemanInventoryUpload::Async::ProgressOutput.register(generate_label)
13
- generate_output.status = 'generate_status_test'
14
- upload_label = ForemanInventoryUpload::Async::UploadReportDirectJob.output_label(test_org.id)
15
- upload_output = ForemanInventoryUpload::Async::ProgressOutput.register(upload_label)
16
- upload_output.status = 'upload_status_test'
17
-
18
11
  get :index, session: set_session_user
19
12
 
20
13
  assert_response :success
21
14
  actual = JSON.parse(response.body)
22
- actual_account_statuses = actual['accounts'][test_org.label]
23
- assert_equal 'generate_status_test', actual_account_statuses['generate_report_status']
24
- assert_equal 'upload_status_test', actual_account_statuses['upload_report_status']
15
+ assert actual['accounts'].key?(test_org.name)
16
+ actual_account = actual['accounts'][test_org.name]
17
+
18
+ # Verify the response structure
19
+ assert_includes actual_account.keys, 'generated_status'
20
+ assert_includes actual_account.keys, 'uploaded_status'
21
+ assert_includes actual_account.keys, 'generate_task'
22
+ assert_includes actual_account.keys, 'report_file_paths'
23
+ assert_equal test_org.id, actual_account['id']
25
24
  end
26
25
  end