class IDRAC::Firmware
def upload_firmware(firmware_path)
def upload_firmware(firmware_path) puts "Uploading firmware file: #{firmware_path}".light_cyan begin # First, get the HttpPushUri from the UpdateService response = client.authenticated_request( :get, "/redfish/v1/UpdateService" ) if response.status != 200 puts "Failed to get UpdateService information: #{response.status}".red raise Error, "Failed to get UpdateService information: #{response.status}" end update_service = JSON.parse(response.body) http_push_uri = update_service['HttpPushUri'] if http_push_uri.nil? puts "HttpPushUri not found in UpdateService".red raise Error, "HttpPushUri not found in UpdateService" end puts "Found HttpPushUri: #{http_push_uri}".light_cyan # Get the ETag for the firmware inventory etag_response = client.authenticated_request( :get, http_push_uri ) if etag_response.status != 200 puts "Failed to get ETag: #{etag_response.status}".red raise Error, "Failed to get ETag: #{etag_response.status}" end etag = etag_response.headers['ETag'] if etag.nil? puts "ETag not found in response headers".yellow # Some iDRACs don't require ETag, so we'll continue else puts "Got ETag: #{etag}".light_cyan end # Upload the firmware file file_content = File.read(firmware_path) headers = { 'Content-Type' => 'multipart/form-data', 'If-Match' => etag } # Create a temp file for multipart upload upload_io = Faraday::UploadIO.new(firmware_path, 'application/octet-stream') payload = { :file => upload_io } upload_response = client.authenticated_request( :post, http_push_uri, { headers: headers, body: payload, } ) if upload_response.status != 201 && upload_response.status != 200 puts "Failed to upload firmware: #{upload_response.status} - #{upload_response.body}".red if upload_response.body.include?("already in progress") raise Error, "A deployment or update operation is already in progress. Please wait for it to complete before attempting another update." else raise Error, "Failed to upload firmware: #{upload_response.status} - #{upload_response.body}" end end # Extract the firmware ID from the response begin upload_data = JSON.parse(upload_response.body) firmware_id = upload_data['Id'] || upload_data['@odata.id']&.split('/')&.last if firmware_id.nil? # Try to extract from the Location header location = upload_response.headers['Location'] firmware_id = location&.split('/')&.last end if firmware_id.nil? puts "Warning: Could not extract firmware ID from response".yellow puts "Response body: #{upload_response.body}" # We'll try to continue with the SimpleUpdate action anyway else puts "Firmware file uploaded successfully with ID: #{firmware_id}".green end rescue JSON::ParserError => e puts "Warning: Could not parse upload response: #{e.message}".yellow puts "Response body: #{upload_response.body}" # We'll try to continue with the SimpleUpdate action anyway end # Now initiate the firmware update using SimpleUpdate action puts "Initiating firmware update using SimpleUpdate...".light_cyan # Construct the image URI image_uri = nil if firmware_id image_uri = "#{http_push_uri}/#{firmware_id}" else # If we couldn't extract the firmware ID, try using the Location header image_uri = upload_response.headers['Location'] end # If we still don't have an image URI, try to use the HTTP push URI as a fallback if image_uri.nil? puts "Warning: Could not determine image URI, using HTTP push URI as fallback".yellow image_uri = http_push_uri end puts "Using ImageURI: #{image_uri}".light_cyan # Initiate the SimpleUpdate action simple_update_payload = { "ImageURI" => image_uri, "TransferProtocol" => "HTTP" } update_response = client.authenticated_request( :post, "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate", { headers: { 'Content-Type' => 'application/json' }, body: simple_update_payload.to_json } ) if update_response.status != 202 && update_response.status != 200 puts "Failed to initiate firmware update: #{update_response.status} - #{update_response.body}".red raise Error, "Failed to initiate firmware update: #{update_response.status} - #{update_response.body}" end # Extract the job ID from the response job_id = nil # Try to extract from the response body first begin update_data = JSON.parse(update_response.body) job_id = update_data['Id'] || update_data['JobID'] rescue JSON::ParserError # If we can't parse the body, that's okay, we'll try other methods end # If we couldn't get the job ID from the body, try the Location header if job_id.nil? location = update_response.headers['Location'] job_id = location&.split('/')&.last end # If we still don't have a job ID, try the response headers if job_id.nil? # Some iDRACs return the job ID in a custom header update_response.headers.each do |key, value| if key.downcase.include?('job') && value.is_a?(String) && value.match?(/JID_\d+/) job_id = value break end end end # If we still don't have a job ID, check for any JID_ pattern in the response body if job_id.nil? && update_response.body.is_a?(String) match = update_response.body.match(/JID_\d+/) job_id = match[0] if match end # If we still don't have a job ID, check the task service for recent jobs if job_id.nil? puts "Could not extract job ID from response, checking task service for recent jobs...".yellow tasks_response = client.authenticated_request( :get, "/redfish/v1/TaskService/Tasks" ) if tasks_response.status == 200 begin tasks_data = JSON.parse(tasks_response.body) if tasks_data['Members'] && tasks_data['Members'].any? # Get the most recent task most_recent_task = tasks_data['Members'].first task_id = most_recent_task['@odata.id']&.split('/')&.last if task_id && task_id.match?(/JID_\d+/) job_id = task_id puts "Found recent job ID: #{job_id}".light_cyan end end rescue JSON::ParserError # If we can't parse the tasks response, we'll have to give up end end end if job_id.nil? puts "Could not extract job ID from response".red raise Error, "Could not extract job ID from response" end puts "Firmware update job created with ID: #{job_id}".green return job_id rescue => e puts "Error during firmware upload: #{e.message}".red.bold raise Error, "Error during firmware upload: #{e.message}" end end