idrac 0.10.1 → 0.10.3

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: 4faf659d1c3e461b586ef1201dae99bb8e82c099e68d153f1a10577bf9905aaa
4
- data.tar.gz: 656f070d756624b34eb78579e2398084dfbf8e9cf6f2ebe85f42d930e505bdef
3
+ metadata.gz: 67737e81c936f047dc3264470e8cf3d913e10d0b300af332f4bd16aca03f4078
4
+ data.tar.gz: 62467838651fb0d17ab870948d1b3a82e02f5de31297a2e6d7d7ed260dea044a
5
5
  SHA512:
6
- metadata.gz: 8e790bf89380f88a3a78f590287732aea452557b908fff4cd9ad2220f1f8d03efeada37536a04b3dfb61d6645e8c570c219098510504f1430cab8a1cb8a72b64
7
- data.tar.gz: c67eca58928d95678a58fdd114f723f1178d0cfa6d311afb82c77e49edf3c754e2a6b7e865620ba2332c0fee19fef650a7e85817c7664667105a01c15eca9dd6
6
+ metadata.gz: 4164655c54761c104a7b43f5f489972d74238c7f086b3bd865988e875eb756edaa751b6bd3241b0655c154fae57f575849d09796bbc9c0feca015192def2cbe3
7
+ data.tar.gz: 5aff6a5a4cbd3362e6dab402f493759ae7b0db72f75adc40c20a9cc84da2e1edea8990d27cd3b0dd4f10ee21ed0775404d101a2dd09752a8a55883dcc2312303
data/README.md CHANGED
@@ -167,9 +167,14 @@ client.clear_jobs!
167
167
  # Force clear job queue (use with caution)
168
168
  client.force_clear_jobs!
169
169
 
170
- # Wait for a specific job to complete
170
+ # Wait for a specific job to complete (raises IDRAC::Error if the job failed)
171
171
  job_data = client.wait_for_job("JID_12345678")
172
172
 
173
+ # Wait for a job and get its outcome back instead of an exception
174
+ result = client.wait_for_job_completion("JID_12345678")
175
+ # => { status: :success, job_id: "JID_12345678", job_state: "Completed",
176
+ # message: "...", messages: ["..."], job: { ... } }
177
+
173
178
  # Lifecycle Controller operations
174
179
  # Check if Lifecycle Controller is enabled
175
180
  status = client.get_idrac_lifecycle_status
@@ -291,6 +296,30 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
291
296
 
292
297
  ## Changelog
293
298
 
299
+ ### Version 0.10.3
300
+ - **Fixed: SCP imports reported as failures when they had succeeded**: `set_system_configuration_profile` waited on the *task* that submitted the import. iDRAC answers the import with a TaskService URI whose id is the id of the job it queued, and that task sits at `TaskState "Pending"` / `"New job."` while the job does the real work. The transient task state was returned as `{status: :failed, task_state: "Pending", messages: ["New job."]}`, so callers aborted on imports that went on to complete.
301
+ - The import now waits for the **job** to reach a terminal state and returns the **job's** outcome
302
+ - Return value: `{ status: :success | :failed | :timeout, job_id:, job_state:, message:, messages:, job: }`, plus `error:` for anything other than `:success` (`:status` is unchanged; `:job_id` is new, so callers no longer have to diff the job list to find the job)
303
+ - `Failed`, `CompletedWithErrors`, and `RebootFailed` surface the job's own `Message`
304
+ - A location header that points straight at a job still uses the job path
305
+ - New `timeout:` option (default 600s)
306
+ - **New `wait_for_job_completion(job_id, timeout:, interval:)`**: waits for a job and returns its outcome as a hash rather than raising, keeps polling while the host is briefly unreachable (an SCP import can bounce it), and always reports the job id
307
+ - **Fixed `wait_for_task` treating a queued task as failed**: it broke out of its poll loop on any state other than `Running`, so `New`/`Pending`/`Starting` were reported as failures. It now waits through every non-terminal state, accepts a full location header, and takes a `timeout:` (default 600s)
308
+ - **Removed stray `debugger` calls** from `wait_for_task` and `import_system_configuration`
309
+
310
+ ### Version 0.10.2
311
+ - **Fixed: firmware updates matched the wrong Dell Update Package**: `check_updates` paired an installed component with a catalog DUP by display name. Names are ambiguous — every NIC package reads "Ethernet" — so it selected packages that do not flash the installed device. iDRAC rejected those on apply (RED097 / "not compatible") and the version column compared unrelated components.
312
+ - Matching is now done on the Dell **componentID**, the key that actually identifies the device
313
+ - `find_updates_for_system` records `component_ids:` for each DUP, read from `SupportedDevices/Device/@componentID`
314
+ - `get_system_inventory` records `component_id:` for each installed component, from the Dell OEM block when present and otherwise parsed out of the inventory `Id` (`Installed-<componentID>-<version>`), which is all iDRAC8 exposes
315
+ - New `FirmwareCatalog#updates_for_component(catalog_updates, fw)` does the matching, and falls back to the old name heuristic only when the inventory reports no componentID (or reports `"0"`)
316
+ - New `FirmwareCatalog#version_key(version)` gives a sortable key, so when the catalog lists several revisions for one component the newest is chosen
317
+ - **Fixed: every component reported as out of date**: the catalog version came from `dellVersion`, which is the package revision label (`A17`), not a version. Comparing that against an installed numeric version (`25.5.9.0001`) flagged everything. `vendorVersion` is now preferred.
318
+
319
+ ### Versions 0.10.0-0.10.1
320
+ - Use the Dell OEM `Install` action on iDRAC8 instead of `SimpleUpdate`
321
+ - Fixed a false-positive virtual-media "inserted" state on iDRAC8 (benign VRM0009 500 on eject is now a no-op)
322
+
294
323
  ### Version 0.9.1
295
324
  - **Code Simplification**: Removed redundant `handle_response` calls throughout the codebase
296
325
  - `authenticated_request` now automatically handles error responses (status >= 400)
data/bin/idrac CHANGED
@@ -1663,12 +1663,19 @@ module IDRAC
1663
1663
  # TSR (Technical Support Report) commands
1664
1664
  desc "tsr_collect", "Generate and download TSR/SupportAssist collection"
1665
1665
  method_option :output, type: :string, desc: "Output filename"
1666
+ method_option :timeout, type: :numeric, default: 600, desc: "Seconds to wait for collection to complete"
1667
+ method_option :data_types, type: :string, desc: "Comma-separated log types to collect (0=HWData, 1=OSAppData, 2=TTYLogs, 3=DebugLogs; default: HWData,OSAppData)"
1666
1668
  def tsr_collect
1667
1669
  with_idrac_client do |client|
1668
1670
  puts "Generating and downloading TSR logs...".yellow
1669
1671
  puts "This may take several minutes...".yellow
1670
-
1671
- result = client.generate_and_download_tsr(output_file: options[:output])
1672
+
1673
+ data_selector = options[:data_types]&.split(',')
1674
+ result = client.generate_and_download_tsr(
1675
+ output_file: options[:output],
1676
+ data_selector_values: data_selector,
1677
+ wait_timeout: options[:timeout]
1678
+ )
1672
1679
 
1673
1680
  if result
1674
1681
  puts "TSR logs saved to: #{result}".green
data/idrac.gemspec CHANGED
@@ -34,7 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.require_paths = ["lib"]
35
35
 
36
36
  # Dependencies - Using semantic versioning recommendations
37
- spec.add_dependency "httparty", "~> 0.21", ">= 0.21.0"
37
+ spec.add_dependency "httparty", "~> 0.24", ">= 0.24.0"
38
38
  spec.add_dependency "nokogiri", "~> 1.15", ">= 1.15.0"
39
39
  spec.add_dependency "faraday", "~> 2.7", ">= 2.7.0"
40
40
  spec.add_dependency "faraday-multipart", "~> 1.0", ">= 1.0.0"
data/lib/idrac/boot.rb CHANGED
@@ -675,9 +675,8 @@ module IDRAC
675
675
  body: params.to_json
676
676
  )
677
677
 
678
- task = wait_for_task(response.headers["location"])
679
- debugger
680
- return task
678
+ # Same operation as set_system_configuration_profile: wait on the import JOB.
679
+ return wait_for_scp_import(response.headers["location"])
681
680
  end
682
681
  end
683
682
  end
data/lib/idrac/client.rb CHANGED
@@ -392,10 +392,18 @@ module IDRAC
392
392
  raise e
393
393
  end
394
394
  end
395
- end # Wait for a task to complete
395
+ end
396
+
397
+ # Task states that mean the task has not produced a result yet. A task that has only
398
+ # been queued reports "Pending"/"New" ("New job."), which is not an outcome -- so we
399
+ # keep waiting instead of reporting that transient state as a failure.
400
+ TASK_INCOMPLETE_STATES = %w[New Starting Pending Running Stopping Cancelling].freeze
396
401
 
397
- def wait_for_task(task_id)
402
+ # Wait for a task to complete
403
+ def wait_for_task(task_id, timeout: 600)
404
+ task_id = task_id.to_s.split("/").last # tolerate being handed a full location header
398
405
  task = nil
406
+ deadline = Time.now + timeout
399
407
 
400
408
  begin
401
409
  loop do
@@ -406,9 +414,7 @@ module IDRAC
406
414
  when 200..299
407
415
  task = JSON.parse(task_response.body)
408
416
 
409
- if task["TaskState"] != "Running"
410
- break
411
- end
417
+ break unless TASK_INCOMPLETE_STATES.include?(task["TaskState"])
412
418
 
413
419
  # Extract percentage complete if available
414
420
  percent_complete = nil
@@ -418,6 +424,12 @@ module IDRAC
418
424
  end
419
425
 
420
426
  debug "Waiting for task to complete...: #{task["TaskState"]} #{task["TaskStatus"]}", 1
427
+
428
+ if Time.now >= deadline
429
+ return { status: :timeout, task_state: task["TaskState"], task_status: task["TaskStatus"],
430
+ error: "Timed out after #{timeout}s waiting for task #{task_id} (state: #{task["TaskState"]})" }
431
+ end
432
+
421
433
  sleep 5
422
434
  else
423
435
  return {
@@ -451,7 +463,7 @@ module IDRAC
451
463
  }
452
464
  end
453
465
  rescue => e
454
- debugger
466
+ debug "Exception monitoring task #{task_id}: #{e.message}", 1, :red
455
467
  return { status: :error, error: "Exception monitoring task: #{e.message}" }
456
468
  end
457
469
  end
@@ -86,9 +86,16 @@ module IDRAC
86
86
 
87
87
  if component_response.status == 200
88
88
  component_data = JSON.parse(component_response.body)
89
+ # Dell componentID — prefer the Oem field, else parse the inventory
90
+ # Id ("Installed-<componentID>-<version>"). Used to match DUPs by
91
+ # componentID rather than by ambiguous display name.
92
+ fw_id = component_data['Id'].to_s
93
+ component_id = component_data.dig('Oem', 'Dell', 'DellSoftwareInventory', 'ComponentID')
94
+ component_id ||= fw_id[/\A(?:Installed|Current|Previous|Available)-(\d+)-/, 1]
89
95
  firmware_inventory << {
90
96
  name: component_data['Name'],
91
97
  id: component_data['Id'],
98
+ component_id: component_id,
92
99
  version: component_data['Version'],
93
100
  updateable: component_data['Updateable'] || false,
94
101
  status: component_data['Status'] ? component_data['Status']['State'] : 'Unknown'
@@ -168,25 +175,16 @@ module IDRAC
168
175
  next if displayed_components.include?(firmware_name.downcase)
169
176
  displayed_components.add(firmware_name.downcase)
170
177
 
171
- # Extract key identifiers from the firmware name
172
- identifiers = catalog.extract_identifiers(firmware_name)
173
-
174
- # Try to find a matching update
175
- matching_updates = catalog_updates.select do |update|
176
- update_name = update[:name] || ""
177
-
178
- # Check if any of our identifiers match the update name
179
- identifiers.any? { |id| update_name.downcase.include?(id.downcase) } ||
180
- # Or if the update name contains the firmware name
181
- update_name.downcase.include?(firmware_name.downcase) ||
182
- # Or if the firmware name contains the update name
183
- firmware_name.downcase.include?(update_name.downcase)
184
- end
185
-
178
+ # Match the installed component to catalog DUPs. Prefer the Dell
179
+ # componentID (reliable) — only fall back to the legacy name heuristic
180
+ # when the inventory doesn't expose one.
181
+ matching_updates = catalog.updates_for_component(catalog_updates, fw)
182
+
186
183
  if matching_updates.any?
187
- # Use the first matching update
188
- update = matching_updates.first
189
-
184
+ # Among matches, take the newest available version (the catalog can
185
+ # list several revisions for one component).
186
+ update = matching_updates.max_by { |u| catalog.version_key(u[:version]) }
187
+
190
188
  # Check if version is newer
191
189
  needs_update = catalog.compare_versions(fw[:version], update[:version])
192
190
 
@@ -253,12 +251,7 @@ module IDRAC
253
251
 
254
252
  # Skip if this update was already matched to a current firmware
255
253
  next if inventory[:firmware].any? do |fw|
256
- firmware_name = fw[:name] || ""
257
- identifiers = catalog.extract_identifiers(firmware_name)
258
-
259
- identifiers.any? { |id| update_name.downcase.include?(id.downcase) } ||
260
- update_name.downcase.include?(firmware_name.downcase) ||
261
- firmware_name.downcase.include?(update_name.downcase)
254
+ catalog.updates_for_component([update], fw).any?
262
255
  end
263
256
 
264
257
  puts "%-30s %-20s %-20s %-10s %-15s %s" % [
@@ -161,11 +161,20 @@ module IDRAC
161
161
  category_node = component.xpath("./Category/Display[@lang='en']").first
162
162
  category = category_node ? category_node.text.strip : ""
163
163
 
164
- version = component['dellVersion'] || component['vendorVersion'] || ""
165
-
164
+ # Prefer the numeric vendorVersion (e.g. "25.5.9.0001") over dellVersion,
165
+ # which is just the package revision label ("A17"). Comparing that label
166
+ # against an installed numeric version falsely flags every component.
167
+ version = component['vendorVersion'] || component['dellVersion'] || ""
168
+
169
+ # Dell componentIDs this DUP actually flashes. This is the reliable key
170
+ # for matching a DUP to an installed component (see #check_updates) —
171
+ # names are ambiguous (every NIC DUP says "Ethernet"), so name-matching
172
+ # picks wrong DUPs and iDRAC rejects them (RED097 / "not compatible").
173
+ component_ids = component.xpath(".//SupportedDevices/Device/@componentID").map(&:value).uniq
174
+
166
175
  # Skip if missing essential information
167
176
  next if name.empty? || path.empty? || version.empty?
168
-
177
+
169
178
  # Only include firmware updates
170
179
  if component_type.include?("Firmware") ||
171
180
  category.include?("BIOS") ||
@@ -174,13 +183,14 @@ module IDRAC
174
183
  name.include?("BIOS") ||
175
184
  name.include?("Firmware") ||
176
185
  name.include?("iDRAC")
177
-
186
+
178
187
  updates << {
179
188
  name: name,
180
189
  version: version,
181
190
  path: path,
182
191
  component_type: component_type,
183
192
  category: category,
193
+ component_ids: component_ids,
184
194
  download_url: "https://downloads.dell.com/#{path}"
185
195
  }
186
196
  end
@@ -189,7 +199,35 @@ module IDRAC
189
199
  puts "Found #{updates.size} firmware updates for system ID #{system_id}"
190
200
  updates
191
201
  end
192
-
202
+
203
+ # Select the catalog DUPs that apply to one installed component (`fw` from
204
+ # the iDRAC inventory). Matches on Dell componentID — the reliable key —
205
+ # and only falls back to the legacy name heuristic when the inventory
206
+ # didn't expose a componentID (e.g. it reports "0").
207
+ def updates_for_component(catalog_updates, fw)
208
+ component_id = fw[:component_id]
209
+ if component_id && component_id != "0"
210
+ catalog_updates.select { |u| Array(u[:component_ids]).include?(component_id) }
211
+ else
212
+ name = fw[:name] || ""
213
+ ids = extract_identifiers(name)
214
+ catalog_updates.select do |u|
215
+ un = u[:name] || ""
216
+ ids.any? { |id| un.downcase.include?(id.downcase) } ||
217
+ un.downcase.include?(name.downcase) ||
218
+ name.downcase.include?(un.downcase)
219
+ end
220
+ end
221
+ end
222
+
223
+ # Sortable key for a Dell version string (e.g. "25.5.9.0001", "22.00.6").
224
+ # Non-numeric/odd versions sort lowest rather than raising.
225
+ def version_key(version)
226
+ Gem::Version.new(version.to_s[/\d[\d.]*/] || "0")
227
+ rescue ArgumentError
228
+ Gem::Version.new("0")
229
+ end
230
+
193
231
  def extract_identifiers(name)
194
232
  return [] unless name
195
233
 
data/lib/idrac/jobs.rb CHANGED
@@ -185,7 +185,55 @@ module IDRAC
185
185
 
186
186
  raise Error, "Timeout waiting for job to complete"
187
187
  end
188
-
188
+
189
+ # Job states that mean the job is finished, one way or another.
190
+ JOB_TERMINAL_STATES = %w[Completed CompletedWithErrors Failed RebootFailed].freeze
191
+
192
+ # Wait for an iDRAC job to reach a terminal state and report on the JOB itself.
193
+ #
194
+ # Unlike wait_for_job this does not raise for a job that simply failed -- it returns
195
+ # the outcome so the caller can log or re-check it. The hash always carries :job_id.
196
+ #
197
+ # { status: :success | :failed | :timeout, job_id:, job_state:,
198
+ # message:, messages: [message], job: <raw job data>,
199
+ # error: <the job's own message, on anything but :success> }
200
+ def wait_for_job_completion(job_id, timeout: 600, interval: 10)
201
+ job_id = job_id.to_s.split("/").last
202
+ deadline = Time.now + timeout
203
+ state = nil
204
+ last_error = nil
205
+
206
+ while Time.now < deadline
207
+ begin
208
+ response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Jobs/#{job_id}")
209
+ job = response.status == 200 ? JSON.parse(response.body) : nil
210
+ state = job ? job["JobState"] : nil
211
+
212
+ if JOB_TERMINAL_STATES.include?(state)
213
+ message = job["Message"] || Array(job["Messages"]).map { |m| m["Message"] }.compact.first
214
+ success = state == "Completed"
215
+ debug "Job #{job_id} #{state}: #{message}", 1, success ? :green : :red
216
+ result = { status: success ? :success : :failed, job_id: job_id, job_state: state,
217
+ message: message, messages: [message].compact, job: job }
218
+ result[:error] = message || "Job #{job_id} finished with state #{state}" unless success
219
+ return result
220
+ end
221
+
222
+ last_error = "HTTP #{response.status}" unless job
223
+ debug "Job #{job_id} state: #{state || last_error}. Waiting...", 1, :yellow
224
+ rescue StandardError => e
225
+ # An SCP import can bounce the host, so a job can be briefly unreadable. Keep polling.
226
+ last_error = e.message
227
+ debug "Job #{job_id} not readable (#{e.message}). Waiting...", 1, :yellow
228
+ end
229
+
230
+ sleep interval
231
+ end
232
+
233
+ { status: :timeout, job_id: job_id, job_state: state,
234
+ error: "Timed out after #{timeout}s waiting for job #{job_id} (last state: #{state || last_error || 'unknown'})" }
235
+ end
236
+
189
237
  # Get system tasks
190
238
  def tasks
191
239
  response = authenticated_request(:get, '/redfish/v1/TaskService/Tasks')
@@ -84,8 +84,13 @@ module IDRAC
84
84
  end
85
85
  end
86
86
 
87
- # Apply a system configuration profile to the iDRAC
88
- def set_system_configuration_profile(scp, target: "ALL", reboot: false, retry_count: 0)
87
+ # Apply a system configuration profile to the iDRAC.
88
+ #
89
+ # Waits for the import JOB to finish and returns the JOB's outcome:
90
+ #
91
+ # { status: :success | :failed | :timeout, job_id:, job_state:, message:,
92
+ # messages: [message], job: <raw job data>, error: <message unless :success> }
93
+ def set_system_configuration_profile(scp, target: "ALL", reboot: false, timeout: 600, retry_count: 0)
89
94
  # Ensure scp has the proper structure with SystemConfiguration wrapper
90
95
  scp_to_apply = if scp.is_a?(Hash) && scp["SystemConfiguration"]
91
96
  scp
@@ -135,7 +140,27 @@ module IDRAC
135
140
  return { status: :failed, error: error_message }
136
141
  end
137
142
 
138
- return handle_location(response.headers["location"])
143
+ return wait_for_scp_import(response.headers["location"], timeout: timeout)
144
+ end
145
+
146
+ # Wait for an SCP import submitted at +location+ and report the JOB's outcome.
147
+ #
148
+ # iDRAC answers the import with a TaskService URI whose id is the id of the job it
149
+ # queued (/redfish/v1/TaskService/Tasks/JID_...). That task is only the submission:
150
+ # it sits at TaskState "Pending" / "New job." while the job does the real work, so
151
+ # waiting on the task reports a queued job as a failure. The job is the operation,
152
+ # so the job is what we wait on -- and the job id goes back to the caller.
153
+ def wait_for_scp_import(location, timeout: 600)
154
+ return { status: :failed, error: "SCP import returned no location header" } if location.nil? || location.to_s.empty?
155
+
156
+ id = location.to_s.split("/").last
157
+ # Dell job ids (JID_/RID_) name the job whether the header points at the job itself
158
+ # or at the task that queued it. Anything else is a real task: handle it as before.
159
+ if location.to_s.include?("/Jobs/") || id.start_with?("JID_", "RID_")
160
+ wait_for_job_completion(id, timeout: timeout)
161
+ else
162
+ handle_location(location)
163
+ end
139
164
  end
140
165
 
141
166
  # This puts the SCP into a format that can be used by reasonable Ruby code.
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.10.1"
4
+ VERSION = "0.10.3"
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idrac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.1
4
+ version: 0.10.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-06-02 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: httparty
@@ -16,20 +15,20 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '0.21'
18
+ version: '0.24'
20
19
  - - ">="
21
20
  - !ruby/object:Gem::Version
22
- version: 0.21.0
21
+ version: 0.24.0
23
22
  type: :runtime
24
23
  prerelease: false
25
24
  version_requirements: !ruby/object:Gem::Requirement
26
25
  requirements:
27
26
  - - "~>"
28
27
  - !ruby/object:Gem::Version
29
- version: '0.21'
28
+ version: '0.24'
30
29
  - - ">="
31
30
  - !ruby/object:Gem::Version
32
- version: 0.21.0
31
+ version: 0.24.0
33
32
  - !ruby/object:Gem::Dependency
34
33
  name: nokogiri
35
34
  requirement: !ruby/object:Gem::Requirement
@@ -282,7 +281,6 @@ licenses:
282
281
  metadata:
283
282
  homepage_uri: https://github.com/buildio/idrac
284
283
  source_code_uri: https://github.com/buildio/idrac
285
- post_install_message:
286
284
  rdoc_options: []
287
285
  require_paths:
288
286
  - lib
@@ -297,8 +295,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
295
  - !ruby/object:Gem::Version
298
296
  version: '0'
299
297
  requirements: []
300
- rubygems_version: 3.5.16
301
- signing_key:
298
+ rubygems_version: 3.6.9
302
299
  specification_version: 4
303
300
  summary: API Client for Dell iDRAC
304
301
  test_files: []