idrac 0.1.28 → 0.1.30
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 +24 -1
- data/bin/idrac +80 -51
- data/dell_firmware_downloads/Catalog.etag +1 -0
- data/dell_firmware_downloads/Catalog.xml +0 -0
- data/idrac-0.1.6/.rspec +3 -0
- data/idrac-0.1.6/README.md +103 -0
- data/idrac-0.1.6/Rakefile +17 -0
- data/idrac-0.1.6/bin/console +11 -0
- data/idrac-0.1.6/bin/idrac +179 -0
- data/idrac-0.1.6/bin/setup +8 -0
- data/idrac-0.1.6/idrac.gemspec +51 -0
- data/idrac-0.1.6/lib/idrac/client.rb +109 -0
- data/idrac-0.1.6/lib/idrac/firmware.rb +366 -0
- data/idrac-0.1.6/lib/idrac/version.rb +5 -0
- data/idrac-0.1.6/lib/idrac.rb +30 -0
- data/idrac-0.1.6/sig/idrac.rbs +4 -0
- data/idrac-0.1.7/.rspec +3 -0
- data/idrac-0.1.7/README.md +103 -0
- data/idrac-0.1.7/Rakefile +17 -0
- data/idrac-0.1.7/bin/console +11 -0
- data/idrac-0.1.7/bin/idrac +179 -0
- data/idrac-0.1.7/bin/setup +8 -0
- data/idrac-0.1.7/idrac.gemspec +51 -0
- data/idrac-0.1.7/lib/idrac/client.rb +109 -0
- data/idrac-0.1.7/lib/idrac/firmware.rb +366 -0
- data/idrac-0.1.7/lib/idrac/screenshot.rb +49 -0
- data/idrac-0.1.7/lib/idrac/version.rb +5 -0
- data/idrac-0.1.7/lib/idrac.rb +30 -0
- data/idrac-0.1.7/sig/idrac.rbs +4 -0
- data/idrac.gemspec +1 -0
- data/idrac.py +500 -0
- data/lib/idrac/client.rb +206 -144
- data/lib/idrac/firmware.rb +272 -62
- data/lib/idrac/firmware_catalog.rb +131 -59
- data/lib/idrac/version.rb +1 -1
- data/test_firmware_update.rb +68 -0
- data/updater.rb +729 -0
- metadata +45 -1
data/lib/idrac/firmware.rb
CHANGED
@@ -5,6 +5,8 @@ require 'json'
|
|
5
5
|
require 'nokogiri'
|
6
6
|
require 'fileutils'
|
7
7
|
require 'securerandom'
|
8
|
+
require 'set'
|
9
|
+
require 'colorize'
|
8
10
|
require_relative 'firmware_catalog'
|
9
11
|
|
10
12
|
module IDRAC
|
@@ -125,39 +127,59 @@ module IDRAC
|
|
125
127
|
system_model = inventory[:system][:model]
|
126
128
|
service_tag = inventory[:system][:service_tag]
|
127
129
|
|
128
|
-
puts "Checking updates for system with service tag: #{service_tag}"
|
129
|
-
puts "Searching for updates for model: #{system_model}"
|
130
|
+
puts "Checking updates for system with service tag: #{service_tag}".light_cyan
|
131
|
+
puts "Searching for updates for model: #{system_model}".light_cyan
|
130
132
|
|
131
133
|
# Find system models in the catalog
|
132
|
-
models = catalog.find_system_models(system_model
|
134
|
+
models = catalog.find_system_models(system_model)
|
133
135
|
|
134
136
|
if models.empty?
|
135
|
-
puts "No matching system model found in catalog"
|
137
|
+
puts "No matching system model found in catalog".yellow
|
136
138
|
return []
|
137
139
|
end
|
138
140
|
|
139
141
|
# Use the first matching model
|
140
142
|
model = models.first
|
141
|
-
puts "Found system IDs for #{model[:name]}: #{model[:id]}"
|
143
|
+
puts "Found system IDs for #{model[:name]}: #{model[:id]}".green
|
142
144
|
|
143
145
|
# Find updates for this system
|
144
146
|
catalog_updates = catalog.find_updates_for_system(model[:id])
|
145
|
-
puts "Found #{catalog_updates.size} firmware updates for #{model[:name]}"
|
147
|
+
puts "Found #{catalog_updates.size} firmware updates for #{model[:name]}".green
|
146
148
|
|
147
149
|
# Compare current firmware with available updates
|
148
150
|
updates = []
|
149
151
|
|
150
152
|
# Print header for firmware comparison table
|
151
|
-
puts "\nFirmware Version Comparison:"
|
153
|
+
puts "\nFirmware Version Comparison:".green.bold
|
152
154
|
puts "=" * 100
|
153
|
-
puts "%-30s %-20s %-20s %-10s %-15s
|
155
|
+
puts "%-30s %-20s %-20s %-10s %-15s %s" % ["Component", "Current Version", "Available Version", "Updateable", "Category", "Status"]
|
154
156
|
puts "-" * 100
|
155
157
|
|
156
|
-
#
|
158
|
+
# Track components we've already displayed to avoid duplicates
|
159
|
+
displayed_components = Set.new
|
160
|
+
|
161
|
+
# First show current firmware with available updates
|
157
162
|
inventory[:firmware].each do |fw|
|
158
|
-
#
|
163
|
+
# Make sure firmware name is not nil
|
164
|
+
firmware_name = fw[:name] || ""
|
165
|
+
|
166
|
+
# Skip if we've already displayed this component
|
167
|
+
next if displayed_components.include?(firmware_name.downcase)
|
168
|
+
displayed_components.add(firmware_name.downcase)
|
169
|
+
|
170
|
+
# Extract key identifiers from the firmware name
|
171
|
+
identifiers = extract_identifiers(firmware_name)
|
172
|
+
|
173
|
+
# Try to find a matching update
|
159
174
|
matching_updates = catalog_updates.select do |update|
|
160
|
-
|
175
|
+
update_name = update[:name] || ""
|
176
|
+
|
177
|
+
# Check if any of our identifiers match the update name
|
178
|
+
identifiers.any? { |id| update_name.downcase.include?(id.downcase) } ||
|
179
|
+
# Or if the update name contains the firmware name
|
180
|
+
update_name.downcase.include?(firmware_name.downcase) ||
|
181
|
+
# Or if the firmware name contains the update name
|
182
|
+
firmware_name.downcase.include?(update_name.downcase)
|
161
183
|
end
|
162
184
|
|
163
185
|
if matching_updates.any?
|
@@ -180,73 +202,117 @@ module IDRAC
|
|
180
202
|
}
|
181
203
|
|
182
204
|
# Print row with update available
|
183
|
-
puts "%-30s %-20s %-20s %-10s %-15s
|
184
|
-
fw[:name][0..29],
|
205
|
+
puts "%-30s %-20s %-20s %-10s %-15s %s" % [
|
206
|
+
fw[:name].to_s[0..29],
|
185
207
|
fw[:version],
|
186
208
|
update[:version],
|
187
|
-
fw[:updateable] ? "Yes" : "No",
|
209
|
+
fw[:updateable] ? "Yes".light_green : "No".light_red,
|
188
210
|
update[:category] || "N/A",
|
189
|
-
"UPDATE AVAILABLE"
|
211
|
+
"UPDATE AVAILABLE".light_green.bold
|
190
212
|
]
|
191
213
|
else
|
192
|
-
# Print row with no update
|
193
|
-
|
194
|
-
|
214
|
+
# Print row with no update needed
|
215
|
+
status = if !needs_update
|
216
|
+
"Current".light_blue
|
217
|
+
elsif !fw[:updateable]
|
218
|
+
"Not updateable".light_red
|
219
|
+
else
|
220
|
+
"No update needed".light_yellow
|
221
|
+
end
|
222
|
+
|
223
|
+
puts "%-30s %-20s %-20s %-10s %-15s %s" % [
|
224
|
+
fw[:name].to_s[0..29],
|
195
225
|
fw[:version],
|
196
226
|
update[:version] || "N/A",
|
197
|
-
fw[:updateable] ? "Yes" : "No",
|
227
|
+
fw[:updateable] ? "Yes".light_green : "No".light_red,
|
198
228
|
update[:category] || "N/A",
|
199
|
-
|
229
|
+
status
|
200
230
|
]
|
201
231
|
end
|
202
232
|
else
|
203
233
|
# No matching update found
|
204
|
-
puts "%-30s %-20s %-20s %-10s %-15s
|
205
|
-
fw[:name][0..29],
|
234
|
+
puts "%-30s %-20s %-20s %-10s %-15s %s" % [
|
235
|
+
fw[:name].to_s[0..29],
|
206
236
|
fw[:version],
|
207
237
|
"N/A",
|
208
|
-
fw[:updateable] ? "Yes" : "No",
|
238
|
+
fw[:updateable] ? "Yes".light_green : "No".light_red,
|
209
239
|
"N/A",
|
210
|
-
"No update available"
|
240
|
+
"No update available".light_yellow
|
211
241
|
]
|
212
242
|
end
|
213
243
|
end
|
214
244
|
|
245
|
+
# Then show available updates that don't match any current firmware
|
246
|
+
catalog_updates.each do |update|
|
247
|
+
update_name = update[:name] || ""
|
248
|
+
|
249
|
+
# Skip if we've already displayed this component
|
250
|
+
next if displayed_components.include?(update_name.downcase)
|
251
|
+
displayed_components.add(update_name.downcase)
|
252
|
+
|
253
|
+
# Skip if this update was already matched to a current firmware
|
254
|
+
next if inventory[:firmware].any? do |fw|
|
255
|
+
firmware_name = fw[:name] || ""
|
256
|
+
identifiers = extract_identifiers(firmware_name)
|
257
|
+
|
258
|
+
identifiers.any? { |id| update_name.downcase.include?(id.downcase) } ||
|
259
|
+
update_name.downcase.include?(firmware_name.downcase) ||
|
260
|
+
firmware_name.downcase.include?(update_name.downcase)
|
261
|
+
end
|
262
|
+
|
263
|
+
puts "%-30s %-20s %-20s %-10s %-15s %s" % [
|
264
|
+
update_name.to_s[0..29],
|
265
|
+
"Not Installed".light_red,
|
266
|
+
update[:version] || "Unknown",
|
267
|
+
"N/A",
|
268
|
+
update[:category] || "N/A",
|
269
|
+
"NEW COMPONENT".light_blue
|
270
|
+
]
|
271
|
+
end
|
272
|
+
|
273
|
+
puts "=" * 100
|
274
|
+
|
215
275
|
updates
|
216
276
|
end
|
217
277
|
|
218
|
-
def interactive_update(catalog_path = nil)
|
219
|
-
updates = check_updates(catalog_path)
|
278
|
+
def interactive_update(catalog_path = nil, selected_updates = nil)
|
279
|
+
updates = selected_updates || check_updates(catalog_path)
|
220
280
|
|
221
281
|
if updates.empty?
|
222
|
-
puts "No updates available for your system."
|
282
|
+
puts "No updates available for your system.".yellow
|
223
283
|
return
|
224
284
|
end
|
225
285
|
|
226
|
-
puts "\nAvailable
|
286
|
+
puts "\nAvailable Updates:".green.bold
|
227
287
|
updates.each_with_index do |update, index|
|
228
|
-
puts "#{index + 1}. #{update[:name]}: #{update[:current_version]} -> #{update[:available_version]}"
|
288
|
+
puts "#{index + 1}. #{update[:name]}: #{update[:current_version]} -> #{update[:available_version]}".light_cyan
|
229
289
|
end
|
230
290
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
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
|
296
|
+
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
|
312
|
+
end
|
313
|
+
|
314
|
+
selected.each do |update|
|
315
|
+
puts "Downloading #{update[:name]} version #{update[:available_version]}...".light_cyan.bold
|
250
316
|
|
251
317
|
# Create temp directory
|
252
318
|
temp_dir = Dir.mktmpdir
|
@@ -267,15 +333,35 @@ module IDRAC
|
|
267
333
|
end
|
268
334
|
end
|
269
335
|
else
|
270
|
-
puts "Failed to download update: #{response.code} #{response.message}"
|
336
|
+
puts "Failed to download update: #{response.code} #{response.message}".red
|
271
337
|
next
|
272
338
|
end
|
273
339
|
end
|
274
340
|
end
|
275
341
|
|
276
|
-
puts "Installing #{update[:name]} version #{update[:available_version]}..."
|
277
|
-
|
278
|
-
|
342
|
+
puts "Installing #{update[:name]} version #{update[:available_version]}...".light_cyan.bold
|
343
|
+
|
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
|
361
|
+
else
|
362
|
+
puts "Error during firmware update: #{e.message}".red.bold
|
363
|
+
end
|
364
|
+
end
|
279
365
|
|
280
366
|
ensure
|
281
367
|
# Clean up temp directory
|
@@ -323,28 +409,103 @@ module IDRAC
|
|
323
409
|
post_body << "\r\n--#{boundary}--\r\n"
|
324
410
|
|
325
411
|
# Upload the firmware
|
326
|
-
|
412
|
+
begin
|
413
|
+
response = client.authenticated_request(
|
414
|
+
:post,
|
415
|
+
http_push_uri,
|
416
|
+
{
|
417
|
+
headers: {
|
418
|
+
'Content-Type' => "multipart/form-data; boundary=#{boundary}",
|
419
|
+
'If-Match' => etag
|
420
|
+
},
|
421
|
+
body: post_body.join
|
422
|
+
}
|
423
|
+
)
|
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
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
if response.status < 200 || response.status >= 300
|
435
|
+
error_message = response.body.to_s
|
436
|
+
|
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."
|
442
|
+
else
|
443
|
+
raise Error, "Firmware upload failed with status #{response.status}: #{response.body}"
|
444
|
+
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(
|
327
466
|
:post,
|
328
|
-
|
467
|
+
"/redfish/v1/UpdateService/Actions/UpdateService.SimpleUpdate",
|
329
468
|
{
|
330
469
|
headers: {
|
331
|
-
'Content-Type' =>
|
332
|
-
'If-Match' => etag
|
470
|
+
'Content-Type' => 'application/json'
|
333
471
|
},
|
334
|
-
body:
|
472
|
+
body: JSON.generate({
|
473
|
+
'ImageURI' => image_uri,
|
474
|
+
'@Redfish.OperationApplyTime' => 'Immediate'
|
475
|
+
})
|
335
476
|
}
|
336
477
|
)
|
337
478
|
|
338
|
-
if
|
339
|
-
raise Error, "Firmware
|
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}"
|
340
481
|
end
|
341
482
|
|
342
483
|
# Extract job ID from response
|
343
|
-
|
344
|
-
|
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
|
345
490
|
|
491
|
+
# If not found in header, try to get from response body
|
492
|
+
if job_id.nil? && !simple_update_response.body.empty?
|
493
|
+
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
|
500
|
+
rescue JSON::ParserError
|
501
|
+
# Not JSON, ignore
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
# If still no job ID, use the upload ID as a fallback
|
346
506
|
if job_id.nil?
|
347
|
-
|
507
|
+
job_id = upload_id
|
508
|
+
puts "No job ID found in SimpleUpdate response, using upload ID as job ID"
|
348
509
|
end
|
349
510
|
|
350
511
|
puts "Firmware update job created with ID: #{job_id}"
|
@@ -392,5 +553,54 @@ module IDRAC
|
|
392
553
|
response_data = JSON.parse(response.body)
|
393
554
|
response_data['TaskState'] || 'Unknown'
|
394
555
|
end
|
556
|
+
|
557
|
+
# Helper method to extract identifiers from component names
|
558
|
+
def extract_identifiers(name)
|
559
|
+
return [] unless name
|
560
|
+
|
561
|
+
identifiers = []
|
562
|
+
|
563
|
+
# Extract model numbers like X520, I350, etc.
|
564
|
+
model_matches = name.scan(/[IX]\d{3,4}/)
|
565
|
+
identifiers.concat(model_matches)
|
566
|
+
|
567
|
+
# Extract PERC model like H730
|
568
|
+
perc_matches = name.scan(/[HP]\d{3,4}/)
|
569
|
+
identifiers.concat(perc_matches)
|
570
|
+
|
571
|
+
# Extract other common identifiers
|
572
|
+
if name.include?("NIC") || name.include?("Ethernet") || name.include?("Network")
|
573
|
+
identifiers << "NIC"
|
574
|
+
end
|
575
|
+
|
576
|
+
if name.include?("PERC") || name.include?("RAID")
|
577
|
+
identifiers << "PERC"
|
578
|
+
# Extract PERC model like H730
|
579
|
+
perc_match = name.match(/PERC\s+([A-Z]\d{3})/)
|
580
|
+
identifiers << perc_match[1] if perc_match
|
581
|
+
end
|
582
|
+
|
583
|
+
if name.include?("BIOS")
|
584
|
+
identifiers << "BIOS"
|
585
|
+
end
|
586
|
+
|
587
|
+
if name.include?("iDRAC") || name.include?("IDRAC") || name.include?("Remote Access Controller")
|
588
|
+
identifiers << "iDRAC"
|
589
|
+
end
|
590
|
+
|
591
|
+
if name.include?("Power Supply") || name.include?("PSU")
|
592
|
+
identifiers << "PSU"
|
593
|
+
end
|
594
|
+
|
595
|
+
if name.include?("Lifecycle Controller")
|
596
|
+
identifiers << "LC"
|
597
|
+
end
|
598
|
+
|
599
|
+
if name.include?("CPLD")
|
600
|
+
identifiers << "CPLD"
|
601
|
+
end
|
602
|
+
|
603
|
+
identifiers
|
604
|
+
end
|
395
605
|
end
|
396
606
|
end
|