idrac 0.1.26 → 0.1.28

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: 4d751ffb6e590b80a6a5dc72457d8a4be11213224a13ebeae50698ae9f6cafff
4
- data.tar.gz: 5a3f826850bf9aee8e40287ff7f8ab7c5bd71944ddce2b183dbe88dabed9c3fa
3
+ metadata.gz: 180bec6c43d2aced138c9479bbbfe23cec04c6149d2cd66343b5a738c0ce479f
4
+ data.tar.gz: 10fae4fc88a7d1ba456e3f7dc7d2efbfb8c7500b7839e7b59a2e27d5dae53c7c
5
5
  SHA512:
6
- metadata.gz: 553bef804c000680be8ecadb15c19f2b4a335db5a7a44b5071ccb230f168880268e906f5a296a70c8659a5bbbb14a0ffb3a6f56cfc4c9ca216e0a1f8a6024a0f
7
- data.tar.gz: ff4d0ce0967de6fbddd425844e0c86165276c44d508a2b8de41a20ec54e3998f7d2f1d5aa7cadf34201dd26113bd0abafb1e01d13439edbcb1d22f273e31a1f5
6
+ metadata.gz: '0990d968c95f92c2b410b8338c88e8bff464089cc828f9bd49c2fa8bb8111aff7f059fc68d8ff958ccf9c2036af6aebcd2614b96b4ef757bbf93d59f47eecba5'
7
+ data.tar.gz: bfea9fe661ac565bc5ee652204a927a4c19977abef08bdd51ddf0c79027cc9f40c24d3b21522a9247d8933c22499f99b2bea73be02e0e647f81b42bf7ef2ec53
data/README.md CHANGED
@@ -37,8 +37,10 @@ idrac screenshot --host=192.168.1.100 --username=root --password=calvin
37
37
  # Specify a custom output filename
38
38
  idrac screenshot --host=192.168.1.100 --username=root --password=calvin --output=my_screenshot.png
39
39
 
40
- # Download the Dell firmware catalog
41
- idrac firmware:catalog --host=192.168.1.100 --username=root --password=calvin
40
+ # Download the Dell firmware catalog (no host required)
41
+ idrac catalog download
42
+ # or
43
+ idrac firmware:catalog
42
44
 
43
45
  # Check firmware status and available updates
44
46
  idrac firmware:status --host=192.168.1.100 --username=root --password=calvin
@@ -93,8 +95,9 @@ puts "Screenshot saved to: #{filename}"
93
95
  # Firmware operations
94
96
  firmware = IDRAC::Firmware.new(client)
95
97
 
96
- # Download catalog
97
- catalog_path = firmware.download_catalog
98
+ # Download catalog (no client required)
99
+ catalog = IDRAC::FirmwareCatalog.new
100
+ catalog_path = catalog.download
98
101
 
99
102
  # Get system inventory
100
103
  inventory = firmware.get_system_inventory
@@ -127,6 +130,21 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
127
130
 
128
131
  ## Changelog
129
132
 
133
+ ### Version 0.1.28
134
+ - **Improved Firmware Update Checking**: Completely redesigned the firmware update checking process
135
+ - Added a dedicated `FirmwareCatalog` class for better separation of concerns
136
+ - Improved component matching with more accurate detection of available updates
137
+ - Enhanced output format with a tabular display showing component details, versions, and update status
138
+ - Added system model detection for more accurate firmware matching
139
+ - Improved version comparison logic for different version formats (numeric, Dell A00 format, etc.)
140
+ - Better handling of network adapters and other component types
141
+
142
+ ### Version 0.1.27
143
+ - **Removed Host Requirement for Catalog Download**: The `catalog download` and `firmware:catalog` commands no longer require the `--host` parameter
144
+ - Added a dedicated `catalog` command that can be used directly: `idrac catalog download`
145
+ - The catalog download functionality can now be used without an iDRAC connection
146
+ - Updated the Ruby API to support catalog downloads without a client
147
+
130
148
  ### Version 0.1.26
131
149
  - **Improved Redfish Session Creation**: Fixed issues with the Redfish session creation process
132
150
  - Added multiple fallback methods for creating sessions with different iDRAC versions
data/bin/idrac CHANGED
@@ -12,6 +12,23 @@ require "thor"
12
12
  require "idrac"
13
13
 
14
14
  module IDRAC
15
+ # Standalone catalog command that doesn't require a host
16
+ class CatalogCommand < Thor
17
+ desc "download [DIRECTORY]", "Download Dell firmware catalog"
18
+ def download(directory = nil)
19
+ # Create a FirmwareCatalog instance
20
+ catalog = IDRAC::FirmwareCatalog.new
21
+
22
+ begin
23
+ catalog_path = catalog.download(directory)
24
+ puts "Catalog downloaded to: #{catalog_path}"
25
+ rescue IDRAC::Error => e
26
+ puts "Error: #{e.message}"
27
+ exit 1
28
+ end
29
+ end
30
+ end
31
+
15
32
  class CLI < Thor
16
33
  class_option :host, type: :string, required: true, desc: "iDRAC host address"
17
34
  class_option :username, type: :string, required: false, default: "root", desc: "iDRAC username (default: root)"
@@ -42,19 +59,8 @@ module IDRAC
42
59
 
43
60
  desc "firmware:catalog [DIRECTORY]", "Download Dell firmware catalog"
44
61
  def firmware_catalog(directory = nil)
45
- check_ssl_verification
46
- client = create_client
47
- firmware = IDRAC::Firmware.new(client)
48
-
49
- begin
50
- catalog_path = firmware.download_catalog(directory)
51
- puts "Catalog downloaded to: #{catalog_path}"
52
- rescue IDRAC::Error => e
53
- puts "Error: #{e.message}"
54
- exit 1
55
- ensure
56
- client.logout
57
- end
62
+ # Forward to the catalog command
63
+ CatalogCommand.new.download(directory)
58
64
  end
59
65
 
60
66
  desc "firmware:status", "Show current firmware status and available updates"
@@ -85,20 +91,15 @@ module IDRAC
85
91
  # Check for updates if catalog is available
86
92
  if options[:catalog] || File.exist?(default_catalog)
87
93
  catalog_path = options[:catalog] || default_catalog
88
- puts "\nChecking for updates using catalog: #{catalog_path}"
89
94
 
95
+ # Check for updates using the firmware class
90
96
  updates = firmware.check_updates(catalog_path)
91
97
 
92
98
  if updates.empty?
93
99
  puts "No updates available."
94
- else
95
- puts "\nAvailable Updates:"
96
- updates.each do |update|
97
- puts " #{update[:name]}: #{update[:current_version]} -> #{update[:available_version]}"
98
- end
99
100
  end
100
101
  else
101
- puts "\nTo check for updates, download the catalog first with 'idrac firmware:catalog'"
102
+ puts "\nTo check for updates, download the catalog first with 'idrac catalog download'"
102
103
  end
103
104
  rescue IDRAC::Error => e
104
105
  puts "Error: #{e.message}"
@@ -129,7 +130,8 @@ module IDRAC
129
130
  # If still no catalog, download it
130
131
  if catalog_path.nil?
131
132
  puts "No catalog found. Downloading..."
132
- catalog_path = firmware.download_catalog
133
+ catalog = IDRAC::FirmwareCatalog.new
134
+ catalog_path = catalog.download
133
135
  end
134
136
 
135
137
  firmware.interactive_update(catalog_path)
@@ -198,4 +200,18 @@ module IDRAC
198
200
  end
199
201
  end
200
202
 
201
- IDRAC::CLI.start(ARGV)
203
+ # Create a separate CLI class for commands that don't require a host
204
+ module IDRAC
205
+ class StandaloneCLI < Thor
206
+ # Register the catalog command
207
+ desc "catalog", "Download Dell firmware catalog"
208
+ subcommand "catalog", CatalogCommand
209
+ end
210
+ end
211
+
212
+ # Check if the first argument is 'catalog'
213
+ if ARGV[0] == 'catalog'
214
+ IDRAC::StandaloneCLI.start(ARGV)
215
+ else
216
+ IDRAC::CLI.start(ARGV)
217
+ end
@@ -0,0 +1,3 @@
1
+ module IDRAC
2
+ class Error < StandardError; end
3
+ end
@@ -5,6 +5,7 @@ require 'json'
5
5
  require 'nokogiri'
6
6
  require 'fileutils'
7
7
  require 'securerandom'
8
+ require_relative 'firmware_catalog'
8
9
 
9
10
  module IDRAC
10
11
  class Firmware
@@ -22,6 +23,9 @@ module IDRAC
22
23
  raise Error, "Firmware file not found: #{firmware_path}"
23
24
  end
24
25
 
26
+ # Ensure we have a client
27
+ raise Error, "Client is required for firmware update" unless client
28
+
25
29
  # Login to iDRAC
26
30
  client.login unless client.instance_variable_get(:@session_id)
27
31
 
@@ -37,43 +41,15 @@ module IDRAC
37
41
  end
38
42
 
39
43
  def download_catalog(output_dir = nil)
40
- # Use ~/.idrac as the default directory
41
- output_dir ||= File.expand_path("~/.idrac")
42
- FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
43
-
44
- catalog_gz_path = File.join(output_dir, "Catalog.xml.gz")
45
- catalog_path = File.join(output_dir, "Catalog.xml")
46
-
47
- puts "Downloading Dell catalog from #{CATALOG_URL}..."
48
-
49
- uri = URI.parse(CATALOG_URL)
50
- Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
51
- request = Net::HTTP::Get.new(uri)
52
- http.request(request) do |response|
53
- if response.code == "200"
54
- File.open(catalog_gz_path, 'wb') do |file|
55
- response.read_body do |chunk|
56
- file.write(chunk)
57
- end
58
- end
59
- else
60
- raise Error, "Failed to download catalog: #{response.code} #{response.message}"
61
- end
62
- end
63
- end
64
-
65
- puts "Extracting catalog..."
66
- system("gunzip -f #{catalog_gz_path}")
67
-
68
- if File.exist?(catalog_path)
69
- puts "Catalog downloaded and extracted to #{catalog_path}"
70
- return catalog_path
71
- else
72
- raise Error, "Failed to extract catalog"
73
- end
44
+ # Use the new FirmwareCatalog class
45
+ catalog = FirmwareCatalog.new
46
+ catalog.download(output_dir)
74
47
  end
75
48
 
76
49
  def get_system_inventory
50
+ # Ensure we have a client
51
+ raise Error, "Client is required for system inventory" unless client
52
+
77
53
  puts "Retrieving system inventory..."
78
54
 
79
55
  # Get basic system information
@@ -133,148 +109,112 @@ module IDRAC
133
109
  end
134
110
 
135
111
  def check_updates(catalog_path = nil)
112
+ # Ensure we have a client for system inventory
113
+ raise Error, "Client is required for checking updates" unless client
114
+
136
115
  # Download catalog if not provided
137
116
  catalog_path ||= download_catalog
138
117
 
139
118
  # Get system inventory
140
119
  inventory = get_system_inventory
141
120
 
142
- # Parse catalog
143
- catalog_doc = File.open(catalog_path) { |f| Nokogiri::XML(f) }
121
+ # Create a FirmwareCatalog instance
122
+ catalog = FirmwareCatalog.new(catalog_path)
144
123
 
145
- # Extract service tag
124
+ # Extract system information
125
+ system_model = inventory[:system][:model]
146
126
  service_tag = inventory[:system][:service_tag]
147
127
 
148
128
  puts "Checking updates for system with service tag: #{service_tag}"
129
+ puts "Searching for updates for model: #{system_model}"
149
130
 
150
- # Find applicable updates
151
- updates = []
131
+ # Find system models in the catalog
132
+ models = catalog.find_system_models(system_model.split.first)
152
133
 
153
- # Get current firmware versions
154
- current_versions = {}
155
- inventory[:firmware].each do |fw|
156
- # Use the ID as the key to avoid duplicates
157
- current_versions[fw[:id]] = {
158
- name: fw[:name],
159
- version: fw[:version],
160
- updateable: fw[:updateable],
161
- identifiers: extract_identifiers(fw[:name]) # Extract identifiers for better matching
162
- }
134
+ if models.empty?
135
+ puts "No matching system model found in catalog"
136
+ return []
163
137
  end
164
138
 
165
- # Find matching components in catalog
166
- catalog_doc.xpath('//SoftwareComponent').each do |component|
167
- name = component.at_xpath('Name')&.text
168
- version = component.at_xpath('Version')&.text
169
- path = component.at_xpath('Path')&.text
170
- component_type = component.at_xpath('ComponentType')&.text
171
-
172
- next unless name && version && path # Skip if missing essential data
173
-
174
- # Extract identifiers from catalog component name for better matching
175
- catalog_identifiers = extract_identifiers(name)
176
-
177
- # Check if this component matches any of our firmware
178
- # We'll track if we found a match to avoid duplicates
179
- matched = false
139
+ # Use the first matching model
140
+ model = models.first
141
+ puts "Found system IDs for #{model[:name]}: #{model[:id]}"
142
+
143
+ # Find updates for this system
144
+ catalog_updates = catalog.find_updates_for_system(model[:id])
145
+ puts "Found #{catalog_updates.size} firmware updates for #{model[:name]}"
146
+
147
+ # Compare current firmware with available updates
148
+ updates = []
149
+
150
+ # Print header for firmware comparison table
151
+ puts "\nFirmware Version Comparison:"
152
+ puts "=" * 100
153
+ puts "%-30s %-20s %-20s %-10s %-15s %-20s" % ["Component", "Current Version", "Available Version", "Updateable", "Category", "Status"]
154
+ puts "-" * 100
155
+
156
+ # Process each firmware component
157
+ inventory[:firmware].each do |fw|
158
+ # Find matching updates in catalog
159
+ matching_updates = catalog_updates.select do |update|
160
+ catalog.match_component(fw[:name], update[:name])
161
+ end
180
162
 
181
- current_versions.each do |id, fw_info|
182
- # Skip if not updateable
183
- next unless fw_info[:updateable]
184
-
185
- # Normalize names for comparison
186
- catalog_name = name.downcase.strip
187
- firmware_name = fw_info[:name].downcase.strip
188
-
189
- # Check for matches using multiple strategies
190
- match_found = false
163
+ if matching_updates.any?
164
+ # Use the first matching update
165
+ update = matching_updates.first
191
166
 
192
- # 1. Check if names contain each other
193
- if catalog_name.include?(firmware_name) || firmware_name.include?(catalog_name)
194
- match_found = true
195
- end
196
-
197
- # 2. Check if BIOS components match
198
- if (catalog_name.include?("bios") && firmware_name.include?("bios"))
199
- match_found = true
200
- end
167
+ # Check if version is newer
168
+ needs_update = catalog.compare_versions(fw[:version], update[:version])
201
169
 
202
- # 3. Check if identifiers match
203
- if !match_found && !fw_info[:identifiers].empty? && !catalog_identifiers.empty?
204
- # Check if any identifier from firmware matches any identifier from catalog
205
- if (fw_info[:identifiers] & catalog_identifiers).any?
206
- match_found = true
207
- end
208
- end
209
-
210
- # If we found a match and versions differ, add to updates
211
- if match_found && !matched && version != fw_info[:version]
170
+ # Add to updates list if needed
171
+ if needs_update && fw[:updateable]
212
172
  updates << {
213
- name: name,
214
- current_version: fw_info[:version],
215
- available_version: version,
216
- path: path,
217
- component_type: component_type,
218
- download_url: "https://downloads.dell.com/#{path}"
173
+ name: fw[:name],
174
+ current_version: fw[:version],
175
+ available_version: update[:version],
176
+ path: update[:path],
177
+ component_type: update[:component_type],
178
+ category: update[:category],
179
+ download_url: update[:download_url]
219
180
  }
220
181
 
221
- # Mark as matched to avoid duplicates
222
- matched = true
182
+ # Print row with update available
183
+ puts "%-30s %-20s %-20s %-10s %-15s %-20s" % [
184
+ fw[:name][0..29],
185
+ fw[:version],
186
+ update[:version],
187
+ fw[:updateable] ? "Yes" : "No",
188
+ update[:category] || "N/A",
189
+ "UPDATE AVAILABLE"
190
+ ]
191
+ else
192
+ # Print row with no update available
193
+ puts "%-30s %-20s %-20s %-10s %-15s %-20s" % [
194
+ fw[:name][0..29],
195
+ fw[:version],
196
+ update[:version] || "N/A",
197
+ fw[:updateable] ? "Yes" : "No",
198
+ update[:category] || "N/A",
199
+ "No update available"
200
+ ]
223
201
  end
202
+ else
203
+ # No matching update found
204
+ puts "%-30s %-20s %-20s %-10s %-15s %-20s" % [
205
+ fw[:name][0..29],
206
+ fw[:version],
207
+ "N/A",
208
+ fw[:updateable] ? "Yes" : "No",
209
+ "N/A",
210
+ "No update available"
211
+ ]
224
212
  end
225
213
  end
226
214
 
227
215
  updates
228
216
  end
229
217
 
230
- def extract_identifiers(name)
231
- return [] unless name
232
-
233
- identifiers = []
234
-
235
- # Extract model numbers like X520, I350, etc.
236
- model_matches = name.scan(/[IX]\d{3,4}/)
237
- identifiers.concat(model_matches)
238
-
239
- # Extract PERC model like H730
240
- perc_matches = name.scan(/[HP]\d{3,4}/)
241
- identifiers.concat(perc_matches)
242
-
243
- # Extract other common identifiers
244
- if name.include?("NIC") || name.include?("Ethernet") || name.include?("Network")
245
- identifiers << "NIC"
246
- end
247
-
248
- if name.include?("PERC") || name.include?("RAID")
249
- identifiers << "PERC"
250
- # Extract PERC model like H730
251
- perc_match = name.match(/PERC\s+([A-Z]\d{3})/)
252
- identifiers << perc_match[1] if perc_match
253
- end
254
-
255
- if name.include?("BIOS")
256
- identifiers << "BIOS"
257
- end
258
-
259
- if name.include?("iDRAC") || name.include?("IDRAC") || name.include?("Remote Access Controller")
260
- identifiers << "iDRAC"
261
- end
262
-
263
- if name.include?("Power Supply") || name.include?("PSU")
264
- identifiers << "PSU"
265
- end
266
-
267
- if name.include?("Lifecycle Controller")
268
- identifiers << "LC"
269
- end
270
-
271
- if name.include?("CPLD")
272
- identifiers << "CPLD"
273
- end
274
-
275
- identifiers
276
- end
277
-
278
218
  def interactive_update(catalog_path = nil)
279
219
  updates = check_updates(catalog_path)
280
220
 
@@ -0,0 +1,243 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'fileutils'
4
+ require 'nokogiri'
5
+
6
+ module IDRAC
7
+ class FirmwareCatalog
8
+ CATALOG_URL = "https://downloads.dell.com/catalog/Catalog.xml.gz"
9
+
10
+ attr_reader :catalog_path
11
+
12
+ def initialize(catalog_path = nil)
13
+ @catalog_path = catalog_path
14
+ end
15
+
16
+ def download(output_dir = nil)
17
+ # Use ~/.idrac as the default directory
18
+ output_dir ||= File.expand_path("~/.idrac")
19
+ FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
20
+
21
+ catalog_gz_path = File.join(output_dir, "Catalog.xml.gz")
22
+ catalog_path = File.join(output_dir, "Catalog.xml")
23
+
24
+ puts "Downloading Dell catalog from #{CATALOG_URL}..."
25
+
26
+ uri = URI.parse(CATALOG_URL)
27
+ Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
28
+ request = Net::HTTP::Get.new(uri)
29
+ http.request(request) do |response|
30
+ if response.code == "200"
31
+ File.open(catalog_gz_path, 'wb') do |file|
32
+ response.read_body do |chunk|
33
+ file.write(chunk)
34
+ end
35
+ end
36
+ else
37
+ raise Error, "Failed to download catalog: #{response.code} #{response.message}"
38
+ end
39
+ end
40
+ end
41
+
42
+ puts "Extracting catalog..."
43
+ system("gunzip -f #{catalog_gz_path}")
44
+
45
+ if File.exist?(catalog_path)
46
+ puts "Catalog downloaded and extracted to #{catalog_path}"
47
+ @catalog_path = catalog_path
48
+ return catalog_path
49
+ else
50
+ raise Error, "Failed to extract catalog"
51
+ end
52
+ end
53
+
54
+ def parse
55
+ raise Error, "No catalog path specified" unless @catalog_path
56
+ raise Error, "Catalog file not found: #{@catalog_path}" unless File.exist?(@catalog_path)
57
+
58
+ File.open(@catalog_path) { |f| Nokogiri::XML(f) }
59
+ end
60
+
61
+ def find_system_models(model_name)
62
+ doc = parse
63
+ models = []
64
+
65
+ # Extract just the model number for PowerEdge servers (e.g., R640 from PowerEdge R640)
66
+ model_code = nil
67
+ if model_name.include?("PowerEdge")
68
+ model_code = model_name.split.last
69
+ end
70
+
71
+ doc.xpath('//SupportedSystems/Brand/Model').each do |model|
72
+ name = model.at_xpath('Display')&.text
73
+ code = model.at_xpath('Code')&.text
74
+
75
+ # Try to match by full name
76
+ if name && name.downcase.include?(model_name.downcase)
77
+ models << {
78
+ name: name,
79
+ code: code,
80
+ id: model['id']
81
+ }
82
+ # Try to match by model code (e.g., R640)
83
+ elsif model_code && code && code.downcase == model_code.downcase
84
+ models << {
85
+ name: name,
86
+ code: code,
87
+ id: model['id']
88
+ }
89
+ end
90
+ end
91
+
92
+ models
93
+ end
94
+
95
+ def find_updates_for_system(system_id)
96
+ doc = parse
97
+ updates = []
98
+
99
+ doc.xpath("//SoftwareComponent[SupportedSystems/Brand/Model[@id='#{system_id}']]").each do |component|
100
+ name = component.at_xpath('Name')&.text
101
+ version = component.at_xpath('Version')&.text
102
+ path = component.at_xpath('Path')&.text
103
+ component_type = component.at_xpath('ComponentType')&.text
104
+ category = component.at_xpath('Category')&.text
105
+
106
+ next unless name && version && path
107
+
108
+ updates << {
109
+ name: name,
110
+ version: version,
111
+ path: path,
112
+ component_type: component_type,
113
+ category: category,
114
+ download_url: "https://downloads.dell.com/#{path}"
115
+ }
116
+ end
117
+
118
+ updates
119
+ end
120
+
121
+ def extract_identifiers(name)
122
+ return [] unless name
123
+
124
+ identifiers = []
125
+
126
+ # Extract model numbers like X520, I350, etc.
127
+ model_matches = name.scan(/[IX]\d{3,4}/)
128
+ identifiers.concat(model_matches)
129
+
130
+ # Extract PERC model like H730
131
+ perc_matches = name.scan(/[HP]\d{3,4}/)
132
+ identifiers.concat(perc_matches)
133
+
134
+ # Extract other common identifiers
135
+ if name.include?("NIC") || name.include?("Ethernet") || name.include?("Network")
136
+ identifiers << "NIC"
137
+ end
138
+
139
+ if name.include?("PERC") || name.include?("RAID")
140
+ identifiers << "PERC"
141
+ # Extract PERC model like H730
142
+ perc_match = name.match(/PERC\s+([A-Z]\d{3})/)
143
+ identifiers << perc_match[1] if perc_match
144
+ end
145
+
146
+ if name.include?("BIOS")
147
+ identifiers << "BIOS"
148
+ end
149
+
150
+ if name.include?("iDRAC") || name.include?("IDRAC") || name.include?("Remote Access Controller")
151
+ identifiers << "iDRAC"
152
+ end
153
+
154
+ if name.include?("Power Supply") || name.include?("PSU")
155
+ identifiers << "PSU"
156
+ end
157
+
158
+ if name.include?("Lifecycle Controller")
159
+ identifiers << "LC"
160
+ end
161
+
162
+ if name.include?("CPLD")
163
+ identifiers << "CPLD"
164
+ end
165
+
166
+ identifiers
167
+ end
168
+
169
+ def match_component(firmware_name, catalog_name)
170
+ # Normalize names for comparison
171
+ catalog_name_lower = catalog_name.downcase.strip
172
+ firmware_name_lower = firmware_name.downcase.strip
173
+
174
+ # 1. Direct substring match
175
+ return true if catalog_name_lower.include?(firmware_name_lower) || firmware_name_lower.include?(catalog_name_lower)
176
+
177
+ # 2. Special case for BIOS
178
+ return true if catalog_name_lower.include?("bios") && firmware_name_lower.include?("bios")
179
+
180
+ # 3. Check identifiers
181
+ firmware_identifiers = extract_identifiers(firmware_name)
182
+ catalog_identifiers = extract_identifiers(catalog_name)
183
+
184
+ return true if (firmware_identifiers & catalog_identifiers).any?
185
+
186
+ # 4. Special case for network adapters
187
+ if (firmware_name_lower.include?("ethernet") || firmware_name_lower.include?("network")) &&
188
+ (catalog_name_lower.include?("ethernet") || catalog_name_lower.include?("network"))
189
+ return true
190
+ end
191
+
192
+ # No match found
193
+ false
194
+ end
195
+
196
+ def compare_versions(current_version, available_version)
197
+ # If versions are identical, no update needed
198
+ return false if current_version == available_version
199
+
200
+ # If either version is N/A, no update available
201
+ return false if current_version == "N/A" || available_version == "N/A"
202
+
203
+ # Try to handle Dell's version format (e.g., A00, A01, etc.)
204
+ if available_version.match?(/^[A-Z]\d+$/)
205
+ # If current version doesn't match Dell's format, assume update is needed
206
+ return true unless current_version.match?(/^[A-Z]\d+$/)
207
+
208
+ # Compare Dell version format (A00 < A01 < A02 < ... < B00 < B01 ...)
209
+ available_letter = available_version[0]
210
+ available_number = available_version[1..-1].to_i
211
+
212
+ current_letter = current_version[0]
213
+ current_number = current_version[1..-1].to_i
214
+
215
+ return true if current_letter < available_letter
216
+ return true if current_letter == available_letter && current_number < available_number
217
+ return false
218
+ end
219
+
220
+ # For numeric versions, try to compare them
221
+ if current_version.match?(/^[\d\.]+$/) && available_version.match?(/^[\d\.]+$/)
222
+ current_parts = current_version.split('.').map(&:to_i)
223
+ available_parts = available_version.split('.').map(&:to_i)
224
+
225
+ # Compare each part of the version
226
+ max_length = [current_parts.length, available_parts.length].max
227
+ max_length.times do |i|
228
+ current_part = current_parts[i] || 0
229
+ available_part = available_parts[i] || 0
230
+
231
+ return true if current_part < available_part
232
+ return false if current_part > available_part
233
+ end
234
+
235
+ # If we get here, versions are equal
236
+ return false
237
+ end
238
+
239
+ # If we can't determine, assume update is needed
240
+ true
241
+ end
242
+ end
243
+ 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.26"
4
+ VERSION = "0.1.28"
5
5
  end
data/lib/idrac.rb CHANGED
@@ -10,21 +10,16 @@ require 'uri'
10
10
  require 'debug' if ENV['RUBY_ENV'] == 'development'
11
11
 
12
12
  require_relative "idrac/version"
13
+ require_relative "idrac/error"
13
14
  require_relative "idrac/client"
14
15
  require_relative "idrac/screenshot"
15
16
  require_relative "idrac/firmware"
17
+ require_relative "idrac/firmware_catalog"
16
18
 
17
19
  module IDRAC
18
20
  class Error < StandardError; end
19
21
 
20
- def self.new(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: true)
21
- Client.new(
22
- host: host,
23
- username: username,
24
- password: password,
25
- port: port,
26
- use_ssl: use_ssl,
27
- verify_ssl: verify_ssl
28
- )
22
+ def self.new(options = {})
23
+ Client.new(options)
29
24
  end
30
25
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idrac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.26
4
+ version: 0.1.28
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel
@@ -209,7 +209,9 @@ files:
209
209
  - idrac.gemspec
210
210
  - lib/idrac.rb
211
211
  - lib/idrac/client.rb
212
+ - lib/idrac/error.rb
212
213
  - lib/idrac/firmware.rb
214
+ - lib/idrac/firmware_catalog.rb
213
215
  - lib/idrac/screenshot.rb
214
216
  - lib/idrac/version.rb
215
217
  - sig/idrac.rbs