radfish 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,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+ require 'uri'
6
+ require 'openssl'
7
+
8
+ module Radfish
9
+ class VendorDetector
10
+
11
+ attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl
12
+ attr_accessor :verbosity
13
+
14
+ def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: false)
15
+ @host = host
16
+ @username = username
17
+ @password = password
18
+ @port = port
19
+ @use_ssl = use_ssl
20
+ @verify_ssl = verify_ssl
21
+ @verbosity = 0
22
+ end
23
+
24
+ def detect
25
+ puts "Detecting vendor for #{host}..." if @verbosity && @verbosity > 0
26
+
27
+ # Try to get the Redfish service root
28
+ service_root = fetch_service_root
29
+ return nil unless service_root
30
+
31
+ vendor = identify_vendor(service_root)
32
+ puts "Detected vendor: #{vendor || 'Unknown'}" if @verbosity && @verbosity > 0
33
+
34
+ vendor
35
+ end
36
+
37
+ private
38
+
39
+ def fetch_service_root
40
+ uri = URI("#{base_url}/redfish/v1")
41
+
42
+ http = Net::HTTP.new(uri.host, uri.port)
43
+ http.use_ssl = use_ssl
44
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless verify_ssl
45
+ http.open_timeout = 5
46
+ http.read_timeout = 10
47
+
48
+ req = Net::HTTP::Get.new(uri)
49
+ req.basic_auth(username, password)
50
+ req['Accept'] = 'application/json'
51
+
52
+ begin
53
+ res = http.request(req)
54
+
55
+ if res.code.to_i == 200
56
+ JSON.parse(res.body)
57
+ else
58
+ puts "Failed to fetch service root: HTTP #{res.code}" if @verbosity && @verbosity > 0
59
+ nil
60
+ end
61
+ rescue => e
62
+ puts "Error fetching service root: #{e.message}" if @verbosity && @verbosity > 0
63
+ nil
64
+ end
65
+ end
66
+
67
+ def identify_vendor(service_root)
68
+ # Check explicit vendor field
69
+ vendor = service_root['Vendor'] || service_root['Oem']&.keys&.first
70
+
71
+ if vendor
72
+ return normalize_vendor(vendor)
73
+ end
74
+
75
+ # Try to identify by product name
76
+ product = service_root['Product']
77
+ if product
78
+ case product
79
+ when /dell/i, /poweredge/i, /idrac/i
80
+ return 'dell'
81
+ when /supermicro/i, /smc/i
82
+ return 'supermicro'
83
+ when /hpe/i, /hewlett/i, /proliant/i, /ilo/i
84
+ return 'hpe'
85
+ when /lenovo/i, /thinkserver/i, /thinksystem/i
86
+ return 'lenovo'
87
+ when /asrockrack/i, /asrock/i
88
+ return 'asrockrack'
89
+ end
90
+ end
91
+
92
+ # Check manager endpoint for more clues
93
+ managers_url = service_root.dig('Managers', '@odata.id')
94
+ if managers_url
95
+ vendor_from_managers = detect_from_managers(managers_url)
96
+ return vendor_from_managers if vendor_from_managers
97
+ end
98
+
99
+ # Default to generic if we can't determine
100
+ 'generic'
101
+ end
102
+
103
+ def detect_from_managers(managers_path)
104
+ uri = URI("#{base_url}#{managers_path}")
105
+
106
+ http = Net::HTTP.new(uri.host, uri.port)
107
+ http.use_ssl = use_ssl
108
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless verify_ssl
109
+ http.open_timeout = 5
110
+ http.read_timeout = 10
111
+
112
+ req = Net::HTTP::Get.new(uri)
113
+ req.basic_auth(username, password)
114
+ req['Accept'] = 'application/json'
115
+
116
+ begin
117
+ res = http.request(req)
118
+
119
+ if res.code.to_i == 200
120
+ data = JSON.parse(res.body)
121
+
122
+ # Check first manager
123
+ if data['Members'] && data['Members'].first
124
+ manager_url = data['Members'].first['@odata.id']
125
+
126
+ # Dell uses iDRAC.Embedded.1
127
+ return 'dell' if manager_url.include?('iDRAC')
128
+
129
+ # HPE uses numbered managers like /redfish/v1/Managers/1
130
+ # Supermicro also uses /redfish/v1/Managers/1
131
+ # Need to fetch actual manager data
132
+
133
+ manager_data = fetch_manager(manager_url)
134
+ if manager_data
135
+ # Check manager model/description
136
+ model = manager_data['Model'] || ''
137
+ description = manager_data['Description'] || ''
138
+ # firmware = manager_data['FirmwareVersion'] || '' # Reserved for future use
139
+
140
+ return 'dell' if model.match?(/idrac/i) || description.match?(/idrac/i)
141
+ return 'hpe' if model.match?(/ilo/i) || description.match?(/ilo/i)
142
+ return 'supermicro' if model.match?(/supermicro/i) || description.match?(/smc/i)
143
+ return 'lenovo' if model.match?(/lenovo/i) || description.match?(/xcc/i)
144
+ return 'asrockrack' if model.match?(/asrock/i)
145
+ end
146
+ end
147
+ end
148
+ rescue => e
149
+ puts "Error detecting from managers: #{e.message}" if @verbosity && @verbosity > 1
150
+ end
151
+
152
+ nil
153
+ end
154
+
155
+ def fetch_manager(manager_path)
156
+ uri = URI("#{base_url}#{manager_path}")
157
+
158
+ http = Net::HTTP.new(uri.host, uri.port)
159
+ http.use_ssl = use_ssl
160
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE unless verify_ssl
161
+ http.open_timeout = 5
162
+ http.read_timeout = 10
163
+
164
+ req = Net::HTTP::Get.new(uri)
165
+ req.basic_auth(username, password)
166
+ req['Accept'] = 'application/json'
167
+
168
+ begin
169
+ res = http.request(req)
170
+ JSON.parse(res.body) if res.code.to_i == 200
171
+ rescue => e
172
+ puts "Error fetching manager: #{e.message}" if @verbosity && @verbosity > 1
173
+ nil
174
+ end
175
+ end
176
+
177
+ def normalize_vendor(vendor_string)
178
+ case vendor_string.to_s.downcase
179
+ when /dell/i, /idrac/i
180
+ 'dell'
181
+ when /supermicro/i, /smc/i
182
+ 'supermicro'
183
+ when /hpe/i, /hewlett/i, /hp/i, /ilo/i
184
+ 'hpe'
185
+ when /lenovo/i
186
+ 'lenovo'
187
+ when /asrock/i
188
+ 'asrockrack'
189
+ else
190
+ vendor_string.to_s.downcase
191
+ end
192
+ end
193
+
194
+ def base_url
195
+ protocol = use_ssl ? 'https' : 'http'
196
+ "#{protocol}://#{host}:#{port}"
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Radfish
4
+ VERSION = "0.1.0"
5
+ end
data/lib/radfish.rb ADDED
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'httparty'
4
+ require 'faraday'
5
+ require 'faraday/multipart'
6
+ require 'base64'
7
+ require 'uri'
8
+ require 'json'
9
+ require 'colorize'
10
+ require 'active_support'
11
+ require 'active_support/core_ext'
12
+
13
+ module Radfish
14
+ class Error < StandardError; end
15
+ class AuthenticationError < Error; end
16
+ class ConnectionError < Error; end
17
+ class NotFoundError < Error; end
18
+ class TimeoutError < Error; end
19
+ class UnsupportedVendorError < Error; end
20
+
21
+ module Debuggable
22
+ def debug(message, level = 1, color = :light_cyan)
23
+ return unless respond_to?(:verbosity) && verbosity >= level
24
+ color_method = color.is_a?(Symbol) && String.method_defined?(color) ? color : :to_s
25
+ puts message.send(color_method)
26
+
27
+ if respond_to?(:verbosity) && verbosity >= 3 && caller.length > 1
28
+ puts " Called from:".light_yellow
29
+ caller[1..3].each do |call|
30
+ puts " #{call}".light_yellow
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ class << self
37
+ def new(options = {})
38
+ Client.new(options)
39
+ end
40
+
41
+ def connect(**options, &block)
42
+ Client.connect(**options, &block)
43
+ end
44
+
45
+ def detect_vendor(host:, username:, password:, **options)
46
+ VendorDetector.new(host: host, username: username, password: password, **options).detect
47
+ end
48
+
49
+ def register_adapter(vendor, adapter_class)
50
+ @adapters ||= {}
51
+ @adapters[vendor.to_s.downcase] = adapter_class
52
+ end
53
+
54
+ def get_adapter(vendor)
55
+ @adapters ||= {}
56
+ @adapters[vendor.to_s.downcase]
57
+ end
58
+
59
+ def supported_vendors
60
+ @adapters&.keys || []
61
+ end
62
+ end
63
+ end
64
+
65
+ require 'radfish/version'
66
+ require 'radfish/core/base_client'
67
+ require 'radfish/core/session'
68
+ require 'radfish/core/power'
69
+ require 'radfish/core/system'
70
+ require 'radfish/core/storage'
71
+ require 'radfish/core/virtual_media'
72
+ require 'radfish/core/boot'
73
+ require 'radfish/core/jobs'
74
+ require 'radfish/core/utility'
75
+ require 'radfish/vendor_detector'
76
+ require 'radfish/client'
77
+
78
+ # Auto-load adapters if available
79
+ begin
80
+ require 'radfish/idrac_adapter'
81
+ rescue LoadError
82
+ # radfish-idrac gem not installed
83
+ end
84
+
85
+ begin
86
+ require 'radfish/supermicro_adapter'
87
+ rescue LoadError
88
+ # radfish-supermicro gem not installed
89
+ end
data/radfish.gemspec ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/radfish/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "radfish"
7
+ spec.version = Radfish::VERSION
8
+ spec.authors = ["Jonathan Siegel"]
9
+ spec.email = ["248302+usiegj00@users.noreply.github.com"]
10
+
11
+ spec.summary = "Unified Redfish API Client for Server Management"
12
+ spec.description = "A Ruby client library that provides a unified interface for managing servers via Redfish API across multiple hardware vendors (Dell, HPE, Supermicro, etc.) with automatic vendor detection and adaptation"
13
+ spec.homepage = "https://github.com/buildio/radfish"
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/radfish"
21
+ spec.metadata["changelog_uri"] = "https://github.com/buildio/radfish/blob/main/CHANGELOG.md"
22
+
23
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
+ Dir["{exe,lib}/**/*", "LICENSE", "Rakefile", "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 "thor", "~> 1.2"
32
+ spec.add_dependency "httparty", "~> 0.21"
33
+ spec.add_dependency "faraday", "~> 2.0"
34
+ spec.add_dependency "faraday-multipart", "~> 1.0"
35
+ spec.add_dependency "nokogiri", "~> 1.14"
36
+ spec.add_dependency "colorize", ">= 0.8"
37
+ spec.add_dependency "activesupport", "~> 7.0"
38
+
39
+ # Include common vendor adapters by default
40
+ spec.add_dependency "radfish-supermicro", "~> 0.1"
41
+ spec.add_dependency "radfish-idrac", "~> 0.1"
42
+
43
+ spec.add_development_dependency "rspec", "~> 3.0"
44
+ spec.add_development_dependency "webmock", "~> 3.0"
45
+ spec.add_development_dependency "debug", "~> 1.0"
46
+ end
metadata ADDED
@@ -0,0 +1,238 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: radfish
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: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.21'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.21'
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday-multipart
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: nokogiri
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.14'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.14'
83
+ - !ruby/object:Gem::Dependency
84
+ name: colorize
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0.8'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0.8'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activesupport
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '7.0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '7.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: radfish-supermicro
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.1'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.1'
125
+ - !ruby/object:Gem::Dependency
126
+ name: radfish-idrac
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.1'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.1'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: webmock
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '3.0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '3.0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: debug
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '1.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '1.0'
181
+ description: A Ruby client library that provides a unified interface for managing
182
+ servers via Redfish API across multiple hardware vendors (Dell, HPE, Supermicro,
183
+ etc.) with automatic vendor detection and adaptation
184
+ email:
185
+ - 248302+usiegj00@users.noreply.github.com
186
+ executables:
187
+ - radfish
188
+ extensions: []
189
+ extra_rdoc_files: []
190
+ files:
191
+ - LICENSE
192
+ - README.md
193
+ - Rakefile
194
+ - exe/radfish
195
+ - lib/radfish.rb
196
+ - lib/radfish/cli.rb
197
+ - lib/radfish/cli/base.rb
198
+ - lib/radfish/client.rb
199
+ - lib/radfish/core/base_client.rb
200
+ - lib/radfish/core/boot.rb
201
+ - lib/radfish/core/jobs.rb
202
+ - lib/radfish/core/power.rb
203
+ - lib/radfish/core/session.rb
204
+ - lib/radfish/core/storage.rb
205
+ - lib/radfish/core/system.rb
206
+ - lib/radfish/core/utility.rb
207
+ - lib/radfish/core/virtual_media.rb
208
+ - lib/radfish/vendor_detector.rb
209
+ - lib/radfish/version.rb
210
+ - radfish.gemspec
211
+ homepage: https://github.com/buildio/radfish
212
+ licenses:
213
+ - MIT
214
+ metadata:
215
+ allowed_push_host: https://rubygems.org
216
+ homepage_uri: https://github.com/buildio/radfish
217
+ source_code_uri: https://github.com/buildio/radfish
218
+ changelog_uri: https://github.com/buildio/radfish/blob/main/CHANGELOG.md
219
+ post_install_message:
220
+ rdoc_options: []
221
+ require_paths:
222
+ - lib
223
+ required_ruby_version: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: 2.7.0
228
+ required_rubygems_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ">="
231
+ - !ruby/object:Gem::Version
232
+ version: '0'
233
+ requirements: []
234
+ rubygems_version: 3.3.26
235
+ signing_key:
236
+ specification_version: 4
237
+ summary: Unified Redfish API Client for Server Management
238
+ test_files: []