idrac 0.4.0 → 0.4.5

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: 04a47e02d37a42167e7007aa92fc8f1d1b4023a52d44bb65ed1132a2035c110a
4
- data.tar.gz: d17c640f8c8c238a6ea6aa4c037f39d2a9c2bfeaaebc478b1a464c75a648bbc4
3
+ metadata.gz: ffcb1d99822a0e7d0015f018b6967e3977d7506438a3e2d917e0615f281aa0e3
4
+ data.tar.gz: 188a912ccdcdb5712ead6b5f181d59902f9b50718e3c3e39e2949f6fbc7ad606
5
5
  SHA512:
6
- metadata.gz: a64a9f69bf757578c9d3fb6d95aeb54b33b4a6604ba432a0b52cdca1e0fdbb2451b19191e2ebbe2468a025648a985a8e5122559f04063db7be8fb20b9a997759
7
- data.tar.gz: 867beff66c1896abe949e87bf65b0c5df5ce65bda9d5e443f31a490792db7a63c737fe1295ea7a85c70a4f43af01ee1d6cc545b43939b581b76ade5b6cd641d1
6
+ metadata.gz: dfd1714f7a86b37991a7493be186043be14a400f6e36d7712c366c1007857eee7eb4ece8390187a56f7faa921f46ffdf9ad1250ff471daf3ad2ccd884c14cda2
7
+ data.tar.gz: 20d5a051050da7a5b75d08529d16e2806af72ac05161393dd1a5cca509f17b2ec497f7a9b2f9c3f0933af72394b81696ae88b48736b6de67bd8ab7ae43319b25
data/bin/idrac CHANGED
@@ -681,22 +681,22 @@ module IDRAC
681
681
 
682
682
  puts "\nVirtual Disks (#{volumes.size}):".green.bold
683
683
  volumes.each do |volume|
684
- capacity_gb = volume[:capacity_bytes].to_f / (1024**3)
685
- health_color = volume[:health] == "OK" ? :green : :yellow
684
+ capacity_gb = volume.capacity_bytes.to_f / (1024**3)
685
+ health_color = volume.health == "OK" ? :green : :yellow
686
686
 
687
- puts "#{volume[:name]}:".bold
688
- puts " RAID Type: #{volume[:raid_level] || volume[:volume_type]}".cyan
689
- puts " Health: #{volume[:health].send(health_color)}"
687
+ puts "#{volume.name}:".bold
688
+ puts " RAID Type: #{volume.raid_level || volume.volume_type}".cyan
689
+ puts " Health: #{volume.health.send(health_color)}"
690
690
  puts " Capacity: #{capacity_gb.round(2)} GB".cyan
691
- puts " Stripe Size: #{volume[:stripe_size]}".cyan
692
- puts " Read Cache: #{volume[:read_cache_policy]}".cyan
693
- puts " Write Cache: #{volume[:write_cache_policy]}".cyan
694
- puts " FastPath: #{volume[:fastpath] == 'enabled' ? 'Enabled'.green : 'Disabled'.yellow}"
695
- puts " Encrypted: #{volume[:encrypted] ? 'Yes'.green : 'No'.yellow}" if volume[:encrypted] != nil
691
+ puts " Stripe Size: #{volume.stripe_size}".cyan
692
+ puts " Read Cache: #{volume.read_cache_policy}".cyan
693
+ puts " Write Cache: #{volume.write_cache_policy}".cyan
694
+ puts " FastPath: #{volume.fastpath == 'enabled' ? 'Enabled'.green : 'Disabled'.yellow}"
695
+ puts " Encrypted: #{volume.encrypted ? 'Yes'.green : 'No'.yellow}" if volume.encrypted != nil
696
696
 
697
- if volume[:progress]
698
- puts " Progress: #{volume[:progress]}%".cyan
699
- puts " Operation: #{volume[:message]}".cyan
697
+ if volume.progress
698
+ puts " Progress: #{volume.progress}%".cyan
699
+ puts " Operation: #{volume.message}".cyan
700
700
  end
701
701
  puts ""
702
702
  end
@@ -742,7 +742,7 @@ module IDRAC
742
742
 
743
743
  puts "Available volumes:".green
744
744
  volumes.each_with_index do |vol, idx|
745
- puts "#{idx+1}. #{vol[:name]} (#{vol[:raid_level] || vol[:volume_type]}, #{(vol[:capacity_bytes].to_f / (1024**3)).round(2)} GB)"
745
+ puts "#{idx+1}. #{vol.name} (#{vol.raid_level || vol.volume_type}, #{(vol.capacity_bytes.to_f / (1024**3)).round(2)} GB)"
746
746
  end
747
747
 
748
748
  print "Enter the number of the volume to delete (or 'q' to quit): "
@@ -757,12 +757,12 @@ module IDRAC
757
757
  if index >= 0 && index < volumes.size
758
758
  volume = volumes[index]
759
759
 
760
- puts "You are about to delete '#{volume[:name]}'. All data will be lost!".red.bold
760
+ puts "You are about to delete '#{volume.name}'. All data will be lost!".red.bold
761
761
  print "Type the volume name to confirm deletion: "
762
762
  confirm = $stdin.gets.chomp
763
763
 
764
764
  if confirm == volume[:name]
765
- client.delete_volume(volume[:"@odata.id"])
765
+ client.delete_volume(volume.data_id)
766
766
  puts "Volume deleted successfully".green
767
767
  else
768
768
  puts "Volume name did not match. Operation cancelled".yellow
@@ -846,15 +846,15 @@ module IDRAC
846
846
 
847
847
  puts "\nMemory Modules (#{memory.size}):".green.bold
848
848
  memory.each do |dimm|
849
- capacity_gb = dimm["capacity_bytes"].to_f / (1024**3)
850
- health_color = dimm["health"] == "OK" ? :green : :red
849
+ capacity_gb = dimm.capacity_bytes.to_f / (1024**3)
850
+ health_color = dimm.health == "OK" ? :green : :red
851
851
 
852
- puts "#{dimm['name']}:".bold
853
- puts " Model: #{dimm['model']}".cyan
854
- puts " Health: #{dimm['health'].send(health_color)}"
852
+ puts "#{dimm.name}:".bold
853
+ puts " Model: #{dimm.model}".cyan
854
+ puts " Health: #{dimm.health.send(health_color)}"
855
855
  puts " Capacity: #{capacity_gb.round(2)} GB".cyan
856
- puts " Speed: #{dimm['speed_mhz']} MHz".cyan
857
- puts " Serial: #{dimm['serial']}".cyan
856
+ puts " Speed: #{dimm.speed_mhz} MHz".cyan
857
+ puts " Serial: #{dimm.serial}".cyan
858
858
  puts ""
859
859
  end
860
860
 
@@ -871,14 +871,14 @@ module IDRAC
871
871
 
872
872
  puts "\nPower Supplies (#{psus.size}):".green.bold
873
873
  psus.each do |psu|
874
- health_color = psu["status"] == "OK" ? :green : :red
874
+ health_color = psu.status == "OK" ? :green : :red
875
875
 
876
- puts "#{psu['name']}:".bold
877
- puts " Model: #{psu['model']}".cyan
878
- puts " Health: #{psu['status'].send(health_color)}"
879
- puts " Input: #{psu['voltage']} V (#{psu['voltage_human']})".cyan
880
- puts " Output: #{psu['watts']} W".cyan
881
- puts " Serial: #{psu['serial']}".cyan
876
+ puts "#{psu.name}:".bold
877
+ puts " Model: #{psu.model}".cyan
878
+ puts " Health: #{psu.status.send(health_color)}"
879
+ puts " Input: #{psu.voltage} V (#{psu.voltage_human})".cyan
880
+ puts " Output: #{psu.watts} W".cyan
881
+ puts " Serial: #{psu.serial}".cyan
882
882
  puts ""
883
883
  end
884
884
  end
@@ -897,11 +897,11 @@ module IDRAC
897
897
 
898
898
  puts "\nFans (#{fans.size}):".green.bold
899
899
  fans.each do |fan|
900
- health_color = fan["status"] == "OK" ? :green : :red
900
+ health_color = fan.status == "OK" ? :green : :red
901
901
 
902
- puts "#{fan['name']}:".bold
903
- puts " Health: #{fan['status'].send(health_color)}"
904
- puts " Speed: #{fan['rpm']} RPM".cyan
902
+ puts "#{fan.name}:".bold
903
+ puts " Health: #{fan.status.send(health_color)}"
904
+ puts " Speed: #{fan.rpm} RPM".cyan
905
905
  puts ""
906
906
  end
907
907
  end
data/idrac.gemspec CHANGED
@@ -41,6 +41,7 @@ Gem::Specification.new do |spec|
41
41
  spec.add_dependency "thor", ">= 1.2.0", "< 1.4.0"
42
42
  spec.add_dependency "base64", "~> 0.1", ">= 0.1.0"
43
43
  spec.add_dependency "colorize", "~> 1.1"
44
+ spec.add_dependency "recursive-open-struct", "~> 1.1"
44
45
 
45
46
  # Development dependencies
46
47
  spec.add_development_dependency "bundler", "~> 2.4", ">= 2.4.0"
data/lib/idrac/client.rb CHANGED
@@ -286,16 +286,6 @@ module IDRAC
286
286
  web.capture_screenshot
287
287
  end
288
288
 
289
- # Parse JSON string and convert to OpenStruct
290
- # @param json [String] JSON string
291
- # @param use_ostruct [Boolean] Whether to convert to OpenStruct (default: true)
292
- # @return [OpenStruct, Hash] Parsed response
293
- def parse_json(json, use_ostruct = true)
294
- parsed = JSON.parse(json)
295
- use_ostruct ? IDRAC::Util.to_ostruct(parsed) : parsed
296
- end
297
-
298
- # Kept for backward compatibility
299
289
  def base_url
300
290
  protocol = use_ssl ? 'https' : 'http'
301
291
  "#{protocol}://#{host}:#{port}"
@@ -304,8 +294,8 @@ module IDRAC
304
294
  def redfish_version
305
295
  response = authenticated_request(:get, "/redfish/v1")
306
296
  if response.status == 200
307
- data = parse_json(response.body)
308
- data.RedfishVersion
297
+ data = JSON.parse(response.body)
298
+ data["RedfishVersion"]
309
299
  else
310
300
  raise Error, "Failed to get Redfish version: #{response.status} - #{response.body}"
311
301
  end
data/lib/idrac/power.rb CHANGED
@@ -30,9 +30,9 @@ module IDRAC
30
30
  break
31
31
  when 409
32
32
  begin
33
- error_data = parse_json(response.body)
34
- if error_data.error && error_data.error['@Message.ExtendedInfo'] &&
35
- error_data.error['@Message.ExtendedInfo'].any? { |m| m.Message =~ /Server is already powered ON/ }
33
+ error_data = JSON.parse(response.body)
34
+ if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"] &&
35
+ error_data["error"]["@Message.ExtendedInfo"].any? { |m| m["Message"] =~ /Server is already powered ON/ }
36
36
  puts "Server is already powered ON.".yellow
37
37
  return false
38
38
  else
@@ -84,9 +84,9 @@ module IDRAC
84
84
  when 409
85
85
  # Conflict -- Server is already off
86
86
  begin
87
- error_data = parse_json(response.body)
88
- if error_data.error && error_data.error['@Message.ExtendedInfo'] &&
89
- error_data.error['@Message.ExtendedInfo'].any? { |m| m.Message =~ /Server is already powered OFF/ }
87
+ error_data = JSON.parse(response.body)
88
+ if error_data["error"] && error_data["error"]["@Message.ExtendedInfo"] &&
89
+ error_data["error"]["@Message.ExtendedInfo"].any? { |m| m["Message"] =~ /Server is already powered OFF/ }
90
90
  puts "Server is already powered OFF.".yellow
91
91
  return false
92
92
  else
@@ -98,8 +98,8 @@ module IDRAC
98
98
  else
99
99
  error_message = "Failed to power off server. Status code: #{response.status}"
100
100
  begin
101
- error_data = parse_json(response.body)
102
- error_message += ", Message: #{error_data.error.message}" if error_data.error && error_data.error.message
101
+ error_data = JSON.parse(response.body)
102
+ error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
103
103
  rescue
104
104
  # Ignore JSON parsing errors
105
105
  end
@@ -137,8 +137,8 @@ module IDRAC
137
137
  else
138
138
  error_message = "Failed to reboot server. Status code: #{response.status}"
139
139
  begin
140
- error_data = parse_json(response.body)
141
- error_message += ", Message: #{error_data.error.message}" if error_data.error && error_data.error.message
140
+ error_data = JSON.parse(response.body)
141
+ error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
142
142
  rescue
143
143
  # Ignore JSON parsing errors
144
144
  end
@@ -156,8 +156,8 @@ module IDRAC
156
156
 
157
157
  if response.status == 200
158
158
  begin
159
- system_data = parse_json(response.body)
160
- return system_data.PowerState
159
+ system_data = JSON.parse(response.body)
160
+ return system_data["PowerState"]
161
161
  rescue JSON::ParserError
162
162
  raise Error, "Failed to parse power state response: #{response.body}"
163
163
  end
@@ -174,8 +174,8 @@ module IDRAC
174
174
 
175
175
  if response.status == 200
176
176
  begin
177
- data = parse_json(response.body)
178
- watts = data.PowerControl[0].PowerConsumedWatts
177
+ data = JSON.parse(response.body)
178
+ watts = data["PowerControl"][0]["PowerConsumedWatts"]
179
179
  # puts "Power usage: #{watts} watts".light_cyan
180
180
  return watts.to_f
181
181
  rescue JSON::ParserError
@@ -184,8 +184,8 @@ module IDRAC
184
184
  else
185
185
  error_message = "Failed to get power usage. Status code: #{response.status}"
186
186
  begin
187
- error_data = parse_json(response.body)
188
- error_message += ", Message: #{error_data.error.message}" if error_data.error && error_data.error.message
187
+ error_data = JSON.parse(response.body)
188
+ error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
189
189
  rescue
190
190
  # Ignore JSON parsing errors
191
191
  end
data/lib/idrac/storage.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'colorize'
3
+ require 'recursive-open-struct'
3
4
 
4
5
  module IDRAC
5
6
  module Storage
@@ -9,24 +10,24 @@ module IDRAC
9
10
  controller_list = controllers
10
11
 
11
12
  puts "Controllers".green
12
- controller_list.each { |c| puts "#{c[:name]} > #{c[:drives_count]}" }
13
+ controller_list.each { |c| puts "#{c.name} > #{c.drives_count}" }
13
14
 
14
15
  puts "Drives".green
15
16
  controller_list.each do |c|
16
- puts "Storage: #{c[:name]} > #{c[:status]} > #{c[:drives_count]}"
17
+ puts "Storage: #{c.name} > #{c.status} > #{c.drives_count}"
17
18
  end
18
19
 
19
20
  # Find the controller with the most drives (usually the PERC)
20
- controller_info = controller_list.max_by { |c| c[:drives_count] }
21
+ controller_info = controller_list.max_by { |c| c.drives_count }
21
22
 
22
- if controller_info[:name] =~ /PERC/
23
- puts "Found #{controller_info[:name]}".green
23
+ if controller_info.name =~ /PERC/
24
+ puts "Found #{controller_info.name}".green
24
25
  else
25
- puts "Found #{controller_info[:name]} but continuing...".yellow
26
+ puts "Found #{controller_info.name} but continuing...".yellow
26
27
  end
27
28
 
28
29
  # Return the raw controller data
29
- controller_info[:raw]
30
+ controller_info.raw
30
31
  end
31
32
 
32
33
  # Get all storage controllers and return them as an array
@@ -37,9 +38,9 @@ module IDRAC
37
38
  begin
38
39
  data = JSON.parse(response.body)
39
40
 
40
- # Transform and return all controllers as an array of hashes with consistent keys
41
+ # Transform and return all controllers as an array of RecursiveOpenStruct objects with consistent keys
41
42
  controllers = data["Members"].map do |controller|
42
- {
43
+ controller_data = {
43
44
  name: controller["Name"],
44
45
  model: controller["Model"],
45
46
  drives_count: controller["Drives"].size,
@@ -51,9 +52,11 @@ module IDRAC
51
52
  pci_slot: controller.dig("Oem", "Dell", "DellController", "PCISlot"),
52
53
  raw: controller
53
54
  }
55
+
56
+ RecursiveOpenStruct.new(controller_data, recurse_over_arrays: true)
54
57
  end
55
58
 
56
- return controllers.sort_by { |c| c[:name] }
59
+ return controllers.sort_by { |c| c.name }
57
60
  rescue JSON::ParserError
58
61
  raise Error, "Failed to parse controllers response: #{response.body}"
59
62
  end
@@ -87,7 +90,7 @@ module IDRAC
87
90
  drives = data["Drives"].map do |body|
88
91
  serial = body["SerialNumber"]
89
92
  serial = body["Identifiers"].first["DurableName"] if serial.blank?
90
- {
93
+ drive_data = {
91
94
  serial: serial,
92
95
  model: body["Model"],
93
96
  name: body["Name"],
@@ -105,9 +108,11 @@ module IDRAC
105
108
  encryption_ability: body["EncryptionAbility"],
106
109
  "@odata.id": body["@odata.id"]
107
110
  }
111
+
112
+ RecursiveOpenStruct.new(drive_data, recurse_over_arrays: true)
108
113
  end
109
114
 
110
- return drives.sort_by { |d| d[:name] }
115
+ return drives.sort_by { |d| d.name }
111
116
  rescue JSON::ParserError
112
117
  raise Error, "Failed to parse drives response: #{response.body}"
113
118
  end
@@ -131,7 +136,7 @@ module IDRAC
131
136
  data = JSON.parse(response.body)
132
137
  volumes = data["Members"].map do |vol|
133
138
  drives = vol["Links"]["Drives"]
134
- volume = {
139
+ volume_data = {
135
140
  name: vol["Name"],
136
141
  capacity_bytes: vol["CapacityBytes"],
137
142
  volume_type: vol["VolumeType"],
@@ -142,31 +147,32 @@ module IDRAC
142
147
  raid_level: vol["RAIDType"],
143
148
  encrypted: vol["Encrypted"],
144
149
  lock_status: vol.dig("Oem", "Dell", "DellVirtualDisk", "LockStatus"),
145
- "@odata.id": vol["@odata.id"]
150
+ odata_id: vol["@odata.id"]
146
151
  }
147
152
 
148
153
  # Check FastPath settings
149
- volume[:fastpath] = fastpath_good?(volume)
154
+ volume_data[:fastpath] = fastpath_good?(vol)
150
155
 
151
156
  # Handle volume operations and status
152
157
  if vol["Operations"].any?
153
- volume[:health] = vol["Status"]["Health"] ? vol["Status"]["Health"] : "N/A"
154
- volume[:progress] = vol["Operations"].first["PercentageComplete"]
155
- volume[:message] = vol["Operations"].first["OperationName"]
158
+ volume_data[:health] = vol["Status"]["Health"] ? vol["Status"]["Health"] : "N/A"
159
+ volume_data[:progress] = vol["Operations"].first["PercentageComplete"]
160
+ volume_data[:message] = vol["Operations"].first["OperationName"]
156
161
  elsif vol["Status"]["Health"] == "OK"
157
- volume[:health] = "OK"
158
- volume[:progress] = nil
159
- volume[:message] = nil
162
+ volume_data[:health] = "OK"
163
+ volume_data[:progress] = nil
164
+ volume_data[:message] = nil
160
165
  else
161
- volume[:health] = "?"
162
- volume[:progress] = nil
163
- volume[:message] = nil
166
+ volume_data[:health] = "?"
167
+ volume_data[:progress] = nil
168
+ volume_data[:message] = nil
164
169
  end
165
170
 
166
- volume
171
+ # Create the RecursiveOpenStruct after all properties are set
172
+ RecursiveOpenStruct.new(volume_data, recurse_over_arrays: true)
167
173
  end
168
174
 
169
- return volumes.sort_by { |d| d[:name] }
175
+ return volumes.sort_by { |d| d.name }
170
176
  rescue JSON::ParserError
171
177
  raise Error, "Failed to parse volumes response: #{response.body}"
172
178
  end
@@ -360,7 +366,7 @@ module IDRAC
360
366
 
361
367
  # Check if all physical disks are Self-Encrypting Drives
362
368
  def all_seds?(drives)
363
- drives.all? { |d| d[:encryption_ability] == "SelfEncryptingDrive" }
369
+ drives.all? { |d| d.encryption_ability == "SelfEncryptingDrive" }
364
370
  end
365
371
 
366
372
  # Check if the system is ready for SED operations
data/lib/idrac/system.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'json'
2
2
  require 'colorize'
3
+ require 'recursive-open-struct'
3
4
 
4
5
  module IDRAC
5
6
  module System
@@ -9,27 +10,29 @@ module IDRAC
9
10
 
10
11
  if response.status == 200
11
12
  begin
12
- data = parse_json(response.body)
13
- memory = data.Members.map do |m|
14
- dimm_name = m.Name # e.g. DIMM A1
13
+ data = JSON.parse(response.body)
14
+ memory = data["Members"].map do |m|
15
+ dimm_name = m["Name"] # e.g. DIMM A1
15
16
  bank, index = /DIMM ([A-Z])(\d+)/.match(dimm_name).captures rescue [nil, nil]
16
17
 
17
- puts "DIMM: #{m.Model} #{m.Name} > #{m.CapacityMiB}MiB > #{m.Status.Health} > #{m.OperatingSpeedMhz}MHz > #{m.PartNumber} / #{m.SerialNumber}"
18
+ puts "DIMM: #{m["Model"]} #{m["Name"]} > #{m["CapacityMiB"]}MiB > #{m["Status"]["Health"]} > #{m["OperatingSpeedMhz"]}MHz > #{m["PartNumber"]} / #{m["SerialNumber"]}"
18
19
 
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
20
+ memory_data = {
21
+ model: m["Model"],
22
+ name: m["Name"],
23
+ capacity_bytes: m["CapacityMiB"].to_i.megabyte,
24
+ health: m["Status"]["Health"] ? m["Status"]["Health"] : "N/A",
25
+ speed_mhz: m["OperatingSpeedMhz"],
26
+ part_number: m["PartNumber"],
27
+ serial: m["SerialNumber"],
28
+ bank: bank,
29
+ index: index.to_i
29
30
  }
31
+
32
+ RecursiveOpenStruct.new(memory_data, recurse_over_arrays: true)
30
33
  end
31
34
 
32
- return memory.sort_by { |a| [a["bank"] || "Z", a["index"] || 999] }
35
+ return memory.sort_by { |m| [m.bank || "Z", m.index || 999] }
33
36
  rescue JSON::ParserError
34
37
  raise Error, "Failed to parse memory response: #{response.body}"
35
38
  end
@@ -44,21 +47,23 @@ module IDRAC
44
47
 
45
48
  if response.status == 200
46
49
  begin
47
- data = parse_json(response.body)
50
+ data = JSON.parse(response.body)
48
51
  puts "Power Supplies".green
49
52
 
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,
53
+ psus = data["PowerSupplies"].map do |psu|
54
+ puts "PSU: #{psu["Name"]} > #{psu["PowerInputWatts"]}W > #{psu["Status"]["Health"]}"
55
+ psu_data = {
56
+ name: psu["Name"],
57
+ voltage: psu["LineInputVoltage"],
58
+ voltage_human: psu["LineInputVoltageType"], # AC240V
59
+ watts: psu["PowerInputWatts"],
60
+ part: psu["PartNumber"],
61
+ model: psu["Model"],
62
+ serial: psu["SerialNumber"],
63
+ status: psu["Status"]["Health"],
61
64
  }
65
+
66
+ RecursiveOpenStruct.new(psu_data, recurse_over_arrays: true)
62
67
  end
63
68
 
64
69
  return psus
@@ -80,23 +85,25 @@ module IDRAC
80
85
  response = authenticated_request(:get, "/redfish/v1/Chassis/System.Embedded.1/Thermal?$expand=*($levels=1)")
81
86
 
82
87
  if response.status == 200
83
- data = parse_json(response.body)
88
+ data = JSON.parse(response.body)
84
89
 
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
90
+ fans = data["Fans"].map do |fan|
91
+ puts "Fan: #{fan["Name"]} > #{fan["Reading"]} > #{fan["Status"]["Health"]}"
92
+ fan_data = {
93
+ name: fan["Name"],
94
+ rpm: fan["Reading"],
95
+ serial: fan["SerialNumber"],
96
+ status: fan["Status"]["Health"]
92
97
  }
98
+
99
+ RecursiveOpenStruct.new(fan_data, recurse_over_arrays: true)
93
100
  end
94
101
 
95
102
  return fans
96
103
  elsif response.status.between?(400, 499)
97
104
  # Check if system is powered off
98
105
  power_response = authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1?$select=PowerState")
99
- if power_response.status == 200 && parse_json(power_response.body).PowerState == "Off"
106
+ if power_response.status == 200 && JSON.parse(power_response.body)["PowerState"] == "Off"
100
107
  puts "WARN: System is off. Fans are not available.".yellow
101
108
  return []
102
109
  end
@@ -120,12 +127,12 @@ module IDRAC
120
127
 
121
128
  if response.status == 200
122
129
  begin
123
- adapters_data = parse_json(response.body)
130
+ adapters_data = JSON.parse(response.body)
124
131
 
125
132
  # Determine iDRAC version for different port paths
126
133
  idrac_version_response = authenticated_request(:get, "/redfish/v1")
127
- idrac_version_data = parse_json(idrac_version_response.body)
128
- server = idrac_version_data.RedfishVersion || idrac_version_response.headers["server"]
134
+ idrac_version_data = JSON.parse(idrac_version_response.body)
135
+ server = idrac_version_data["RedfishVersion"] || idrac_version_response.headers["server"]
129
136
 
130
137
  is_idrac9 = case server.to_s.downcase
131
138
  when /idrac\/9/
@@ -141,32 +148,32 @@ module IDRAC
141
148
 
142
149
  port_part = is_idrac9 ? 'Ports' : 'NetworkPorts'
143
150
 
144
- nics = adapters_data.Members.map do |adapter|
145
- path = "#{adapter['@odata.id'].split("v1/").last}/#{port_part}?$expand=*($levels=1)"
151
+ nics = adapters_data["Members"].map do |adapter|
152
+ path = "#{adapter["@odata.id"].split("v1/").last}/#{port_part}?$expand=*($levels=1)"
146
153
  ports_response = authenticated_request(:get, "/redfish/v1/#{path}")
147
154
 
148
155
  if ports_response.status == 200
149
- ports_data = parse_json(ports_response.body)
156
+ ports_data = JSON.parse(ports_response.body)
150
157
 
151
- ports = ports_data.Members.map do |nic|
158
+ ports = ports_data["Members"].map do |nic|
152
159
  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"
160
+ link_speed_mbps = nic['CurrentSpeedGbps'].to_i * 1000
161
+ mac_addr = nic['Ethernet']['AssociatedMACAddresses'].first
162
+ port_num = nic['PortId']
163
+ network_technology = nic['LinkNetworkTechnology']
164
+ link_status = nic['LinkStatus'] =~ /up/i ? "Up" : "Down"
158
165
  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
166
+ link_speed_mbps = nic["SupportedLinkCapabilities"].first["LinkSpeedMbps"]
167
+ mac_addr = nic["AssociatedNetworkAddresses"].first
168
+ port_num = nic["PhysicalPortNumber"]
169
+ network_technology = nic["SupportedLinkCapabilities"].first["LinkNetworkTechnology"]
170
+ link_status = nic['LinkStatus']
164
171
  end
165
172
 
166
- puts "NIC: #{nic.Id} > #{mac_addr} > #{link_status} > #{port_num} > #{link_speed_mbps}Mbps"
173
+ puts "NIC: #{nic["Id"]} > #{mac_addr} > #{link_status} > #{port_num} > #{link_speed_mbps}Mbps"
167
174
 
168
175
  {
169
- "name" => nic.Id,
176
+ "name" => nic["Id"],
170
177
  "status" => link_status,
171
178
  "mac" => mac_addr,
172
179
  "port" => port_num,
@@ -176,21 +183,21 @@ module IDRAC
176
183
  end
177
184
 
178
185
  {
179
- "name" => adapter.Id,
180
- "manufacturer" => adapter.Manufacturer,
181
- "model" => adapter.Model,
182
- "part_number" => adapter.PartNumber,
183
- "serial" => adapter.SerialNumber,
186
+ "name" => adapter["Id"],
187
+ "manufacturer" => adapter["Manufacturer"],
188
+ "model" => adapter["Model"],
189
+ "part_number" => adapter["PartNumber"],
190
+ "serial" => adapter["SerialNumber"],
184
191
  "ports" => ports
185
192
  }
186
193
  else
187
194
  # Return adapter info without ports if we can't get port details
188
195
  {
189
- "name" => adapter.Id,
190
- "manufacturer" => adapter.Manufacturer,
191
- "model" => adapter.Model,
192
- "part_number" => adapter.PartNumber,
193
- "serial" => adapter.SerialNumber,
196
+ "name" => adapter["Id"],
197
+ "manufacturer" => adapter["Manufacturer"],
198
+ "model" => adapter["Model"],
199
+ "part_number" => adapter["PartNumber"],
200
+ "serial" => adapter["SerialNumber"],
194
201
  "ports" => []
195
202
  }
196
203
  end
@@ -211,17 +218,17 @@ module IDRAC
211
218
 
212
219
  if response.status == 200
213
220
  begin
214
- data = parse_json(response.body)
221
+ data = JSON.parse(response.body)
215
222
 
216
223
  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
224
+ "name" => data["Id"],
225
+ "status" => data["Status"]["Health"] == 'OK' ? 'Up' : 'Down',
226
+ "mac" => data["MACAddress"],
227
+ "mask" => data["IPv4Addresses"].first["SubnetMask"],
228
+ "ipv4" => data["IPv4Addresses"].first["Address"],
229
+ "origin" => data["IPv4Addresses"].first["AddressOrigin"], # DHCP or Static
223
230
  "port" => nil,
224
- "speed_mbps" => data.SpeedMbps,
231
+ "speed_mbps" => data["SpeedMbps"],
225
232
  "kind" => "ethernet"
226
233
  }
227
234
 
@@ -240,32 +247,32 @@ module IDRAC
240
247
 
241
248
  if response.status == 200
242
249
  begin
243
- data = parse_json(response.body)
250
+ data = JSON.parse(response.body)
244
251
 
245
- pci = data.Members.map do |stub|
246
- manufacturer = stub.Manufacturer
252
+ pci = data["Members"].map do |stub|
253
+ manufacturer = stub["Manufacturer"]
247
254
 
248
255
  # Get PCIe function details if available
249
256
  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
257
+ if stub.dig("Links", "PCIeFunctions", 0, "@odata.id")
258
+ pcie_function_path = stub.dig("Links", "PCIeFunctions", 0, "@odata.id").split("v1/").last
252
259
  function_response = authenticated_request(:get, "/redfish/v1/#{pcie_function_path}")
253
260
 
254
261
  if function_response.status == 200
255
- pcie_function = parse_json(function_response.body)
262
+ pcie_function = JSON.parse(function_response.body)
256
263
  end
257
264
  end
258
265
 
259
266
  # Create device info with available data
260
267
  device_info = {
261
- device_class: pcie_function ? pcie_function.DeviceClass : nil,
268
+ device_class: pcie_function ? pcie_function["DeviceClass"] : nil,
262
269
  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
270
+ name: stub["Name"],
271
+ description: stub["Description"],
272
+ id: pcie_function ? pcie_function["Id"] : stub["Id"],
273
+ slot_type: pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "SlotType") : nil,
274
+ bus_width: pcie_function ? pcie_function.dig("Oem", "Dell", "DellPCIeFunction", "DataBusWidth") : nil,
275
+ nic: pcie_function ? pcie_function.dig("Links", "EthernetInterfaces", 0, "@odata.id") : nil
269
276
  }
270
277
 
271
278
  puts "PCI Device: #{device_info[:name]} > #{device_info[:manufacturer]} > #{device_info[:device_class]} > #{device_info[:description]} > #{device_info[:id]}"
@@ -331,15 +338,15 @@ module IDRAC
331
338
 
332
339
  if response.status == 200
333
340
  begin
334
- data = parse_json(response.body)
341
+ data = JSON.parse(response.body)
335
342
 
336
- logs = data.Members.map do |log|
337
- puts "#{log.Id} : #{log.Created} : #{log.Message} : #{log.Severity}".yellow
343
+ logs = data["Members"].map do |log|
344
+ puts "#{log['Id']} : #{log['Created']} : #{log['Message']} : #{log['Severity']}".yellow
338
345
  log
339
346
  end
340
347
 
341
348
  # Sort by creation date, newest first
342
- return logs.sort_by { |log| log.Created }.reverse
349
+ return logs.sort_by { |log| log['Created'] }.reverse
343
350
  rescue JSON::ParserError
344
351
  raise Error, "Failed to parse system event logs response: #{response.body}"
345
352
  end
@@ -364,7 +371,7 @@ module IDRAC
364
371
  error_message = "Failed to clear System Event Logs. Status code: #{response.status}"
365
372
 
366
373
  begin
367
- error_data = parse_json(response.body, false)
374
+ error_data = JSON.parse(response.body)
368
375
  error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
369
376
  rescue
370
377
  # Ignore JSON parsing errors
@@ -376,7 +383,7 @@ module IDRAC
376
383
 
377
384
  # Get total memory in human-readable format
378
385
  def total_memory_human(memory_data)
379
- total_memory = memory_data.sum { |m| m['capacity_bytes'] }
386
+ total_memory = memory_data.sum { |m| m.capacity_bytes }
380
387
  "%0.2f GB" % (total_memory.to_f / 1.gigabyte)
381
388
  end
382
389
  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.4.0"
4
+ VERSION = "0.4.5"
5
5
  end
@@ -9,25 +9,31 @@ module IDRAC
9
9
 
10
10
  if response.status == 200
11
11
  begin
12
- data = parse_json(response.body)
12
+ data = JSON.parse(response.body)
13
13
 
14
- media = data.Members.map do |m|
15
- {
16
- "id" => m.Id,
17
- "name" => m.Name,
18
- "media_types" => m.MediaTypes,
19
- "connected" => m.Inserted,
20
- "image" => m.Image,
21
- "write_protected" => m.WriteProtected
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
22
28
  }
23
29
  end
24
30
 
25
31
  return media
26
- rescue => e
27
- raise Error, "Failed to get virtual media status: #{e.message}"
32
+ rescue JSON::ParserError
33
+ raise Error, "Failed to parse virtual media response: #{response.body}"
28
34
  end
29
35
  else
30
- raise Error, "Failed to get virtual media status. Status code: #{response.status}"
36
+ raise Error, "Failed to get virtual media. Status code: #{response.status}"
31
37
  end
32
38
  end
33
39
 
data/lib/idrac.rb CHANGED
@@ -7,7 +7,7 @@ require 'faraday/multipart'
7
7
  require 'base64'
8
8
  require 'uri'
9
9
  require 'colorize'
10
- require 'ostruct'
10
+ require 'recursive-open-struct'
11
11
  # If dev, required debug
12
12
  require 'debug' if ENV['RUBY_ENV'] == 'development'
13
13
 
@@ -30,23 +30,6 @@ module IDRAC
30
30
  end
31
31
  end
32
32
 
33
- # Utility methods for the IDRAC module
34
- module Util
35
- # Recursively converts a hash to an OpenStruct
36
- # @param obj [Hash, Array, Object] The object to convert
37
- # @return [OpenStruct, Array, Object] The converted object
38
- def self.to_ostruct(obj)
39
- case obj
40
- when Hash
41
- OpenStruct.new(obj.transform_values { |val| to_ostruct(val) })
42
- when Array
43
- obj.map { |item| to_ostruct(item) }
44
- else
45
- obj
46
- end
47
- end
48
- end
49
-
50
33
  class Error < StandardError; end
51
34
 
52
35
  def self.new(options = {})
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.4.0
4
+ version: 0.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel
@@ -144,6 +144,20 @@ dependencies:
144
144
  - - "~>"
145
145
  - !ruby/object:Gem::Version
146
146
  version: '1.1'
147
+ - !ruby/object:Gem::Dependency
148
+ name: recursive-open-struct
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - "~>"
152
+ - !ruby/object:Gem::Version
153
+ version: '1.1'
154
+ type: :runtime
155
+ prerelease: false
156
+ version_requirements: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - "~>"
159
+ - !ruby/object:Gem::Version
160
+ version: '1.1'
147
161
  - !ruby/object:Gem::Dependency
148
162
  name: bundler
149
163
  requirement: !ruby/object:Gem::Requirement