supermicro 0.1.0

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,372 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'colorize'
5
+
6
+ module Supermicro
7
+ module VirtualMedia
8
+ def virtual_media
9
+ response = authenticated_request(:get, "/redfish/v1/Managers/1/VirtualMedia")
10
+
11
+ if response.status == 200
12
+ begin
13
+ data = JSON.parse(response.body)
14
+
15
+ # Fetch each member individually since $expand doesn't work properly
16
+ media = data["Members"].map do |member|
17
+ member_path = member["@odata.id"]
18
+ member_response = authenticated_request(:get, member_path)
19
+
20
+ if member_response.status == 200
21
+ m = JSON.parse(member_response.body)
22
+ else
23
+ next nil
24
+ end
25
+ # Check if media is actually inserted (not just a dummy URL)
26
+ dummy_url = "http://0.0.0.0/dummy.iso"
27
+ has_real_image = !m["Image"].nil? && !m["Image"].empty? && m["Image"] != dummy_url
28
+
29
+ is_inserted = m["Inserted"] ||
30
+ has_real_image ||
31
+ (m["ConnectedVia"] && m["ConnectedVia"] != "NotConnected") ||
32
+ (m["ImageName"] && !m["ImageName"].empty?)
33
+
34
+ # Override: if it's the dummy URL, consider it not inserted
35
+ is_inserted = false if m["Image"] == dummy_url
36
+
37
+ # Show media status in debug mode
38
+ if is_inserted
39
+ debug "#{m["Name"] || m["Id"]} #{m["ConnectedVia"]} #{m["Image"] || m["ImageName"]}", 1, :green
40
+ else
41
+ debug "#{m["Name"] || m["Id"]} #{m["ConnectedVia"] || 'NotConnected'}", 1, :yellow
42
+ end
43
+
44
+ action_path = m.dig("Actions", "#VirtualMedia.InsertMedia", "target")
45
+ eject_path = m.dig("Actions", "#VirtualMedia.EjectMedia", "target")
46
+
47
+ # Use a more descriptive name if the API returns generic names
48
+ name = if m["Name"] == "Virtual Removable Media" && m["Id"]
49
+ "#{m["Name"]} (#{m["Id"]})"
50
+ else
51
+ m["Name"] || m["Id"]
52
+ end
53
+
54
+ {
55
+ device: m["Id"],
56
+ name: name,
57
+ inserted: is_inserted,
58
+ image: m["Image"] || m["ImageName"],
59
+ connected_via: m["ConnectedVia"],
60
+ media_types: m["MediaTypes"],
61
+ action_path: action_path,
62
+ eject_path: eject_path
63
+ }
64
+ end.compact # Remove any nil entries
65
+
66
+ return media
67
+ rescue JSON::ParserError
68
+ raise Error, "Failed to parse virtual media response: #{response.body}"
69
+ end
70
+ else
71
+ raise Error, "Failed to get virtual media. Status code: #{response.status}"
72
+ end
73
+ end
74
+
75
+ def eject_virtual_media(device: nil)
76
+ media_list = virtual_media
77
+
78
+ if device
79
+ # Check for media with an image URL even if Inserted is false (Supermicro quirk)
80
+ media_to_eject = media_list.find { |m| m[:device] == device && (m[:inserted] || m[:image]) }
81
+ else
82
+ media_to_eject = media_list.find { |m| m[:inserted] || m[:image] }
83
+ end
84
+
85
+ if media_to_eject.nil?
86
+ debug "No media to eject#{device ? " for device #{device}" : ''}", 1, :yellow
87
+ return false
88
+ end
89
+
90
+ debug "Ejecting #{media_to_eject[:name]} (#{media_to_eject[:image]})...", 1, :yellow
91
+
92
+ # Supermicro quirk: EjectMedia action may not be available when Inserted is false
93
+ # Try to use InsertMedia with Inserted:false to eject
94
+ path = if media_to_eject[:eject_path] && media_to_eject[:inserted]
95
+ media_to_eject[:eject_path]
96
+ elsif media_to_eject[:action_path]
97
+ # Use InsertMedia action to eject by setting Inserted to false
98
+ media_to_eject[:action_path]
99
+ else
100
+ "/redfish/v1/Managers/1/VirtualMedia/#{media_to_eject[:device]}/Actions/VirtualMedia.InsertMedia"
101
+ end
102
+
103
+ # For Supermicro, use InsertMedia with a dummy image and Inserted:false to eject
104
+ # Empty string doesn't work, so use a dummy URL
105
+ body = if path.include?("InsertMedia")
106
+ {
107
+ "Image" => "http://0.0.0.0/dummy.iso", # Dummy URL to clear the media
108
+ "Inserted" => false,
109
+ "TransferMethod" => "Stream"
110
+ }
111
+ else
112
+ {}
113
+ end
114
+
115
+ response = authenticated_request(
116
+ :post,
117
+ path,
118
+ body: body.to_json,
119
+ headers: { 'Content-Type': 'application/json' }
120
+ )
121
+
122
+ if response.status.between?(200, 299)
123
+ debug "Media ejected successfully.", 1, :green
124
+ return true
125
+ else
126
+ debug "Failed to eject media: #{response.status} - #{response.body}", 1, :red
127
+ return false
128
+ end
129
+ end
130
+
131
+ def insert_virtual_media(iso_url, device: nil)
132
+ # Check for license if mounting HTTP/HTTPS media
133
+ if iso_url.start_with?('http://', 'https://')
134
+ license_info = check_virtual_media_license
135
+
136
+ if license_info[:available] == false
137
+ raise Error, "Virtual media license required: #{license_info[:message]}"
138
+ elsif license_info[:available] == :unknown
139
+ debug "Warning: Unable to verify virtual media license. Mount may fail if license is missing.", 1, :yellow
140
+ debug "Supermicro requires SFT-OOB-LIC or SFT-DCMS-SINGLE for HTTP/HTTPS virtual media.", 1, :yellow
141
+ else
142
+ debug "Virtual media license verified: #{license_info[:licenses].join(', ')}", 2, :green
143
+ end
144
+ end
145
+
146
+ device ||= find_best_virtual_media_device
147
+
148
+ unless device
149
+ raise Error, "No suitable virtual media device found"
150
+ end
151
+
152
+ eject_virtual_media(device: device)
153
+
154
+ debug "Inserting media: #{iso_url} into #{device}...", 1, :yellow
155
+
156
+ tries = 0
157
+ max_tries = 3
158
+
159
+ while tries < max_tries
160
+ begin
161
+ media_info = virtual_media.find { |m| m[:device] == device }
162
+
163
+ unless media_info
164
+ raise Error, "Virtual media device #{device} not found"
165
+ end
166
+
167
+ path = if media_info[:action_path]
168
+ media_info[:action_path]
169
+ else
170
+ "/redfish/v1/Managers/1/VirtualMedia/#{device}/Actions/VirtualMedia.InsertMedia"
171
+ end
172
+
173
+ body = {
174
+ "Image" => iso_url,
175
+ "Inserted" => true,
176
+ "WriteProtected" => true,
177
+ "TransferMethod" => "Stream",
178
+ "TransferProtocolType" => iso_url.start_with?("https") ? "HTTPS" : "HTTP"
179
+ }
180
+
181
+ response = authenticated_request(
182
+ :post,
183
+ path,
184
+ body: body.to_json,
185
+ headers: { 'Content-Type': 'application/json' }
186
+ )
187
+
188
+ if response.status == 202
189
+ # Async operation - poll the task
190
+ debug "Virtual media insert is async, polling task...", 1, :yellow
191
+
192
+ task_result = wait_for_task_completion(response, timeout: 30)
193
+
194
+ if task_result[:success]
195
+ # Task completed, now verify the media is connected
196
+ if verbosity == 0
197
+ # Show spinner while verifying connection
198
+ spinner = Spinner.new("Verifying connection", type: :dots, color: :yellow)
199
+ spinner.start
200
+ else
201
+ debug "Task completed, verifying connection...", 1, :yellow
202
+ end
203
+
204
+ connected = false
205
+ 5.times do |i|
206
+ sleep 1
207
+ current_media = virtual_media
208
+ inserted_media = current_media.find { |m| m[:device] == device }
209
+
210
+ if inserted_media
211
+ if verbosity == 0
212
+ spinner&.update("Checking connection: #{inserted_media[:connected_via]}")
213
+ else
214
+ debug " Status: ConnectedVia=#{inserted_media[:connected_via]}, Inserted=#{inserted_media[:inserted]}", 2
215
+ end
216
+
217
+ if inserted_media[:connected_via] == "URI" && inserted_media[:inserted]
218
+ spinner&.stop("Media connected via URI", success: true) if verbosity == 0
219
+ debug "✓ Media connected successfully via URI!", 1, :green
220
+ connected = true
221
+ break
222
+ end
223
+ end
224
+ end
225
+
226
+ spinner&.stop if spinner && verbosity == 0
227
+
228
+ if connected
229
+ return true
230
+ else
231
+ # Final check
232
+ current_media = virtual_media
233
+ inserted_media = current_media.find { |m| m[:device] == device }
234
+ if inserted_media && inserted_media[:image] == iso_url
235
+ if inserted_media[:connected_via] == "NotConnected"
236
+ debug "ERROR: Media mounted but NOT CONNECTED!", 1, :red
237
+ debug "The ISO will NOT boot! ConnectedVia must be 'URI', not 'NotConnected'.", 1, :red
238
+ return false
239
+ else
240
+ debug "Media mounted with status: #{inserted_media[:connected_via]}", 1, :green
241
+ return true
242
+ end
243
+ else
244
+ debug "Failed to verify media mount.", 1, :red
245
+ return false
246
+ end
247
+ end
248
+ else
249
+ debug "Task failed or timed out", 1, :red
250
+ return false
251
+ end
252
+ elsif response.status.between?(200, 299)
253
+ # Synchronous success (rare)
254
+ debug "Virtual media inserted synchronously.", 1, :green
255
+ sleep 2
256
+ current_media = virtual_media
257
+ inserted_media = current_media.find { |m| m[:device] == device }
258
+
259
+ if inserted_media && (inserted_media[:inserted] || inserted_media[:image] == iso_url)
260
+ if inserted_media[:connected_via] == "URI"
261
+ debug "✓ Media connected via URI", 1, :green
262
+ return true
263
+ elsif inserted_media[:connected_via] == "NotConnected"
264
+ debug "WARNING: Media mounted but not connected!", 1, :red
265
+ return false
266
+ else
267
+ debug "Media mounted with status: #{inserted_media[:connected_via]}", 1, :yellow
268
+ return true
269
+ end
270
+ else
271
+ debug "Could not verify media mount", 1, :yellow
272
+ return true
273
+ end
274
+ elsif response.status == 400 && response.body.include?("already")
275
+ debug "Media already inserted, ejecting and retrying...", 1, :yellow
276
+ eject_virtual_media(device: device)
277
+ sleep 2
278
+ tries += 1
279
+ else
280
+ debug "Failed to insert media: #{response.status} - #{response.body}", 1, :red
281
+ tries += 1
282
+ sleep 2
283
+ end
284
+ rescue => e
285
+ debug "Error inserting media: #{e.message}", 1, :red
286
+ tries += 1
287
+ sleep 2
288
+ end
289
+ end
290
+
291
+ raise Error, "Failed to insert virtual media after #{max_tries} attempts"
292
+ end
293
+
294
+ def find_best_virtual_media_device
295
+ media_list = virtual_media
296
+
297
+ cd_device = media_list.find { |m|
298
+ m[:media_types]&.include?("CD") || m[:media_types]&.include?("DVD") ||
299
+ m[:name]&.downcase&.include?("cd") || m[:device]&.downcase&.include?("cd")
300
+ }
301
+
302
+ return cd_device[:device] if cd_device
303
+
304
+ removable = media_list.find { |m|
305
+ m[:media_types]&.include?("Removable") ||
306
+ m[:name]&.downcase&.include?("removable")
307
+ }
308
+
309
+ return removable[:device] if removable
310
+
311
+ media_list.first&.dig(:device)
312
+ end
313
+
314
+ def virtual_media_status
315
+ media_list = virtual_media
316
+
317
+ debug "\n=== Virtual Media Status ===", 1, :green
318
+
319
+ media_list.each do |media|
320
+ debug "\nDevice: #{media[:name]} (#{media[:device]})", 1, :cyan
321
+ debug " Media Types: #{media[:media_types]&.join(', ')}", 1 if media[:media_types]
322
+ debug " Inserted: #{media[:inserted] ? 'Yes'.green : 'No'.yellow}", 1
323
+ debug " Image: #{media[:image]}", 1 if media[:image]
324
+ debug " Connected Via: #{media[:connected_via]}", 1 if media[:connected_via]
325
+ end
326
+
327
+ media_list
328
+ end
329
+
330
+ def mount_iso_and_boot(iso_url, device: nil)
331
+ debug "Mounting ISO and setting boot override...", 1, :yellow
332
+
333
+ insert_virtual_media(iso_url, device: device)
334
+
335
+ sleep 2
336
+
337
+ begin
338
+ require_relative 'boot'
339
+ boot_to_cd
340
+ debug "System will boot from virtual media on next restart.", 1, :green
341
+ return true
342
+ rescue => e
343
+ debug "ISO mounted but failed to set boot override: #{e.message}", 1, :yellow
344
+ return false
345
+ end
346
+ end
347
+
348
+ def unmount_all_media
349
+ debug "Unmounting all virtual media...", 1, :yellow
350
+
351
+ media_list = virtual_media
352
+ mounted = media_list.select { |m| m[:inserted] }
353
+
354
+ if mounted.empty?
355
+ debug "No virtual media currently mounted.", 1, :yellow
356
+ return true
357
+ end
358
+
359
+ success = true
360
+ mounted.each do |media|
361
+ if eject_virtual_media(device: media[:device])
362
+ debug " Ejected: #{media[:name]}", 1, :green
363
+ else
364
+ debug " Failed to eject: #{media[:name]}", 1, :red
365
+ success = false
366
+ end
367
+ end
368
+
369
+ success
370
+ end
371
+ end
372
+ end
data/lib/supermicro.rb ADDED
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'nokogiri'
5
+ require 'faraday'
6
+ require 'faraday/multipart'
7
+ require 'base64'
8
+ require 'uri'
9
+ require 'colorize'
10
+ require 'active_support'
11
+ require 'active_support/core_ext'
12
+ require 'debug' if ENV['RUBY_ENV'] == 'development'
13
+
14
+ module Supermicro
15
+ module Debuggable
16
+ def debug(message, level = 1, color = :light_cyan)
17
+ return unless respond_to?(:verbosity) && verbosity >= level
18
+ color_method = color.is_a?(Symbol) && String.method_defined?(color) ? color : :to_s
19
+ puts message.send(color_method)
20
+
21
+ if respond_to?(:verbosity) && verbosity >= 3 && caller.length > 1
22
+ puts " Called from:".light_yellow
23
+ caller[1..3].each do |call|
24
+ puts " #{call}".light_yellow
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class Error < StandardError; end
31
+
32
+ def self.new(options = {})
33
+ Client.new(options)
34
+ end
35
+
36
+ def self.connect(**options, &block)
37
+ Client.connect(**options, &block)
38
+ end
39
+ end
40
+
41
+ require 'supermicro/version'
42
+ require 'supermicro/error'
43
+ require 'supermicro/session'
44
+ require 'supermicro/spinner'
45
+ require 'supermicro/power'
46
+ require 'supermicro/jobs'
47
+ require 'supermicro/storage'
48
+ require 'supermicro/system'
49
+ require 'supermicro/tasks'
50
+ require 'supermicro/virtual_media'
51
+ require 'supermicro/boot'
52
+ require 'supermicro/system_config'
53
+ require 'supermicro/utility'
54
+ require 'supermicro/license'
55
+ require 'supermicro/client'
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/supermicro/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "supermicro"
7
+ spec.version = Supermicro::VERSION
8
+ spec.authors = ["Jonathan Siegel"]
9
+ spec.email = ["248302+usiegj00@users.noreply.github.com"]
10
+
11
+ spec.summary = "API Client for Supermicro BMC"
12
+ spec.description = "A Ruby client for the Supermicro BMC Redfish API with support for power management, virtual media, and system configuration"
13
+ spec.homepage = "https://github.com/buildio/supermicro"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.7.0"
16
+
17
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://github.com/buildio/supermicro"
21
+ spec.metadata["changelog_uri"] = "https://github.com/buildio/supermicro/blob/main/CHANGELOG.md"
22
+
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ Dir["{lib}/**/*", "LICENSE", "README.md", "*.gemspec"]
25
+ end
26
+
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "httparty", "~> 0.21"
32
+ spec.add_dependency "faraday", "~> 2.0"
33
+ spec.add_dependency "faraday-multipart", "~> 1.0"
34
+ spec.add_dependency "nokogiri", "~> 1.14"
35
+ spec.add_dependency "colorize", "~> 0.8"
36
+ spec.add_dependency "activesupport", "~> 7.0"
37
+
38
+ spec.add_development_dependency "rspec", "~> 3.0"
39
+ spec.add_development_dependency "webmock", "~> 3.0"
40
+ spec.add_development_dependency "debug", "~> 1.0"
41
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: supermicro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonathan Siegel
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-08-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.21'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.21'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday-multipart
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.14'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: colorize
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.8'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.8'
83
+ - !ruby/object:Gem::Dependency
84
+ name: activesupport
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '7.0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '7.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: debug
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.0'
139
+ description: A Ruby client for the Supermicro BMC Redfish API with support for power
140
+ management, virtual media, and system configuration
141
+ email:
142
+ - 248302+usiegj00@users.noreply.github.com
143
+ executables: []
144
+ extensions: []
145
+ extra_rdoc_files: []
146
+ files:
147
+ - LICENSE
148
+ - README.md
149
+ - lib/supermicro.rb
150
+ - lib/supermicro/boot.rb
151
+ - lib/supermicro/client.rb
152
+ - lib/supermicro/error.rb
153
+ - lib/supermicro/jobs.rb
154
+ - lib/supermicro/license.rb
155
+ - lib/supermicro/power.rb
156
+ - lib/supermicro/session.rb
157
+ - lib/supermicro/spinner.rb
158
+ - lib/supermicro/storage.rb
159
+ - lib/supermicro/system.rb
160
+ - lib/supermicro/system_config.rb
161
+ - lib/supermicro/tasks.rb
162
+ - lib/supermicro/utility.rb
163
+ - lib/supermicro/version.rb
164
+ - lib/supermicro/virtual_media.rb
165
+ - supermicro.gemspec
166
+ homepage: https://github.com/buildio/supermicro
167
+ licenses:
168
+ - MIT
169
+ metadata:
170
+ allowed_push_host: https://rubygems.org
171
+ homepage_uri: https://github.com/buildio/supermicro
172
+ source_code_uri: https://github.com/buildio/supermicro
173
+ changelog_uri: https://github.com/buildio/supermicro/blob/main/CHANGELOG.md
174
+ post_install_message:
175
+ rdoc_options: []
176
+ require_paths:
177
+ - lib
178
+ required_ruby_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: 2.7.0
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ requirements: []
189
+ rubygems_version: 3.3.26
190
+ signing_key:
191
+ specification_version: 4
192
+ summary: API Client for Supermicro BMC
193
+ test_files: []