foreman_rh_cloud 13.0.8 → 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 (130) 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/queue_for_upload_job.rb +1 -23
  12. data/lib/foreman_inventory_upload/async/upload_report_direct_job.rb +171 -0
  13. data/lib/foreman_inventory_upload.rb +0 -4
  14. data/lib/foreman_rh_cloud/plugin.rb +1 -0
  15. data/lib/foreman_rh_cloud/version.rb +1 -1
  16. data/lib/inventory_sync/async/inventory_hosts_sync.rb +0 -2
  17. data/lib/tasks/rh_cloud_inventory.rake +4 -2
  18. data/package.json +1 -1
  19. data/test/controllers/accounts_controller_test.rb +10 -11
  20. data/test/controllers/insights_cloud/api/cloud_request_controller_test.rb +1 -2
  21. data/test/jobs/host_inventory_report_job_test.rb +161 -97
  22. data/test/jobs/queue_for_upload_job_test.rb +1 -12
  23. data/test/jobs/single_host_report_job_test.rb +36 -54
  24. data/test/jobs/upload_report_direct_job_test.rb +399 -0
  25. data/test/unit/rh_cloud_permissions_test.rb +2 -0
  26. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js +6 -6
  27. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js +49 -34
  28. data/webpack/ForemanInventoryUpload/Components/AccountList/AccountListActions.js +2 -2
  29. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.fixtures.js +4 -5
  30. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/ListItem.js +15 -7
  31. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItem/__tests__/__snapshots__/ListItem.test.js.snap +11 -11
  32. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.fixtures.js +2 -2
  33. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatus.js +10 -14
  34. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/ListItemStatusHelper.js +9 -4
  35. data/webpack/ForemanInventoryUpload/Components/AccountList/Components/ListItemStatus/__tests__/__snapshots__/ListItemStatus.test.js.snap +4 -4
  36. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountList.test.js.snap +15 -9
  37. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListActions.test.js.snap +7 -7
  38. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListReducer.test.js.snap +6 -6
  39. data/webpack/ForemanInventoryUpload/Components/AccountList/__tests__/__snapshots__/AccountListSelectors.test.js.snap +12 -12
  40. data/webpack/ForemanInventoryUpload/Components/Dashboard/Dashboard.js +37 -130
  41. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/Dashboard.test.js +60 -17
  42. data/webpack/ForemanInventoryUpload/Components/Dashboard/index.js +1 -34
  43. data/webpack/ForemanInventoryUpload/Components/InventoryFilter/__tests__/__snapshots__/integration.test.js.snap +0 -1
  44. data/webpack/ForemanInventoryUpload/Components/NavContainer/NavContainer.js +1 -26
  45. data/webpack/ForemanInventoryUpload/Components/PageHeader/PageHeader.js +24 -17
  46. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/PageHeader.test.js +178 -8
  47. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageTitle.test.js.snap +2 -2
  48. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/ToolbarButtons.js +3 -1
  49. data/webpack/ForemanInventoryUpload/Components/PageHeader/components/ToolbarButtons/__tests__/ToolbarButtons.test.js +69 -51
  50. data/webpack/ForemanInventoryUpload/Components/TabHeader/TabHeader.js +22 -9
  51. data/webpack/ForemanInventoryUpload/Components/TabHeader/__tests__/TabHeader.test.js +67 -4
  52. data/webpack/ForemanInventoryUpload/Components/TaskHistory/TaskHistory.js +140 -0
  53. data/webpack/ForemanInventoryUpload/Components/TaskHistory/index.js +1 -0
  54. data/webpack/ForemanInventoryUpload/Components/TaskHistory/taskHistory.scss +40 -0
  55. data/webpack/ForemanInventoryUpload/Components/TaskProgress/TaskProgress.js +340 -0
  56. data/webpack/ForemanInventoryUpload/Components/TaskProgress/index.js +1 -0
  57. data/webpack/ForemanInventoryUpload/Components/TaskProgress/taskProgress.scss +8 -0
  58. data/webpack/ForemanInventoryUpload/ForemanInventoryHelpers.js +2 -2
  59. data/webpack/ForemanInventoryUpload/ForemanInventoryUploadReducers.js +0 -2
  60. data/webpack/ForemanInventoryUpload/__tests__/__snapshots__/ForemanInventoryHelpers.test.js.snap +1 -1
  61. data/webpack/ForemanRhCloudPages.js +0 -1
  62. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/InsightsTable.test.js +11 -19
  63. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationHelpers.js +1 -2
  64. data/webpack/InsightsCloudSync/Components/RemediationModal/RemediationModal.js +2 -4
  65. data/webpack/InsightsVulnerabilityHostIndexExtensions/__tests__/CVECountCell.test.js +77 -22
  66. data/webpack/__mocks__/foremanReact/components/common/dates/RelativeDateTime.js +14 -0
  67. data/webpack/__tests__/ForemanRhCloudHelpers.test.js +5 -1
  68. metadata +13 -68
  69. data/app/models/task_output_line.rb +0 -2
  70. data/app/models/task_output_status.rb +0 -2
  71. data/lib/foreman_inventory_upload/async/generate_report_job.rb +0 -61
  72. data/lib/foreman_inventory_upload/async/progress_output.rb +0 -38
  73. data/lib/foreman_inventory_upload/async/shell_process.rb +0 -77
  74. data/lib/foreman_inventory_upload/async/upload_report_job.rb +0 -97
  75. data/lib/foreman_inventory_upload/scripts/uploader.sh.erb +0 -55
  76. data/test/controllers/reports_controller_test.rb +0 -21
  77. data/test/controllers/uploads_controller_test.rb +0 -21
  78. data/test/jobs/generate_report_job_test.rb +0 -146
  79. data/test/jobs/upload_report_job_test.rb +0 -38
  80. data/test/unit/shell_process_job_test.rb +0 -29
  81. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardActions.js +0 -88
  82. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardConstants.js +0 -9
  83. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardReducer.js +0 -68
  84. data/webpack/ForemanInventoryUpload/Components/Dashboard/DashboardSelectors.js +0 -17
  85. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardActions.test.js +0 -51
  86. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardIntegration.test.js +0 -17
  87. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardReducer.test.js +0 -64
  88. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/DashboardSelectors.test.js +0 -46
  89. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/Dashboard.test.js.snap +0 -36
  90. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardActions.test.js.snap +0 -76
  91. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardReducer.test.js.snap +0 -44
  92. data/webpack/ForemanInventoryUpload/Components/Dashboard/__tests__/__snapshots__/DashboardSelectors.test.js.snap +0 -42
  93. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.fixtures.js +0 -0
  94. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModal.js +0 -55
  95. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/FullScreenModalHelper.js +0 -0
  96. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/FullScreenModal.test.js +0 -13
  97. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/__tests__/__snapshots__/FullScreenModal.test.js.snap +0 -65
  98. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/fullScreenModal.scss +0 -20
  99. data/webpack/ForemanInventoryUpload/Components/FullScreenModal/index.js +0 -1
  100. data/webpack/ForemanInventoryUpload/Components/PageHeader/__tests__/__snapshots__/PageHeader.test.js.snap +0 -36
  101. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.fixtures.js +0 -18
  102. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerate.js +0 -65
  103. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/ReportGenerateHelper.js +0 -0
  104. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/ReportGenerate.test.js +0 -14
  105. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/__tests__/__snapshots__/ReportGenerate.test.js.snap +0 -47
  106. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/index.js +0 -1
  107. data/webpack/ForemanInventoryUpload/Components/ReportGenerate/reportGenerate.scss +0 -0
  108. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.fixtures.js +0 -18
  109. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUpload.js +0 -46
  110. data/webpack/ForemanInventoryUpload/Components/ReportUpload/ReportUploadHelper.js +0 -0
  111. data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/ReportUpload.test.js +0 -14
  112. data/webpack/ForemanInventoryUpload/Components/ReportUpload/__tests__/__snapshots__/ReportUpload.test.js.snap +0 -47
  113. data/webpack/ForemanInventoryUpload/Components/ReportUpload/index.js +0 -1
  114. data/webpack/ForemanInventoryUpload/Components/ReportUpload/reportUpload.scss +0 -0
  115. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.fixtures.js +0 -0
  116. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBody.js +0 -31
  117. data/webpack/ForemanInventoryUpload/Components/TabBody/TabBodyHelper.js +0 -0
  118. data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/TabBody.test.js +0 -13
  119. data/webpack/ForemanInventoryUpload/Components/TabBody/__tests__/__snapshots__/TabBody.test.js.snap +0 -19
  120. data/webpack/ForemanInventoryUpload/Components/TabBody/index.js +0 -1
  121. data/webpack/ForemanInventoryUpload/Components/TabBody/tabBody.scss +0 -5
  122. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.fixtures.js +0 -10
  123. data/webpack/ForemanInventoryUpload/Components/Terminal/Terminal.js +0 -110
  124. data/webpack/ForemanInventoryUpload/Components/Terminal/TerminalHelper.js +0 -6
  125. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/Terminal.test.js +0 -34
  126. data/webpack/ForemanInventoryUpload/Components/Terminal/__tests__/__snapshots__/Terminal.test.js.snap +0 -98
  127. data/webpack/ForemanInventoryUpload/Components/Terminal/index.js +0 -1
  128. data/webpack/ForemanInventoryUpload/Components/Terminal/terminal.scss +0 -32
  129. data/webpack/InsightsCloudSync/Components/InsightsTable/__tests__/__snapshots__/InsightsTable.test.js.snap +0 -112
  130. data/webpack/__mocks__/foremanReact/common/hooks/API/APIHooks.js +0 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: aba47c451526fd85b565ba8fdcb17d6b7d643cd3c6fbea186a40943a6a4887bc
4
- data.tar.gz: 16357a50048143a026497016cd38533b34d211c6ea61fccf55bd2a298852766e
3
+ metadata.gz: ae4a5664771227e232b4a38812dbf04fe704f1be85f10105873543cb2cc4b1c2
4
+ data.tar.gz: 69720759a2e2365af0b9c7e94c82e0f3adb15202d65ddf454e7beefa694cb30d
5
5
  SHA512:
6
- metadata.gz: c863fed43a17edb0cfdb465e4b26b529e9a09460ab9677cea6e94bc3fea6c560c8fdf01c691ea4ea2d495c7ab715db90cbee5c3bf87217e3aae135c9b29b5e2f
7
- data.tar.gz: c1d61f3f092e87829a28fd1ac237f46c29772aeae926d2a3c65cba440d3233844aa8dd40eb38a18ff56a09c68f9578aaf83a142d37d1aff06679d0ba6b87c904
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::UploadReportJob)
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::UploadReportJob.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
@@ -9,7 +9,6 @@ module ForemanInventoryUpload
9
9
  def run
10
10
  logger.debug('Ensuring objects')
11
11
  ensure_ouput_folder
12
- ensure_output_script
13
12
  logger.debug("Copying #{report_file} to #{uploads_folder}")
14
13
  enqueued_file_name = File.join(uploads_folder, report_file)
15
14
  FileUtils.mv(File.join(base_folder, report_file), enqueued_file_name)
@@ -22,31 +21,10 @@ module ForemanInventoryUpload
22
21
  @uploads_folder ||= ForemanInventoryUpload.uploads_folder
23
22
  end
24
23
 
25
- def script_file
26
- @script_file ||= File.join(uploads_folder, ForemanInventoryUpload.upload_script_file)
27
- end
28
-
29
24
  def ensure_ouput_folder
30
25
  FileUtils.mkdir_p(uploads_folder)
31
26
  end
32
27
 
33
- def ensure_output_script
34
- return if File.exist?(script_file)
35
-
36
- script_source = File.join(ForemanRhCloud::Engine.root, 'lib/foreman_inventory_upload/scripts/uploader.sh.erb')
37
-
38
- template_src = Foreman::Renderer::Source::String.new(content: File.read(script_source))
39
- scope = Foreman::Renderer::Scope::Base.new(
40
- source: template_src,
41
- variables: {
42
- upload_url: ForemanInventoryUpload.upload_url,
43
- }
44
- )
45
- script_source = Foreman::Renderer.render(template_src, scope)
46
- File.write(script_file, script_source)
47
- FileUtils.chmod('+x', script_file)
48
- end
49
-
50
28
  def logger
51
29
  Foreman::Logging.logger('background')
52
30
  end
@@ -60,7 +38,7 @@ module ForemanInventoryUpload
60
38
  end
61
39
 
62
40
  def plan_upload_report(enqueued_file_name, organization_id)
63
- plan_action(UploadReportJob, enqueued_file_name, organization_id)
41
+ plan_action(UploadReportDirectJob, enqueued_file_name, organization_id)
64
42
  end
65
43
  end
66
44
  end
@@ -0,0 +1,171 @@
1
+ require 'tempfile'
2
+ require 'rest-client'
3
+
4
+ module ForemanInventoryUpload
5
+ module Async
6
+ class UploadReportDirectJob < ::Actions::EntryAction
7
+ include AsyncHelpers
8
+ include ::ForemanRhCloud::Async::ExponentialBackoff
9
+ include ::ForemanRhCloud::CloudRequest
10
+
11
+ # Wrapper class to avoid monkey-patching File for multipart uploads
12
+ class FileUpload
13
+ attr_reader :file, :content_type
14
+
15
+ def initialize(file, content_type:)
16
+ @file = file
17
+ @content_type = content_type
18
+ end
19
+
20
+ def read(*args)
21
+ @file.read(*args)
22
+ end
23
+
24
+ def path
25
+ @file.path
26
+ end
27
+
28
+ def respond_to_missing?(method_name, include_private = false)
29
+ @file.respond_to?(method_name, include_private) || super
30
+ end
31
+
32
+ def method_missing(method_name, *args, &block)
33
+ if @file.respond_to?(method_name)
34
+ @file.send(method_name, *args, &block)
35
+ else
36
+ super
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.output_label(label)
42
+ "upload_for_#{label}"
43
+ end
44
+
45
+ def resource_locks
46
+ :link
47
+ end
48
+
49
+ def plan(filename, organization_id)
50
+ organization = Organization.find(organization_id)
51
+ action_subject(organization)
52
+
53
+ plan_self(
54
+ filename: filename,
55
+ organization_id: organization_id
56
+ )
57
+ end
58
+
59
+ def try_execute
60
+ if content_disconnected?
61
+ logger.info("Upload canceled: connection to Insights is not enabled. Report location: #{filename}")
62
+ return
63
+ end
64
+
65
+ unless organization.owner_details&.dig('upstreamConsumer', 'idCert')
66
+ logger.info("Skipping organization '#{organization}', no candlepin certificate defined.")
67
+ return
68
+ end
69
+
70
+ Tempfile.create([organization.name, '.pem']) do |cer_file|
71
+ cer_file.write(certificate[:cert])
72
+ cer_file.write(certificate[:key])
73
+ cer_file.flush
74
+ upload_file(cer_file.path)
75
+ end
76
+
77
+ move_to_done_folder
78
+ done!
79
+ end
80
+
81
+ def upload_file(cer_path)
82
+ cert_content = File.read(cer_path)
83
+
84
+ File.open(filename, 'rb') do |file|
85
+ # Wrap file with FileUpload class for RestClient multipart handling
86
+ # RestClient requires objects with :read, :path, and :content_type methods
87
+ wrapped_file = FileUpload.new(file, content_type: 'application/vnd.redhat.qpc.tar+tgz')
88
+
89
+ response = execute_cloud_request(
90
+ method: :post,
91
+ url: ForemanInventoryUpload.upload_url,
92
+ payload: {
93
+ multipart: true,
94
+ file: wrapped_file,
95
+ },
96
+ headers: {
97
+ 'X-Org-Id' => organization.label,
98
+ },
99
+ ssl_client_cert: OpenSSL::X509::Certificate.new(cert_content),
100
+ ssl_client_key: OpenSSL::PKey::RSA.new(cert_content),
101
+ timeout: 600,
102
+ open_timeout: 60
103
+ )
104
+
105
+ logger.debug("Upload response code: #{response.code}")
106
+ end
107
+ end
108
+
109
+ def move_to_done_folder
110
+ FileUtils.mkdir_p(ForemanInventoryUpload.done_folder)
111
+ done_file = ForemanInventoryUpload.done_file_path(File.basename(filename))
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)
116
+ logger.debug("Moved #{filename} to #{done_file}")
117
+ end
118
+
119
+ def certificate
120
+ ForemanRhCloud.with_iop_smart_proxy? ? foreman_certificate : manifest_certificate
121
+ end
122
+
123
+ def manifest_certificate
124
+ candlepin_id_certificate = organization.owner_details['upstreamConsumer']['idCert']
125
+ {
126
+ cert: candlepin_id_certificate['cert'],
127
+ key: candlepin_id_certificate['key'],
128
+ }
129
+ end
130
+
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
+
143
+ {
144
+ cert: File.read(cert_path),
145
+ key: File.read(key_path),
146
+ }
147
+ end
148
+
149
+ def filename
150
+ input[:filename]
151
+ end
152
+
153
+ def organization
154
+ Organization.find(input[:organization_id])
155
+ end
156
+
157
+ def content_disconnected?
158
+ return false if ForemanRhCloud.with_iop_smart_proxy?
159
+ !Setting[:subscription_connection_enabled]
160
+ end
161
+
162
+ def logger
163
+ Foreman::Logging.logger('background')
164
+ end
165
+
166
+ def rescue_strategy_for_self
167
+ Dynflow::Action::Rescue::Fail
168
+ end
169
+ end
170
+ end
171
+ end
@@ -48,10 +48,6 @@ module ForemanInventoryUpload
48
48
  @outputs_folder ||= ensure_folder(File.join(ForemanInventoryUpload.base_folder, 'outputs/'))
49
49
  end
50
50
 
51
- def self.upload_script_file
52
- 'uploader.sh'
53
- end
54
-
55
51
  def self.facts_archive_name(organization, filter = nil)
56
52
  "report_for_#{organization}#{filter.empty? ? nil : "[#{filter.to_s.parameterize}]"}.tar.xz"
57
53
  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],