idrac 0.1.30 → 0.1.31

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6992752c6eee7bfb3c858d8bb465c68c68bff2008732ecfc88553ba7ac0524b5
4
- data.tar.gz: ab1e816e1c1d903c3095c5131652f917a61b3c4006e0941fd05d24555230f9f5
3
+ metadata.gz: b2c0dae199436d9ce49a07eab55be6f44e9260ece8c64b07f7221384174a72de
4
+ data.tar.gz: 5b89e9ef776654053fe6ac4e8d6cd87ad121dfaa37194fd65b3cde2c5d44c683
5
5
  SHA512:
6
- metadata.gz: b65362922f5a5728e6840a005ee7a79f2c08a835bba4ae7394eb304d2992426d9a9a52457173eb579e8e792413767c9249c64f5eacaee9dd47105971b10b3187
7
- data.tar.gz: 8f97a86b72b0fb3544cb85c61f94b8228b037f63adeb345d9da856f7159d7e698c176fd303e212182dd779496b77c07b0b2efe83cc8d8fe8cee281060132d0e0
6
+ metadata.gz: 682eeec6204b0a7f105473fb0c61a44e11e067b48bd8db4c1f693d9895676ae7e8f1dfb6f655d19aeec421fb7fc83c1710508a78ab9f696b2adc6cb6b509bddb
7
+ data.tar.gz: bdfd016c0353de003a0182a6c5f582472f9f8071cf6f47a4fd448bccd62ac60c1747634b349a0afcd3422ec2b62842109b662ad500d6bdeee6cfeb55e9d6e47c
data/README.md CHANGED
@@ -134,6 +134,16 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
134
134
 
135
135
  ## Changelog
136
136
 
137
+ ### Version 0.1.31
138
+ - **Enhanced Job Monitoring**: Completely redesigned the job monitoring system for firmware updates
139
+ - Improved handling of HTTP 202 status codes during job status checks
140
+ - Added detailed progress reporting with percentage complete during firmware updates
141
+ - Enhanced job ID extraction with multiple fallback mechanisms to ensure proper job tracking
142
+ - Improved error handling for common firmware update issues with clear user guidance
143
+ - Added better handling of "deployment already in progress" scenarios
144
+ - Implemented a more robust firmware download process with better error reporting
145
+ - Enhanced the interactive update process with clearer user prompts and status messages
146
+
137
147
  ### Version 0.1.30
138
148
  - **Enhanced Terminal Output with Colors**: Added colorized output for better readability and user experience
139
149
  - Integrated the `colorize` gem to provide color-coded messages throughout the application
@@ -276,6 +276,7 @@ module IDRAC
276
276
  end
277
277
 
278
278
  def interactive_update(catalog_path = nil, selected_updates = nil)
279
+ # Check if updates are available
279
280
  updates = selected_updates || check_updates(catalog_path)
280
281
 
281
282
  if updates.empty?
@@ -283,261 +284,407 @@ module IDRAC
283
284
  return
284
285
  end
285
286
 
287
+ # Display available updates
286
288
  puts "\nAvailable Updates:".green.bold
287
289
  updates.each_with_index do |update, index|
288
290
  puts "#{index + 1}. #{update[:name]}: #{update[:current_version]} -> #{update[:available_version]}".light_cyan
289
291
  end
290
292
 
291
- # If selected_updates is provided, use those directly
292
- if selected_updates
293
- selected = selected_updates
294
- else
295
- # Otherwise prompt the user for selection
293
+ # If no specific updates were selected, ask the user which ones to install
294
+ if selected_updates.nil?
296
295
  puts "\nEnter the number of the update to install (or 'all' for all updates, 'q' to quit):".light_yellow
297
- choice = STDIN.gets.chomp
298
-
299
- return if choice.downcase == 'q'
300
-
301
- selected = if choice.downcase == 'all'
302
- updates
303
- else
304
- index = choice.to_i - 1
305
- if index >= 0 && index < updates.size
306
- [updates[index]]
307
- else
308
- puts "Invalid selection.".red
309
- return
310
- end
311
- end
296
+ input = STDIN.gets.chomp
297
+
298
+ if input.downcase == 'q'
299
+ puts "Update cancelled.".yellow
300
+ return
301
+ elsif input.downcase == 'all'
302
+ selected_updates = updates
303
+ else
304
+ begin
305
+ index = input.to_i - 1
306
+ if index >= 0 && index < updates.length
307
+ selected_updates = [updates[index]]
308
+ else
309
+ puts "Invalid selection. Please enter a number between 1 and #{updates.length}.".red
310
+ return
311
+ end
312
+ rescue
313
+ puts "Invalid input. Please enter a number, 'all', or 'q'.".red
314
+ return
315
+ end
316
+ end
312
317
  end
313
318
 
314
- selected.each do |update|
315
- puts "Downloading #{update[:name]} version #{update[:available_version]}...".light_cyan.bold
316
-
317
- # Create temp directory
318
- temp_dir = Dir.mktmpdir
319
+ # Process each selected update
320
+ selected_updates.each do |update|
321
+ puts "\nDownloading #{update[:name]} version #{update[:available_version]}...".light_cyan
319
322
 
320
323
  begin
321
- # Download the update
322
- update_filename = File.basename(update[:path])
323
- update_path = File.join(temp_dir, update_filename)
324
+ # Download the firmware
325
+ firmware_file = download_firmware(update)
324
326
 
325
- uri = URI.parse(update[:download_url])
326
- Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
327
- request = Net::HTTP::Get.new(uri)
328
- http.request(request) do |response|
329
- if response.code == "200"
330
- File.open(update_path, 'wb') do |file|
331
- response.read_body do |chunk|
332
- file.write(chunk)
333
- end
327
+ if firmware_file
328
+ puts "Installing #{update[:name]} version #{update[:available_version]}...".light_cyan
329
+
330
+ begin
331
+ # Upload and install the firmware
332
+ job_id = upload_firmware(firmware_file)
333
+
334
+ if job_id
335
+ puts "Firmware update job created with ID: #{job_id}".green
336
+
337
+ # Wait for the job to complete
338
+ success = wait_for_job_completion(job_id, 1800) # 30 minutes timeout
339
+
340
+ if success
341
+ puts "Successfully updated #{update[:name]} to version #{update[:available_version]}".green.bold
342
+ else
343
+ puts "Failed to update #{update[:name]}. Check the iDRAC web interface for more details.".red
344
+ puts "You may need to wait for any existing jobs to complete before trying again.".yellow
334
345
  end
335
346
  else
336
- puts "Failed to download update: #{response.code} #{response.message}".red
337
- next
347
+ puts "Failed to create update job for #{update[:name]}".red
348
+ end
349
+ rescue IDRAC::Error => e
350
+ if e.message.include?("already in progress")
351
+ puts "Error: A firmware update is already in progress.".red.bold
352
+ puts "Please wait for the current update to complete before starting another.".yellow
353
+ puts "You can check the status in the iDRAC web interface under Maintenance > System Update.".light_cyan
354
+ elsif e.message.include?("job ID not found") || e.message.include?("Failed to get job status")
355
+ puts "Error: Could not monitor the update job.".red.bold
356
+ puts "The update may still be in progress. Check the iDRAC web interface for status.".yellow
357
+ puts "This can happen if the iDRAC is busy processing the update request.".light_cyan
358
+ else
359
+ puts "Error during firmware update: #{e.message}".red.bold
360
+ end
361
+
362
+ # If we encounter an error with one update, ask if the user wants to continue with others
363
+ if selected_updates.length > 1 && update != selected_updates.last
364
+ puts "\nDo you want to continue with the remaining updates? (y/n)".light_yellow
365
+ continue = STDIN.gets.chomp.downcase
366
+ break unless continue == 'y'
338
367
  end
339
368
  end
369
+ else
370
+ puts "Failed to download firmware for #{update[:name]}".red
340
371
  end
372
+ rescue => e
373
+ puts "Error processing update for #{update[:name]}: #{e.message}".red.bold
341
374
 
342
- puts "Installing #{update[:name]} version #{update[:available_version]}...".light_cyan.bold
375
+ # If we encounter an error with one update, ask if the user wants to continue with others
376
+ if selected_updates.length > 1 && update != selected_updates.last
377
+ puts "\nDo you want to continue with the remaining updates? (y/n)".light_yellow
378
+ continue = STDIN.gets.chomp.downcase
379
+ break unless continue == 'y'
380
+ end
381
+ ensure
382
+ # Clean up temporary files
383
+ FileUtils.rm_f(firmware_file) if firmware_file && File.exist?(firmware_file)
384
+ end
385
+ end
386
+ end
387
+
388
+ def download_firmware(update)
389
+ return false unless update && update[:download_url]
390
+
391
+ begin
392
+ # Create a temporary directory for the download
393
+ temp_dir = Dir.mktmpdir
394
+
395
+ # Extract the filename from the path
396
+ filename = File.basename(update[:path])
397
+ local_path = File.join(temp_dir, filename)
398
+
399
+ puts "Downloading firmware from #{update[:download_url]}".light_cyan
400
+ puts "Saving to #{local_path}".light_cyan
401
+
402
+ # Download the file
403
+ uri = URI.parse(update[:download_url])
404
+
405
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', verify_mode: OpenSSL::SSL::VERIFY_NONE) do |http|
406
+ request = Net::HTTP::Get.new(uri)
343
407
 
344
- begin
345
- job_id = update(update_path, wait: true)
346
- puts "Update completed with job ID: #{job_id}".green.bold
347
- rescue IDRAC::Error => e
348
- if e.message.include?("already in progress")
349
- puts "Error: #{e.message}".red.bold
350
- puts "\nTips for resolving this issue:".yellow.bold
351
- puts "1. Wait for any existing firmware updates to complete (check iDRAC web interface)".light_yellow
352
- puts "2. Restart the iDRAC if no updates appear to be in progress (Settings > iDRAC Settings > Reset iDRAC)".light_yellow
353
- puts "3. Try again after a few minutes".light_yellow
354
- elsif e.message.include?("job ID") || e.message.include?("job not found")
355
- puts "Error: #{e.message}".red.bold
356
- puts "\nThe job ID could not be found or monitored. This could be because:".yellow
357
- puts "1. The iDRAC is busy processing other requests".light_yellow
358
- puts "2. The job was created but not properly tracked".light_yellow
359
- puts "3. The iDRAC firmware may need to be updated first".light_yellow
360
- puts "\nCheck the iDRAC web interface to see if the update was actually initiated.".light_cyan
408
+ http.request(request) do |response|
409
+ if response.code == "200"
410
+ File.open(local_path, 'wb') do |file|
411
+ response.read_body do |chunk|
412
+ file.write(chunk)
413
+ end
414
+ end
415
+
416
+ puts "Download completed successfully".green
417
+ return local_path
361
418
  else
362
- puts "Error during firmware update: #{e.message}".red.bold
419
+ puts "Failed to download firmware: #{response.code} #{response.message}".red
420
+ return false
363
421
  end
364
422
  end
365
-
366
- ensure
367
- # Clean up temp directory
368
- FileUtils.remove_entry(temp_dir)
369
423
  end
424
+ rescue => e
425
+ puts "Error downloading firmware: #{e.message}".red.bold
426
+ return false
370
427
  end
371
428
  end
372
429
 
373
430
  private
374
431
 
375
432
  def upload_firmware(firmware_path)
376
- puts "Uploading firmware file: #{firmware_path}"
377
-
378
- # Get the HttpPushUri from UpdateService
379
- update_service_response = client.authenticated_request(:get, "/redfish/v1/UpdateService")
380
- update_service_data = JSON.parse(update_service_response.body)
381
-
382
- http_push_uri = update_service_data['HttpPushUri']
383
- if http_push_uri.nil?
384
- http_push_uri = "/redfish/v1/UpdateService/FirmwareInventory"
385
- puts "HttpPushUri not found, using default: #{http_push_uri}"
386
- else
387
- puts "Found HttpPushUri: #{http_push_uri}"
388
- end
433
+ puts "Uploading firmware file: #{firmware_path}".light_cyan
389
434
 
390
- # Get the ETag for the HttpPushUri
391
- etag_response = client.authenticated_request(:get, http_push_uri)
392
- etag = etag_response.headers['etag']
393
-
394
- puts "Got ETag: #{etag}"
395
-
396
- # Create a boundary for multipart/form-data
397
- boundary = "----WebKitFormBoundary#{SecureRandom.hex(16)}"
398
-
399
- # Read the file content
400
- file_content = File.binread(firmware_path)
401
- filename = File.basename(firmware_path)
402
-
403
- # Create the multipart body
404
- post_body = []
405
- post_body << "--#{boundary}\r\n"
406
- post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n"
407
- post_body << "Content-Type: application/octet-stream\r\n\r\n"
408
- post_body << file_content
409
- post_body << "\r\n--#{boundary}--\r\n"
410
-
411
- # Upload the firmware
412
435
  begin
436
+ # First, get the HttpPushUri from the UpdateService
413
437
  response = client.authenticated_request(
438
+ :get,
439
+ "/redfish/v1/UpdateService"
440
+ )
441
+
442
+ if response.status != 200
443
+ puts "Failed to get UpdateService information: #{response.status}".red
444
+ raise Error, "Failed to get UpdateService information: #{response.status}"
445
+ end
446
+
447
+ update_service = JSON.parse(response.body)
448
+ http_push_uri = update_service['HttpPushUri']
449
+
450
+ if http_push_uri.nil?
451
+ puts "HttpPushUri not found in UpdateService".red
452
+ raise Error, "HttpPushUri not found in UpdateService"
453
+ end
454
+
455
+ puts "Found HttpPushUri: #{http_push_uri}".light_cyan
456
+
457
+ # Get the ETag for the firmware inventory
458
+ etag_response = client.authenticated_request(
459
+ :get,
460
+ http_push_uri
461
+ )
462
+
463
+ if etag_response.status != 200
464
+ puts "Failed to get ETag: #{etag_response.status}".red
465
+ raise Error, "Failed to get ETag: #{etag_response.status}"
466
+ end
467
+
468
+ etag = etag_response.headers['ETag']
469
+
470
+ if etag.nil?
471
+ puts "ETag not found in response headers".yellow
472
+ # Some iDRACs don't require ETag, so we'll continue
473
+ else
474
+ puts "Got ETag: #{etag}".light_cyan
475
+ end
476
+
477
+ # Upload the firmware file
478
+ file_content = File.read(firmware_path)
479
+
480
+ headers = {
481
+ 'Content-Type' => 'application/octet-stream',
482
+ 'If-Match' => etag
483
+ }
484
+
485
+ upload_response = client.authenticated_request(
414
486
  :post,
415
487
  http_push_uri,
416
488
  {
417
- headers: {
418
- 'Content-Type' => "multipart/form-data; boundary=#{boundary}",
419
- 'If-Match' => etag
420
- },
421
- body: post_body.join
489
+ headers: headers,
490
+ body: file_content
422
491
  }
423
492
  )
424
- rescue => e
425
- # Check if the error is about a deployment already in progress
426
- if e.message.include?("A deployment or update operation is already in progress")
427
- raise Error, "A firmware update is already in progress on the iDRAC. Please wait for it to complete before starting another update. You can check the status in the iDRAC web interface under Maintenance > System Update."
428
- else
429
- # Re-raise the original error
430
- raise
493
+
494
+ if upload_response.status != 201 && upload_response.status != 200
495
+ puts "Failed to upload firmware: #{upload_response.status} - #{upload_response.body}".red
496
+
497
+ if upload_response.body.include?("already in progress")
498
+ raise Error, "A deployment or update operation is already in progress. Please wait for it to complete before attempting another update."
499
+ else
500
+ raise Error, "Failed to upload firmware: #{upload_response.status} - #{upload_response.body}"
501
+ end
431
502
  end
432
- end
433
-
434
- if response.status < 200 || response.status >= 300
435
- error_message = response.body.to_s
436
503
 
437
- # Check for specific error messages in the response
438
- if error_message.include?("A deployment or update operation is already in progress")
439
- raise Error, "A firmware update is already in progress on the iDRAC. Please wait for it to complete before starting another update. You can check the status in the iDRAC web interface under Maintenance > System Update."
440
- elsif error_message.include?("SUP0108")
441
- raise Error, "iDRAC Error SUP0108: A deployment or update operation is already in progress. Wait for the operation to conclude and then try again."
504
+ # Extract the firmware ID from the response
505
+ begin
506
+ upload_data = JSON.parse(upload_response.body)
507
+ firmware_id = upload_data['Id'] || upload_data['@odata.id']&.split('/')&.last
508
+
509
+ if firmware_id.nil?
510
+ # Try to extract from the Location header
511
+ location = upload_response.headers['Location']
512
+ firmware_id = location&.split('/')&.last
513
+ end
514
+
515
+ if firmware_id.nil?
516
+ puts "Warning: Could not extract firmware ID from response".yellow
517
+ puts "Response body: #{upload_response.body}"
518
+ # We'll try to continue with the SimpleUpdate action anyway
519
+ else
520
+ puts "Firmware file uploaded successfully with ID: #{firmware_id}".green
521
+ end
522
+ rescue JSON::ParserError => e
523
+ puts "Warning: Could not parse upload response: #{e.message}".yellow
524
+ puts "Response body: #{upload_response.body}"
525
+ # We'll try to continue with the SimpleUpdate action anyway
526
+ end
527
+
528
+ # Now initiate the firmware update using SimpleUpdate action
529
+ puts "Initiating firmware update using SimpleUpdate...".light_cyan
530
+
531
+ # Construct the image URI
532
+ image_uri = nil
533
+
534
+ if firmware_id
535
+ image_uri = "#{http_push_uri}/#{firmware_id}"
442
536
  else
443
- raise Error, "Firmware upload failed with status #{response.status}: #{response.body}"
537
+ # If we couldn't extract the firmware ID, try using the Location header
538
+ image_uri = upload_response.headers['Location']
444
539
  end
445
- end
446
-
447
- # Extract upload ID from response
448
- response_data = JSON.parse(response.body)
449
- upload_id = response_data['Id'] || response_data['TaskId']
450
-
451
- if upload_id.nil?
452
- raise Error, "Failed to extract upload ID from firmware upload response"
453
- end
454
-
455
- puts "Firmware file uploaded successfully with ID: #{upload_id}"
456
-
457
- # Step 2: Initiate the firmware update using SimpleUpdate
458
- puts "Initiating firmware update using SimpleUpdate..."
459
-
460
- # Construct the image URI using the uploaded file
461
- image_uri = "#{http_push_uri}/#{upload_id}"
462
- puts "Using ImageURI: #{image_uri}"
463
-
464
- # Call the SimpleUpdate action
465
- simple_update_response = client.authenticated_request(
466
- :post,
467
- "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
468
- {
469
- headers: {
470
- 'Content-Type' => 'application/json'
471
- },
472
- body: JSON.generate({
473
- 'ImageURI' => image_uri,
474
- '@Redfish.OperationApplyTime' => 'Immediate'
475
- })
540
+
541
+ # If we still don't have an image URI, try to use the HTTP push URI as a fallback
542
+ if image_uri.nil?
543
+ puts "Warning: Could not determine image URI, using HTTP push URI as fallback".yellow
544
+ image_uri = http_push_uri
545
+ end
546
+
547
+ puts "Using ImageURI: #{image_uri}".light_cyan
548
+
549
+ # Initiate the SimpleUpdate action
550
+ simple_update_payload = {
551
+ "ImageURI" => image_uri,
552
+ "TransferProtocol" => "HTTP"
476
553
  }
477
- )
478
-
479
- if simple_update_response.status < 200 || simple_update_response.status >= 300
480
- raise Error, "Firmware update initiation failed with status #{simple_update_response.status}: #{simple_update_response.body}"
481
- end
482
-
483
- # Extract job ID from response
484
- job_id = nil
485
-
486
- # Try to get job ID from Location header
487
- if simple_update_response.headers['location']
488
- job_id = simple_update_response.headers['location'].split('/').last
489
- end
490
-
491
- # If not found in header, try to get from response body
492
- if job_id.nil? && !simple_update_response.body.empty?
554
+
555
+ update_response = client.authenticated_request(
556
+ :post,
557
+ "/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
558
+ {
559
+ headers: { 'Content-Type' => 'application/json' },
560
+ body: simple_update_payload.to_json
561
+ }
562
+ )
563
+
564
+ if update_response.status != 202 && update_response.status != 200
565
+ puts "Failed to initiate firmware update: #{update_response.status} - #{update_response.body}".red
566
+ raise Error, "Failed to initiate firmware update: #{update_response.status} - #{update_response.body}"
567
+ end
568
+
569
+ # Extract the job ID from the response
570
+ job_id = nil
571
+
572
+ # Try to extract from the response body first
493
573
  begin
494
- update_data = JSON.parse(simple_update_response.body)
495
- if update_data['@odata.id']
496
- job_id = update_data['@odata.id'].split('/').last
497
- elsif update_data['Id']
498
- job_id = update_data['Id']
499
- end
574
+ update_data = JSON.parse(update_response.body)
575
+ job_id = update_data['Id'] || update_data['JobID']
500
576
  rescue JSON::ParserError
501
- # Not JSON, ignore
577
+ # If we can't parse the body, that's okay, we'll try other methods
502
578
  end
579
+
580
+ # If we couldn't get the job ID from the body, try the Location header
581
+ if job_id.nil?
582
+ location = update_response.headers['Location']
583
+ job_id = location&.split('/')&.last
584
+ end
585
+
586
+ # If we still don't have a job ID, try the response headers
587
+ if job_id.nil?
588
+ # Some iDRACs return the job ID in a custom header
589
+ update_response.headers.each do |key, value|
590
+ if key.downcase.include?('job') && value.is_a?(String) && value.match?(/JID_\d+/)
591
+ job_id = value
592
+ break
593
+ end
594
+ end
595
+ end
596
+
597
+ # If we still don't have a job ID, check for any JID_ pattern in the response body
598
+ if job_id.nil? && update_response.body.is_a?(String)
599
+ match = update_response.body.match(/JID_\d+/)
600
+ job_id = match[0] if match
601
+ end
602
+
603
+ # If we still don't have a job ID, check the task service for recent jobs
604
+ if job_id.nil?
605
+ puts "Could not extract job ID from response, checking task service for recent jobs...".yellow
606
+
607
+ tasks_response = client.authenticated_request(
608
+ :get,
609
+ "/redfish/v1/TaskService/Tasks"
610
+ )
611
+
612
+ if tasks_response.status == 200
613
+ begin
614
+ tasks_data = JSON.parse(tasks_response.body)
615
+
616
+ if tasks_data['Members'] && tasks_data['Members'].any?
617
+ # Get the most recent task
618
+ most_recent_task = tasks_data['Members'].first
619
+ task_id = most_recent_task['@odata.id']&.split('/')&.last
620
+
621
+ if task_id && task_id.match?(/JID_\d+/)
622
+ job_id = task_id
623
+ puts "Found recent job ID: #{job_id}".light_cyan
624
+ end
625
+ end
626
+ rescue JSON::ParserError
627
+ # If we can't parse the tasks response, we'll have to give up
628
+ end
629
+ end
630
+ end
631
+
632
+ if job_id.nil?
633
+ puts "Could not extract job ID from response".red
634
+ raise Error, "Could not extract job ID from response"
635
+ end
636
+
637
+ puts "Firmware update job created with ID: #{job_id}".green
638
+ return job_id
639
+ rescue => e
640
+ puts "Error during firmware upload: #{e.message}".red.bold
641
+ raise Error, "Error during firmware upload: #{e.message}"
503
642
  end
504
-
505
- # If still no job ID, use the upload ID as a fallback
506
- if job_id.nil?
507
- job_id = upload_id
508
- puts "No job ID found in SimpleUpdate response, using upload ID as job ID"
509
- end
510
-
511
- puts "Firmware update job created with ID: #{job_id}"
512
- job_id
513
643
  end
514
644
 
515
645
  def wait_for_job_completion(job_id, timeout)
516
- puts "Waiting for firmware update job #{job_id} to complete..."
646
+ puts "Waiting for firmware update job #{job_id} to complete...".light_cyan
517
647
 
518
648
  start_time = Time.now
519
- loop do
520
- status = get_job_status(job_id)
521
-
522
- case status
523
- when 'Completed'
524
- puts "Firmware update completed successfully"
525
- return true
526
- when 'Failed'
527
- raise Error, "Firmware update job failed"
528
- when 'Scheduled', 'Running', 'Downloading', 'Pending'
529
- # Job still in progress
530
- else
531
- puts "Unknown job status: #{status}"
532
- end
533
-
534
- if Time.now - start_time > timeout
535
- raise Error, "Firmware update timed out after #{timeout} seconds"
649
+ last_percent = -1
650
+
651
+ while Time.now - start_time < timeout
652
+ begin
653
+ status = get_job_status(job_id)
654
+
655
+ # Only show percentage updates when they change
656
+ if status[:percent_complete] && status[:percent_complete] != last_percent
657
+ puts "Job progress: #{status[:percent_complete]}% complete".light_cyan
658
+ last_percent = status[:percent_complete]
659
+ end
660
+
661
+ case status[:state]
662
+ when 'Completed'
663
+ puts "Firmware update completed successfully".green
664
+ return true
665
+ when 'Failed', 'CompletedWithErrors'
666
+ message = status[:message] || "Unknown error"
667
+ puts "Firmware update failed: #{message}".red.bold
668
+ return false
669
+ when 'Stopped'
670
+ puts "Firmware update stopped".yellow
671
+ return false
672
+ when 'New', 'Starting', 'Running', 'Pending', 'Scheduled', 'Downloaded', 'Downloading', 'Staged'
673
+ # Job still in progress, continue waiting
674
+ sleep 10
675
+ else
676
+ puts "Unknown job status: #{status[:state]}".yellow
677
+ sleep 10
678
+ end
679
+ rescue => e
680
+ puts "Error checking job status: #{e.message}".red
681
+ puts "Will retry in 15 seconds...".yellow
682
+ sleep 15
536
683
  end
537
-
538
- # Wait before checking again
539
- sleep 10
540
684
  end
685
+
686
+ puts "Timeout waiting for firmware update to complete".red.bold
687
+ false
541
688
  end
542
689
 
543
690
  def get_job_status(job_id)
@@ -546,12 +693,37 @@ module IDRAC
546
693
  "/redfish/v1/TaskService/Tasks/#{job_id}"
547
694
  )
548
695
 
549
- if response.status != 200
550
- raise Error, "Failed to get job status with status #{response.status}: #{response.body}"
696
+ # Status 202 means the request was accepted but still processing
697
+ # This is normal for jobs that are in progress
698
+ if response.status == 202 || response.status == 200
699
+ begin
700
+ data = JSON.parse(response.body)
701
+
702
+ # Extract job state and percent complete
703
+ job_state = data.dig('Oem', 'Dell', 'JobState') || data['TaskState']
704
+ percent_complete = data.dig('Oem', 'Dell', 'PercentComplete') || data['PercentComplete']
705
+
706
+ # Format the percent complete for display
707
+ percent_str = percent_complete.nil? ? "unknown" : "#{percent_complete}%"
708
+
709
+ puts "Job #{job_id} status: #{job_state} (#{percent_str} complete)".light_cyan
710
+
711
+ return {
712
+ id: job_id,
713
+ state: job_state,
714
+ percent_complete: percent_complete,
715
+ status: data['TaskStatus'],
716
+ message: data.dig('Oem', 'Dell', 'Message') || (data['Messages'].first && data['Messages'].first['Message']),
717
+ raw_data: data
718
+ }
719
+ rescue JSON::ParserError => e
720
+ puts "Error parsing job status response: #{e.message}".red.bold
721
+ raise Error, "Failed to parse job status response: #{e.message}"
722
+ end
723
+ else
724
+ puts "Failed to get job status with status #{response.status}: #{response.body}".red
725
+ raise Error, "Failed to get job status with status #{response.status}"
551
726
  end
552
-
553
- response_data = JSON.parse(response.body)
554
- response_data['TaskState'] || 'Unknown'
555
727
  end
556
728
 
557
729
  # Helper method to extract identifiers from component names
data/lib/idrac/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IDRAC
4
- VERSION = "0.1.30"
4
+ VERSION = "0.1.31"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idrac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.30
4
+ version: 0.1.31
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel