idrac 0.1.91 → 0.3.1

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.
@@ -0,0 +1,383 @@
1
+ require 'json'
2
+ require 'colorize'
3
+
4
+ module IDRAC
5
+ module SystemComponentMethods
6
+ # Get memory information
7
+ def memory
8
+ response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/Memory?$expand=*($levels=1)")
9
+
10
+ if response.status == 200
11
+ begin
12
+ data = JSON.parse(response.body)
13
+ memory = data["Members"].map do |m|
14
+ dimm_name = m["Name"] # e.g. DIMM A1
15
+ bank, index = /DIMM ([A-Z])(\d+)/.match(dimm_name).captures rescue [nil, nil]
16
+
17
+ puts "DIMM: #{m["Model"]} #{m["Name"]} > #{m["CapacityMiB"]}MiB > #{m["Status"]["Health"]} > #{m["OperatingSpeedMhz"]}MHz > #{m["PartNumber"]} / #{m["SerialNumber"]}"
18
+
19
+ {
20
+ "model" => m["Model"],
21
+ "name" => m["Name"],
22
+ "capacity_bytes" => m["CapacityMiB"].to_i.megabyte,
23
+ "health" => m["Status"]["Health"] ? m["Status"]["Health"] : "N/A",
24
+ "speed_mhz" => m["OperatingSpeedMhz"],
25
+ "part_number" => m["PartNumber"],
26
+ "serial" => m["SerialNumber"],
27
+ "bank" => bank,
28
+ "index" => index.to_i
29
+ }
30
+ end
31
+
32
+ return memory.sort_by { |a| [a["bank"] || "Z", a["index"] || 999] }
33
+ rescue JSON::ParserError
34
+ raise Error, "Failed to parse memory response: #{response.body}"
35
+ end
36
+ else
37
+ raise Error, "Failed to get memory. Status code: #{response.status}"
38
+ end
39
+ end
40
+
41
+ # Get power supply information
42
+ def psus
43
+ response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/Power")
44
+
45
+ if response.status == 200
46
+ begin
47
+ data = JSON.parse(response.body)
48
+ puts "Power Supplies".green
49
+
50
+ psus = data["PowerSupplies"].map do |psu|
51
+ puts "PSU: #{psu["Name"]} > #{psu["PowerInputWatts"]}W > #{psu["Status"]["Health"]}"
52
+ {
53
+ "name" => psu["Name"],
54
+ "voltage" => psu["LineInputVoltage"],
55
+ "voltage_human" => psu["LineInputVoltageType"], # AC240V
56
+ "watts" => psu["PowerInputWatts"],
57
+ "part" => psu["PartNumber"],
58
+ "model" => psu["Model"],
59
+ "serial" => psu["SerialNumber"],
60
+ "status" => psu["Status"]["Health"],
61
+ }
62
+ end
63
+
64
+ return psus
65
+ rescue JSON::ParserError
66
+ raise Error, "Failed to parse PSU response: #{response.body}"
67
+ end
68
+ else
69
+ raise Error, "Failed to get PSUs. Status code: #{response.status}"
70
+ end
71
+ end
72
+
73
+ # Get fan information
74
+ def fans
75
+ tries = 0
76
+ max_tries = 3
77
+
78
+ while tries < max_tries
79
+ begin
80
+ response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/Thermal?$expand=*($levels=1)")
81
+
82
+ if response.status == 200
83
+ data = JSON.parse(response.body)
84
+
85
+ fans = data["Fans"].map do |fan|
86
+ puts "Fan: #{fan["Name"]} > #{fan["Reading"]} > #{fan["Status"]["Health"]}"
87
+ {
88
+ "name" => fan["Name"],
89
+ "rpm" => fan["Reading"],
90
+ "serial" => fan["SerialNumber"],
91
+ "status" => fan["Status"]["Health"]
92
+ }
93
+ end
94
+
95
+ return fans
96
+ elsif response.status.between?(400, 499)
97
+ # Check if system is powered off
98
+ power_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PowerState")
99
+ if power_response.status == 200 && JSON.parse(power_response.body)["PowerState"] == "Off"
100
+ puts "WARN: System is off. Fans are not available.".yellow
101
+ return []
102
+ end
103
+ end
104
+ rescue => e
105
+ puts "WARN: Error getting fans: #{e.message}".yellow
106
+ end
107
+
108
+ tries += 1
109
+ puts "Failed to get fans. Retrying #{tries}/#{max_tries}.".red if tries < max_tries
110
+ sleep 10
111
+ end
112
+
113
+ puts "Failed to get fans after #{max_tries} tries".red
114
+ return []
115
+ end
116
+
117
+ # Get NIC information
118
+ def nics
119
+ response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1/NetworkAdapters?$expand=*($levels=1)")
120
+
121
+ if response.status == 200
122
+ begin
123
+ adapters_data = JSON.parse(response.body)
124
+
125
+ # Determine iDRAC version for different port paths
126
+ idrac_version_response = authenticated_request(:get, "/redfish/v1")
127
+ idrac_version_data = JSON.parse(idrac_version_response.body)
128
+ server = idrac_version_data["RedfishVersion"] || idrac_version_response.headers["server"]
129
+
130
+ is_idrac9 = case server.to_s.downcase
131
+ when /idrac\/9/
132
+ true
133
+ when /idrac\/8/
134
+ false
135
+ when /appweb\/4.5/
136
+ false
137
+ else
138
+ # Default to newer format for unknown versions
139
+ true
140
+ end
141
+
142
+ port_part = is_idrac9 ? 'Ports' : 'NetworkPorts'
143
+
144
+ nics = adapters_data["Members"].map do |adapter|
145
+ path = "#{adapter["@odata.id"].split("v1/").last}/#{port_part}?$expand=*($levels=1)"
146
+ ports_response = authenticated_request(:get, "/redfish/v1/#{path}")
147
+
148
+ if ports_response.status == 200
149
+ ports_data = JSON.parse(ports_response.body)
150
+
151
+ ports = ports_data["Members"].map do |nic|
152
+ if is_idrac9
153
+ link_speed_mbps = nic['CurrentSpeedGbps'].to_i * 1000
154
+ mac_addr = nic['Ethernet']['AssociatedMACAddresses'].first
155
+ port_num = nic['PortId']
156
+ network_technology = nic['LinkNetworkTechnology']
157
+ link_status = nic['LinkStatus'] =~ /up/i ? "Up" : "Down"
158
+ else # iDRAC 8
159
+ link_speed_mbps = nic["SupportedLinkCapabilities"].first["LinkSpeedMbps"]
160
+ mac_addr = nic["AssociatedNetworkAddresses"].first
161
+ port_num = nic["PhysicalPortNumber"]
162
+ network_technology = nic["SupportedLinkCapabilities"].first["LinkNetworkTechnology"]
163
+ link_status = nic['LinkStatus']
164
+ end
165
+
166
+ puts "NIC: #{nic["Id"]} > #{mac_addr} > #{link_status} > #{port_num} > #{link_speed_mbps}Mbps"
167
+
168
+ {
169
+ "name" => nic["Id"],
170
+ "status" => link_status,
171
+ "mac" => mac_addr,
172
+ "port" => port_num,
173
+ "speed_mbps" => link_speed_mbps,
174
+ "kind" => network_technology&.downcase
175
+ }
176
+ end
177
+
178
+ {
179
+ "name" => adapter["Id"],
180
+ "manufacturer" => adapter["Manufacturer"],
181
+ "model" => adapter["Model"],
182
+ "part_number" => adapter["PartNumber"],
183
+ "serial" => adapter["SerialNumber"],
184
+ "ports" => ports
185
+ }
186
+ else
187
+ # Return adapter info without ports if we can't get port details
188
+ {
189
+ "name" => adapter["Id"],
190
+ "manufacturer" => adapter["Manufacturer"],
191
+ "model" => adapter["Model"],
192
+ "part_number" => adapter["PartNumber"],
193
+ "serial" => adapter["SerialNumber"],
194
+ "ports" => []
195
+ }
196
+ end
197
+ end
198
+
199
+ return nics
200
+ rescue JSON::ParserError
201
+ raise Error, "Failed to parse NICs response: #{response.body}"
202
+ end
203
+ else
204
+ raise Error, "Failed to get NICs. Status code: #{response.status}"
205
+ end
206
+ end
207
+
208
+ # Get iDRAC network information
209
+ def idrac_network
210
+ response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/EthernetInterfaces/iDRAC.Embedded.1%23NIC.1")
211
+
212
+ if response.status == 200
213
+ begin
214
+ data = JSON.parse(response.body)
215
+
216
+ idrac = {
217
+ "name" => data["Id"],
218
+ "status" => data["Status"]["Health"] == 'OK' ? 'Up' : 'Down',
219
+ "mac" => data["MACAddress"],
220
+ "mask" => data["IPv4Addresses"].first["SubnetMask"],
221
+ "ipv4" => data["IPv4Addresses"].first["Address"],
222
+ "origin" => data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
223
+ "port" => nil,
224
+ "speed_mbps" => data["SpeedMbps"],
225
+ "kind" => "ethernet"
226
+ }
227
+
228
+ return idrac
229
+ rescue JSON::ParserError
230
+ raise Error, "Failed to parse iDRAC network response: #{response.body}"
231
+ end
232
+ else
233
+ raise Error, "Failed to get iDRAC network. Status code: #{response.status}"
234
+ end
235
+ end
236
+
237
+ # Get PCI device information
238
+ def pci_devices
239
+ response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/PCIeDevices?$expand=*($levels=1)")
240
+
241
+ if response.status == 200
242
+ begin
243
+ data = JSON.parse(response.body)
244
+
245
+ pci = data["Members"].map do |stub|
246
+ manufacturer = stub["Manufacturer"]
247
+
248
+ # Get PCIe function details if available
249
+ pcie_function = nil
250
+ if stub.dig("Links", "PCIeFunctions", 0, "@odata.id")
251
+ pcie_function_path = stub.dig("Links", "PCIeFunctions", 0, "@odata.id").split("v1/").last
252
+ function_response = authenticated_request(:get, "/redfish/v1/#{pcie_function_path}")
253
+
254
+ if function_response.status == 200
255
+ pcie_function = JSON.parse(function_response.body)
256
+ end
257
+ end
258
+
259
+ # Create device info with available data
260
+ device_info = {
261
+ device_class: pcie_function ? pcie_function["DeviceClass"] : nil,
262
+ manufacturer: manufacturer,
263
+ name: stub["Name"],
264
+ description: stub["Description"],
265
+ id: pcie_function ? pcie_function["Id"] : stub["Id"],
266
+ slot_type: pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "SlotType") : nil,
267
+ bus_width: pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "DataBusWidth") : nil,
268
+ nic: pcie_function ? pcie_function.dig("Links", "EthernetInterfaces", 0, "@odata.id") : nil
269
+ }
270
+
271
+ puts "PCI Device: #{device_info[:name]} > #{device_info[:manufacturer]} > #{device_info[:device_class]} > #{device_info[:description]} > #{device_info[:id]}"
272
+
273
+ device_info
274
+ end
275
+
276
+ return pci
277
+ rescue JSON::ParserError
278
+ raise Error, "Failed to parse PCI devices response: #{response.body}"
279
+ end
280
+ else
281
+ raise Error, "Failed to get PCI devices. Status code: #{response.status}"
282
+ end
283
+ end
284
+
285
+ # Map NICs to PCI bus IDs for Mellanox cards
286
+ def nics_to_pci(nics, pci_devices)
287
+ # Filter for Mellanox network controllers
288
+ mellanox_pci = pci_devices.select do |dev|
289
+ dev[:device_class] =~ /NetworkController/ && dev[:manufacturer] =~ /Mellanox/
290
+ end
291
+
292
+ # Create mapping of NIC names to PCI IDs
293
+ mapping = {}
294
+ mellanox_pci.each do |dev|
295
+ if dev[:nic] && dev[:nic] =~ /.*\/([^\/\-]+-\d+)/
296
+ nic = $1 # e.g. NIC.Slot.1-1
297
+ if dev[:id] =~ /^(\d+)-\d+-\d/
298
+ pci_bus = $1 # e.g. 59
299
+ mapping[nic] = pci_bus
300
+ end
301
+ end
302
+ end
303
+
304
+ # Add PCI bus info to each NIC port
305
+ nics_with_pci = nics.map do |nic|
306
+ nic_with_pci = nic.dup
307
+
308
+ if nic_with_pci["ports"]
309
+ nic_with_pci["ports"] = nic_with_pci["ports"].map do |port|
310
+ port_with_pci = port.dup
311
+ pci_bus = mapping[port["name"]]
312
+
313
+ if pci_bus
314
+ port_with_pci["pci"] = pci_bus
315
+ port_with_pci["linux_device"] = "enp#{pci_bus}s0np0" # e.g. enp3s0np0
316
+ end
317
+
318
+ port_with_pci
319
+ end
320
+ end
321
+
322
+ nic_with_pci
323
+ end
324
+
325
+ return nics_with_pci
326
+ end
327
+
328
+ # Get system event logs
329
+ def system_event_logs
330
+ response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Sel?$expand=*($levels=1)")
331
+
332
+ if response.status == 200
333
+ begin
334
+ data = JSON.parse(response.body)
335
+
336
+ logs = data["Members"].map do |log|
337
+ puts "#{log['Id']} : #{log['Created']} : #{log['Message']} : #{log['Severity']}".yellow
338
+ log
339
+ end
340
+
341
+ # Sort by creation date, newest first
342
+ return logs.sort_by { |log| log['Created'] }.reverse
343
+ rescue JSON::ParserError
344
+ raise Error, "Failed to parse system event logs response: #{response.body}"
345
+ end
346
+ else
347
+ raise Error, "Failed to get system event logs. Status code: #{response.status}"
348
+ end
349
+ end
350
+
351
+ # Clear system event logs
352
+ def clear_system_event_logs
353
+ response = authenticated_request(
354
+ :post,
355
+ "/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog",
356
+ body: {}.to_json,
357
+ headers: { 'Content-Type': 'application/json' }
358
+ )
359
+
360
+ if response.status.between?(200, 299)
361
+ puts "System Event Logs cleared".green
362
+ return true
363
+ else
364
+ error_message = "Failed to clear System Event Logs. Status code: #{response.status}"
365
+
366
+ begin
367
+ error_data = JSON.parse(response.body)
368
+ error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
369
+ rescue
370
+ # Ignore JSON parsing errors
371
+ end
372
+
373
+ raise Error, error_message
374
+ end
375
+ end
376
+
377
+ # Get total memory in human-readable format
378
+ def total_memory_human(memory_data)
379
+ total_memory = memory_data.sum { |m| m['capacity_bytes'] }
380
+ "%0.2f GB" % (total_memory.to_f / 1.gigabyte)
381
+ end
382
+ end
383
+ end
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.91"
4
+ VERSION = "0.3.1"
5
5
  end
@@ -0,0 +1,285 @@
1
+ require 'json'
2
+ require 'colorize'
3
+
4
+ module IDRAC
5
+ module VirtualMediaMethods
6
+ # Get current virtual media status
7
+ def virtual_media
8
+ response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/VirtualMedia?$expand=*($levels=1)")
9
+
10
+ if response.status == 200
11
+ begin
12
+ data = JSON.parse(response.body)
13
+
14
+ media = data["Members"].map do |m|
15
+ if m["Inserted"]
16
+ puts "#{m["Name"]} #{m["ConnectedVia"]} #{m["Image"]}".green
17
+ else
18
+ puts "#{m["Name"]} #{m["ConnectedVia"]}".yellow
19
+ end
20
+
21
+ action_path = m.dig("Actions", "#VirtualMedia.InsertMedia", "target")
22
+
23
+ {
24
+ device: m["Id"],
25
+ inserted: m["Inserted"],
26
+ image: m["Image"] || m["ConnectedVia"],
27
+ action_path: action_path
28
+ }
29
+ end
30
+
31
+ return media
32
+ rescue JSON::ParserError
33
+ raise Error, "Failed to parse virtual media response: #{response.body}"
34
+ end
35
+ else
36
+ raise Error, "Failed to get virtual media. Status code: #{response.status}"
37
+ end
38
+ end
39
+
40
+ # Eject virtual media from a device
41
+ def eject_virtual_media(device: "CD")
42
+ media_list = virtual_media
43
+
44
+ # Find the device to eject
45
+ media_to_eject = media_list.find { |m| m[:device] == device && m[:inserted] }
46
+
47
+ if media_to_eject.nil?
48
+ puts "No media #{device} to eject".yellow
49
+ return false
50
+ end
51
+
52
+ puts "Ejecting #{media_to_eject[:device]} #{media_to_eject[:image]}".yellow
53
+
54
+ # Use the action path from the media object if available
55
+ path = if media_to_eject[:action_path]
56
+ media_to_eject[:action_path].sub(/^\/redfish\/v1\//, "").sub(/InsertMedia$/, "EjectMedia")
57
+ else
58
+ "Managers/iDRAC.Embedded.1/VirtualMedia/#{device}/Actions/VirtualMedia.EjectMedia"
59
+ end
60
+
61
+ response = authenticated_request(
62
+ :post,
63
+ "/redfish/v1/#{path}",
64
+ body: {}.to_json,
65
+ headers: { 'Content-Type': 'application/json' }
66
+ )
67
+
68
+ case response.status
69
+ when 200..299
70
+ sleep 5 # Wait for ejection to complete
71
+ puts "Ejected #{media_to_eject[:device]}".green
72
+ return true
73
+ when 500..599
74
+ # Check if the error is "No Virtual Media devices are currently connected"
75
+ begin
76
+ error_data = JSON.parse(response.body)
77
+ if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"] &&
78
+ error_data["error"]["@Message.ExtendedInfo"].any? { |m| m["Message"] =~ /No Virtual Media devices are currently connected/ }
79
+ puts "No Virtual Media devices are currently connected".yellow
80
+ return false
81
+ end
82
+ rescue JSON::ParserError
83
+ # Ignore parsing errors
84
+ end
85
+
86
+ puts "Failed to eject media: #{response.status}".red
87
+ return false
88
+ else
89
+ puts "Unexpected response code: #{response.status}".red
90
+ return false
91
+ end
92
+ end
93
+
94
+ # Insert virtual media (ISO)
95
+ def insert_virtual_media(iso_url, device: "CD")
96
+ raise Error, "Device must be CD or RemovableDisk" unless ["CD", "RemovableDisk"].include?(device)
97
+
98
+ # First eject any inserted media
99
+ eject_virtual_media(device: device)
100
+
101
+ # Firmware version determines which API to use
102
+ firmware_version = get_firmware_version.split(".")[0,2].join.to_i
103
+
104
+ puts "Inserting media: #{iso_url}".yellow
105
+
106
+ tries = 0
107
+ max_tries = 10
108
+
109
+ while tries < max_tries
110
+ begin
111
+ # Different endpoint based on firmware version
112
+ path = if firmware_version >= 600
113
+ "Systems/System.Embedded.1/VirtualMedia/1/Actions/VirtualMedia.InsertMedia"
114
+ else
115
+ "Managers/iDRAC.Embedded.1/VirtualMedia/#{device}/Actions/VirtualMedia.InsertMedia"
116
+ end
117
+
118
+ response = authenticated_request(
119
+ :post,
120
+ "/redfish/v1/#{path}",
121
+ body: { "Image": iso_url, "Inserted": true, "WriteProtected": true }.to_json,
122
+ headers: { 'Content-Type': 'application/json' }
123
+ )
124
+
125
+ if response.status == 204 || response.status == 200
126
+ puts "Inserted media successfully".green
127
+ return true
128
+ end
129
+
130
+ # Handle error responses
131
+ error_message = "Failed to insert media. Status code: #{response.status}"
132
+ begin
133
+ error_data = JSON.parse(response.body)
134
+ if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
135
+ error_info = error_data["error"]["@Message.ExtendedInfo"].first
136
+ error_message += ", Message: #{error_info['Message']}"
137
+ end
138
+ rescue
139
+ # Ignore JSON parsing errors
140
+ end
141
+
142
+ puts "#{error_message}. Retrying (#{tries + 1}/#{max_tries})...".red
143
+ rescue => e
144
+ puts "Error during insert_virtual_media: #{e.message}. Retrying (#{tries + 1}/#{max_tries})...".red
145
+ end
146
+
147
+ tries += 1
148
+ sleep 60 # Wait before retry
149
+ end
150
+
151
+ raise Error, "Failed to insert virtual media after #{max_tries} attempts"
152
+ end
153
+
154
+ # Set boot to virtual media once, then boot from HD
155
+ def set_one_time_virtual_media_boot
156
+ # Check firmware version to determine which API to use
157
+ firmware_version = get_firmware_version.split(".")[0,2].join.to_i
158
+
159
+ if firmware_version >= 440 # Modern iDRAC
160
+ # Check current boot configuration
161
+ boot_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
162
+ if boot_response.status == 200
163
+ boot_data = JSON.parse(boot_response.body)
164
+ enabled = boot_data['Boot']['BootSourceOverrideEnabled']
165
+ target = boot_data['Boot']['BootSourceOverrideTarget']
166
+ puts "Currently override is #{enabled} to boot from #{target}".yellow
167
+ end
168
+
169
+ # Set one-time boot to CD
170
+ response = authenticated_request(
171
+ :patch,
172
+ "/redfish/v1/Systems/System.Embedded.1",
173
+ body: { "Boot": { "BootSourceOverrideTarget": "Cd", "BootSourceOverrideEnabled": "Once" } }.to_json,
174
+ headers: { 'Content-Type': 'application/json' }
175
+ )
176
+
177
+ if response.status.between?(200, 299)
178
+ puts "One-time boot to virtual media configured".green
179
+ return true
180
+ else
181
+ error_message = "Failed to set one-time boot. Status code: #{response.status}"
182
+ begin
183
+ error_data = JSON.parse(response.body)
184
+ if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
185
+ error_info = error_data["error"]["@Message.ExtendedInfo"].first
186
+ error_message += ", Message: #{error_info['Message']}"
187
+ end
188
+ rescue
189
+ # Ignore JSON parsing errors
190
+ end
191
+
192
+ raise Error, error_message
193
+ end
194
+ else
195
+ # For older iDRAC, we need to use the iDRAC-specific method
196
+ payload = {
197
+ "ServerBoot.1#BootOnce": "Enabled",
198
+ "ServerBoot.1#FirstBootDevice": "VCD-DVD"
199
+ }
200
+
201
+ response = authenticated_request(
202
+ :patch,
203
+ "/redfish/v1/Managers/iDRAC.Embedded.1/Attributes",
204
+ body: payload.to_json,
205
+ headers: { 'Content-Type': 'application/json' }
206
+ )
207
+
208
+ if response.status.between?(200, 299)
209
+ puts "One-time boot to virtual media configured".green
210
+ return true
211
+ else
212
+ error_message = "Failed to set one-time boot. Status code: #{response.status}"
213
+ begin
214
+ error_data = JSON.parse(response.body)
215
+ if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"]
216
+ error_info = error_data["error"]["@Message.ExtendedInfo"].first
217
+ error_message += ", Message: #{error_info['Message']}"
218
+ end
219
+ rescue
220
+ # Ignore JSON parsing errors
221
+ end
222
+
223
+ raise Error, error_message
224
+ end
225
+ end
226
+ end
227
+
228
+ # Get current boot source override settings
229
+ def get_boot_source_override
230
+ response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
231
+
232
+ if response.status == 200
233
+ begin
234
+ data = JSON.parse(response.body)
235
+ boot = data["Boot"]
236
+
237
+ puts "Boot Source Override Configuration:".green
238
+ puts " Enabled: #{boot['BootSourceOverrideEnabled']}"
239
+ puts " Target: #{boot['BootSourceOverrideTarget']}"
240
+ puts " Mode: #{boot['BootSourceOverrideMode']}" if boot['BootSourceOverrideMode']
241
+
242
+ if boot["BootSourceOverrideEnabled"] != "Once" || boot["BootSourceOverrideTarget"] == "None"
243
+ return "None"
244
+ else
245
+ return "#{boot['BootSourceOverrideMode']} #{boot['BootSourceOverrideTarget']}"
246
+ end
247
+ rescue JSON::ParserError
248
+ raise Error, "Failed to parse boot source response: #{response.body}"
249
+ end
250
+ else
251
+ raise Error, "Failed to get boot source override. Status code: #{response.status}"
252
+ end
253
+ end
254
+
255
+ private
256
+
257
+ # Get firmware version (helper method for virtual media operations)
258
+ def get_firmware_version
259
+ response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1?$select=FirmwareVersion")
260
+
261
+ if response.status == 200
262
+ begin
263
+ data = JSON.parse(response.body)
264
+ return data["FirmwareVersion"]
265
+ rescue JSON::ParserError
266
+ raise Error, "Failed to parse firmware version response: #{response.body}"
267
+ end
268
+ else
269
+ # Try again without the $select parameter for older firmware
270
+ response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1")
271
+
272
+ if response.status == 200
273
+ begin
274
+ data = JSON.parse(response.body)
275
+ return data["FirmwareVersion"]
276
+ rescue JSON::ParserError
277
+ raise Error, "Failed to parse firmware version response: #{response.body}"
278
+ end
279
+ else
280
+ raise Error, "Failed to get firmware version. Status code: #{response.status}"
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end