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 +4 -4
- data/README.md +10 -0
- data/lib/idrac/firmware.rb +380 -208
- data/lib/idrac/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2c0dae199436d9ce49a07eab55be6f44e9260ece8c64b07f7221384174a72de
|
4
|
+
data.tar.gz: 5b89e9ef776654053fe6ac4e8d6cd87ad121dfaa37194fd65b3cde2c5d44c683
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/lib/idrac/firmware.rb
CHANGED
@@ -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
|
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
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
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
|
-
|
315
|
-
|
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
|
322
|
-
|
323
|
-
update_path = File.join(temp_dir, update_filename)
|
324
|
+
# Download the firmware
|
325
|
+
firmware_file = download_firmware(update)
|
324
326
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
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
|
337
|
-
|
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
|
-
|
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
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
puts "
|
353
|
-
|
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 "
|
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
|
-
|
419
|
-
'If-Match' => etag
|
420
|
-
},
|
421
|
-
body: post_body.join
|
489
|
+
headers: headers,
|
490
|
+
body: file_content
|
422
491
|
}
|
423
492
|
)
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
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
|
-
#
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
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
|
-
|
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
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
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
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
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(
|
495
|
-
|
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
|
-
#
|
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
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
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
|
-
|
550
|
-
|
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