Class: ForemanInventoryUpload::Async::UploadReportDirectJob

Inherits:
Actions::EntryAction
  • Object
show all
Includes:
AsyncHelpers, ForemanRhCloud::Async::ExponentialBackoff, ForemanRhCloud::CloudRequest
Defined in:
lib/foreman_inventory_upload/async/upload_report_direct_job.rb

Defined Under Namespace

Classes: FileUpload

Class Method Summary collapse

Instance Method Summary collapse

Methods included from ForemanRhCloud::CloudRequest

#execute_cloud_request

Methods included from ForemanRhCloud::Async::ExponentialBackoff

#attempts_before_next_interval, #done!, #done?, #invoke_external_task, #poll_external_task, #poll_intervals

Methods included from AsyncHelpers

#hash_to_s

Class Method Details

.output_label(label) ⇒ Object



41
42
43
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 41

def self.output_label(label)
  "upload_for_#{label}"
end

Instance Method Details

#certificateObject



144
145
146
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 144

def certificate
  ForemanRhCloud.with_iop_smart_proxy? ? foreman_certificate : manifest_certificate
end

#clear_task_output(label) ⇒ Object



194
195
196
197
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 194

def clear_task_output(label)
  TaskOutputLine.where(label: label).delete_all
  TaskOutputStatus.where(label: label).delete_all
end

#content_disconnected?Boolean

Returns:

  • (Boolean)


171
172
173
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 171

def content_disconnected?
  !Setting[:subscription_connection_enabled]
end

#filenameObject



163
164
165
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 163

def filename
  input[:filename]
end

#foreman_certificateObject



156
157
158
159
160
161
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 156

def foreman_certificate
  {
    cert: File.read(Setting[:ssl_certificate]),
    key: File.read(Setting[:ssl_priv_key]),
  }
end

#instance_labelObject



182
183
184
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 182

def instance_label
  input[:instance_label]
end

#loggerObject



186
187
188
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 186

def logger
  Foreman::Logging.logger('background')
end

#manifest_certificateObject



148
149
150
151
152
153
154
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 148

def manifest_certificate
  candlepin_id_certificate = organization.owner_details['upstreamConsumer']['idCert']
  {
    cert: candlepin_id_certificate['cert'],
    key: candlepin_id_certificate['key'],
  }
end

#move_to_done_folderObject



137
138
139
140
141
142
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 137

def move_to_done_folder
  FileUtils.mkdir_p(ForemanInventoryUpload.done_folder)
  done_file = ForemanInventoryUpload.done_file_path(File.basename(filename))
  FileUtils.mv(filename, done_file)
  logger.debug("Moved #{filename} to #{done_file}")
end

#organizationObject



167
168
169
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 167

def organization
  Organization.find(input[:organization_id])
end

#plan(filename, organization_id) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 45

def plan(filename, organization_id)
  # NOTE: This implementation assumes a single organization will not trigger multiple
  # concurrent uploads. The instance_label is derived from organization_id alone, which
  # means concurrent uploads for the same org would share ProgressOutput storage.
  # This matches the pattern in GenerateReportJob. A full fix for thread-safety
  # requires UI changes to display multiple concurrent tasks per org (tracked for PR #2).
  label = UploadReportDirectJob.output_label(organization_id)
  clear_task_output(label)
  plan_self(
    instance_label: label,
    filename: filename,
    organization_id: organization_id
  )
end

#progress_outputObject



175
176
177
178
179
180
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 175

def progress_output
  progress_output = ProgressOutput.register(instance_label)
  yield(progress_output)
ensure
  progress_output.close
end

#rescue_strategy_for_selfObject



190
191
192
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 190

def rescue_strategy_for_self
  Dynflow::Action::Rescue::Fail
end

#try_executeObject



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 60

def try_execute
  if content_disconnected?
    progress_output do |progress_output|
      progress_output.write_line("Report was not moved and upload was canceled because connection to Insights is not enabled. Report location: #{filename}.")
      progress_output.status = "Task aborted, exit 1"
      done!
    end
    return
  end

  unless organization.owner_details&.dig('upstreamConsumer', 'idCert')
    logger.info("Skipping organization '#{organization}', no candlepin certificate defined.")
    progress_output do |progress_output|
      progress_output.write_line("Skipping organization #{organization}, no candlepin certificate defined.")
      progress_output.status = "Task aborted, exit 1"
      done!
    end
    return
  end

  Tempfile.create([organization.name, '.pem']) do |cer_file|
    cer_file.write(certificate[:cert])
    cer_file.write(certificate[:key])
    cer_file.flush
    upload_report(cer_file.path)
  end

  done!
end

#upload_file(cer_path) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 109

def upload_file(cer_path)
  cert_content = File.read(cer_path)

  File.open(filename, 'rb') do |file|
    # Wrap file with FileUpload class for RestClient multipart handling
    # RestClient requires objects with :read, :path, and :content_type methods
    wrapped_file = FileUpload.new(file, content_type: 'application/vnd.redhat.qpc.tar+tgz')

    response = execute_cloud_request(
      method: :post,
      url: ForemanInventoryUpload.upload_url,
      payload: {
        multipart: true,
        file: wrapped_file,
      },
      headers: {
        'X-Org-Id' => organization.label,
      },
      ssl_client_cert: OpenSSL::X509::Certificate.new(cert_content),
      ssl_client_key: OpenSSL::PKey::RSA.new(cert_content),
      timeout: 600,
      open_timeout: 60
    )

    logger.debug("Upload response code: #{response.code}")
  end
end

#upload_report(cer_path) ⇒ Object



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/foreman_inventory_upload/async/upload_report_direct_job.rb', line 90

def upload_report(cer_path)
  progress_output do |progress_output|
    progress_output.write_line("Uploading report for organization #{organization.label}...")
    progress_output.status = "Running upload"

    begin
      upload_file(cer_path)
      progress_output.write_line("Upload completed successfully")
      move_to_done_folder
      progress_output.write_line("Uploaded file moved to done/ folder")
      progress_output.status = "pid #{Process.pid} exit 0"
    rescue StandardError => e
      progress_output.write_line("Upload failed: #{e.message}")
      progress_output.status = "pid #{Process.pid} exit 1"
      raise
    end
  end
end