radfish 0.2.12 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 56ac8a4bd0fa74d4925bd369825c0d7df1b87e77acea9a41d1327461a058e2e4
4
- data.tar.gz: 5089df7444609d31fd193e8d6c022640ec57cbda96baf26c1df920eeb17bc3ab
3
+ metadata.gz: ddba2c0632175f4f75abe456074d912b7d6644371ca78699b7979abb4e9dd3de
4
+ data.tar.gz: 44eedf5868fbea6171d2714a922f1ab35dec320293208c5472e32d7d2104ece7
5
5
  SHA512:
6
- metadata.gz: 10cdabeb27695b1a66aeb6531fa83da737b2a66e9f6ab24505a870797968ec5de49a388299627a954f1641e45ef356182535dcd7e4916bb2e8469b447c1d7dc4
7
- data.tar.gz: bb95d3be33e59529b53231948763f60803f637353a707395cbc674aa898932696868dae481108fc64545055ed26072a7fda308c948214b770635618ab850b8dc
6
+ metadata.gz: 127e46cc938fe890f04ae0ad6b68b2ea0d2643ab7ca27c0855586291f363853cfb3151b0d88860d1f02f364f1430c42bef6f2c35880b6a8cc9504b5c7962b23c
7
+ data.tar.gz: c0e1fb812a023c8ecdfcf47a701dfc2fa4b959c9a63527109b4367a960f6261d5f3a35daa0415fe706ef0f3a66c4097a103bbd0a7d1ecc25b32872191013da46
data/README.md CHANGED
@@ -35,6 +35,76 @@ Future adapters:
35
35
  - radfish-asrockrack
36
36
  ```
37
37
 
38
+ ### The HTTP layer
39
+
40
+ `Radfish::HttpClient` is the single seam between the library and the network.
41
+ Faraday is an implementation detail behind it: the vendor detector, the core
42
+ classes and the adapters talk to `HttpClient` (or to `BaseClient#http_get` and
43
+ friends, which delegate to it) and never build a Faraday connection of their
44
+ own. Everything that BMCs make awkward lives in that one class - permissive TLS
45
+ for old firmware, the `Host` header for SSH tunnels, retries, redirects, debug
46
+ logging, and the translation of transport failures into Radfish errors.
47
+
48
+ Two consequences worth knowing:
49
+
50
+ - **Callers rescue Radfish errors, not Faraday errors.** `HttpClient` converts
51
+ Faraday's connection, timeout and SSL failures into `Radfish::ConnectionError`
52
+ and `Radfish::TimeoutError`. Code above the seam that rescues `Faraday::Error`
53
+ catches nothing.
54
+ - **Adapters that wrap another gem are outside the seam.** The Dell and
55
+ Supermicro adapters delegate to the `idrac` and `supermicro` gems, which have
56
+ their own HTTP stacks, so the behaviour described here does not apply to their
57
+ requests.
58
+
59
+ #### Errors
60
+
61
+ All library errors descend from `Radfish::Error`, so one rescue catches
62
+ everything the library raises:
63
+
64
+ ```
65
+ Radfish::Error
66
+ ├── Radfish::ConnectionError # unreachable host, refused connection, TLS failure
67
+ ├── Radfish::TimeoutError # request took too long
68
+ │ └── Radfish::BootProgressTimeout
69
+ ├── Radfish::AuthenticationError # bad credentials
70
+ ├── Radfish::NotFoundError
71
+ ├── Radfish::UnsupportedVendorError # no adapter for this BMC
72
+ ├── Radfish::VirtualMediaError # + NotFound / Connection / License / Busy
73
+ └── Radfish::TaskError # + TaskTimeoutError / TaskFailedError
74
+ ```
75
+
76
+ #### Retries
77
+
78
+ Failed requests are retried with an exponential backoff on 408, 429, 500, 502,
79
+ 503 and 504, and on connection and timeout failures. **Only idempotent methods
80
+ are retried** - GET, HEAD, PUT and DELETE. A repeated POST is not safe on
81
+ Redfish: it can mean a second session, a second reset, or a second job. A caller
82
+ that knows its POST is safe to repeat can widen the set for one client:
83
+
84
+ ```ruby
85
+ Radfish::HttpClient.new(
86
+ host: '192.168.1.100',
87
+ retry_count: 3, # attempts after the first
88
+ retry_delay: 1, # initial delay, doubled each retry
89
+ retry_methods: Radfish::HttpClient::IDEMPOTENT_METHODS + [:post]
90
+ )
91
+ ```
92
+
93
+ For a whole operation rather than a single request, `BaseClient#with_retries`
94
+ wraps a block.
95
+
96
+ #### Redirects
97
+
98
+ Some BMCs (AMI MegaRAC, for one) redirect `/redfish/v1` to `/redfish/v1/`.
99
+ `HttpClient` follows redirects on GET and HEAD, up to `max_redirects` hops
100
+ (3 by default; pass `max_redirects: 0` on a call to get the 3xx response
101
+ itself). A redirect is only followed back to the same endpoint - a relative
102
+ path, or an absolute URL whose scheme, host and port match the BMC already
103
+ being addressed - because every request carries credentials and Faraday would
104
+ otherwise hand them to whatever host the `Location` header named. POST and
105
+ PATCH redirects are returned to the caller rather than followed, since
106
+ 301/302/303 do not preserve the method.
107
+
38
108
  ## Features
39
109
 
40
110
  ### Automatic Vendor Detection
@@ -183,13 +253,28 @@ All commands support these options:
183
253
  --host, -h HOST # BMC hostname or IP address
184
254
  --username, -u USER # BMC username
185
255
  --password, -p PASS # BMC password
186
- --vendor VENDOR # Force specific vendor (dell, supermicro, etc.)
256
+ --vendor, -v VENDOR # Force specific vendor (dell, supermicro, etc.)
187
257
  --port PORT # BMC port (default: 443)
258
+ --config, -c FILE # Read options from a YAML config file
188
259
  --json # Output in JSON format
189
- --verbose, -v # Enable verbose output (repeat for more verbosity)
190
- --no-verify-ssl # Skip SSL certificate verification
260
+ --verbose # Application-level progress messages
261
+ --debug [N] # HTTP-level debug output (default 2, see below)
262
+ --insecure # Skip SSL certificate verification (default: true)
191
263
  ```
192
264
 
265
+ `--verbose` and `--debug` set the same verbosity level, and `--debug` wins:
266
+
267
+ | Level | Flag | Output |
268
+ |-------|---------------|-----------------------------------------------------|
269
+ | 0 | (none) | Results only |
270
+ | 1 | `--verbose` | Progress messages from the library |
271
+ | 2 | `--debug` | Adds the HTTP request and response log |
272
+ | 3 | `--debug 3` | Adds request and response bodies, and call sites |
273
+
274
+ The `Authorization` header and password fields are filtered out of the log, but
275
+ level 3 prints request and response bodies, which can carry other sensitive
276
+ data - read it before pasting it into an issue.
277
+
193
278
  ### Output Formats
194
279
 
195
280
  #### Default (Human-Readable)
@@ -391,7 +476,7 @@ client.mount_iso_and_boot("http://example.com/os.iso")
391
476
  options = client.boot_options
392
477
 
393
478
  # Set one-time boot
394
- client.set_boot_override("Pxe", persistent: false)
479
+ client.set_boot_override("Pxe", persistence: 'Once')
395
480
 
396
481
  # Quick boot methods
397
482
  client.boot_to_pxe
@@ -457,8 +542,10 @@ Radfish::Client.new(
457
542
  use_ssl: true, # Use HTTPS
458
543
  verify_ssl: false, # Verify certificates
459
544
  direct_mode: false, # Use Basic Auth instead of sessions
460
- retry_count: 3, # Retry failed requests
461
- retry_delay: 1, # Initial delay between retries
545
+ retry_count: 3, # Retries after the first attempt
546
+ retry_delay: 1, # Initial delay between retries, doubled each time
547
+ retry_methods: nil, # Defaults to idempotent methods only
548
+ max_redirects: 3, # Same-endpoint redirects to follow (0 disables)
462
549
  verbosity: 0 # Debug output level (0-3)
463
550
  )
464
551
  ```
@@ -468,11 +555,14 @@ Radfish::Client.new(
468
555
  Enable verbose output:
469
556
 
470
557
  ```ruby
471
- client.verbosity = 1 # Basic debug info
472
- client.verbosity = 2 # Include request/response details
473
- client.verbosity = 3 # Include stack traces
558
+ client.verbosity = 1 # Progress messages from the library
559
+ client.verbosity = 2 # Adds the HTTP request and response log
560
+ client.verbosity = 3 # Adds request and response bodies, and call sites
474
561
  ```
475
562
 
563
+ These are the same levels the CLI sets with `--verbose` and `--debug N`. The
564
+ `Authorization` header and password fields are filtered out of the log.
565
+
476
566
  ## Supported Vendors
477
567
 
478
568
  Currently supported:
@@ -29,6 +29,28 @@ module Radfish
29
29
  match ? adapter.disable_boot_entries(match: match, **opts) : adapter.disable_boot_entries(**opts)
30
30
  end
31
31
 
32
+ # One-time boot to the virtual CD via the vendor's reliable path (Dell: an SCP import that drains
33
+ # any pending config job first, then sets ServerBoot BootOnce + FirstBootDevice=VCD-DVD). Returns
34
+ # the vendor result; raises NotImplementedError on an adapter with no SCP one-time-boot path.
35
+ def set_one_time_cd_boot(**opts)
36
+ require_adapter!(:set_one_time_cd_boot)
37
+ adapter.set_one_time_cd_boot(**opts)
38
+ end
39
+
40
+ # Poll the config job +jid+ (e.g. the BIOS config job a boot-source change schedules) to a
41
+ # terminal state. Returns the state string, or nil on timeout (never raises).
42
+ def wait_config_job(jid, **opts)
43
+ require_adapter!(:wait_config_job)
44
+ adapter.wait_config_job(jid, **opts)
45
+ end
46
+
47
+ # Drain every pending (non-Completed) config job so scheduling a new one does not trip LC068.
48
+ # Returns the ids drained.
49
+ def drain_config_jobs
50
+ require_adapter!(:drain_pending_config_jobs!)
51
+ adapter.drain_pending_config_jobs!
52
+ end
53
+
32
54
  # Underlying boot configuration hash (BootSourceOverride*, boot order, ...).
33
55
  def to_h
34
56
  adapter.boot_config
data/lib/radfish/cli.rb CHANGED
@@ -16,6 +16,7 @@ module Radfish
16
16
  class_option :port, type: :numeric, default: 443, desc: 'BMC port (env: RADFISH_PORT)'
17
17
  class_option :insecure, type: :boolean, default: true, desc: 'Skip SSL verification'
18
18
  class_option :verbose, type: :boolean, default: false, desc: 'Enable verbose output'
19
+ class_option :debug, type: :numeric, lazy_default: 2, desc: 'Debug output level'
19
20
  class_option :json, type: :boolean, default: false, desc: 'Output in JSON format'
20
21
 
21
22
  desc "detect", "Detect the vendor of a BMC"
@@ -25,6 +26,7 @@ module Radfish
25
26
  host: opts[:host],
26
27
  username: opts[:username],
27
28
  password: opts[:password],
29
+ verbosity: opts[:verbosity],
28
30
  port: opts[:port],
29
31
  verify_ssl: !opts[:insecure]
30
32
  )
@@ -336,7 +338,7 @@ module Radfish
336
338
  # Storage Commands
337
339
  desc "storage SUBCOMMAND", "Storage information"
338
340
  def storage(subcommand = 'summary')
339
- with_client do |client|
341
+ with_client do |client, opts|
340
342
  case subcommand
341
343
  when 'summary', 'all'
342
344
  data = client.storage_summary
@@ -370,7 +372,7 @@ module Radfish
370
372
  all_drives.concat(drives) if drives
371
373
  rescue => e
372
374
  ctrl_name = controller.respond_to?(:name) ? controller.name : 'controller'
373
- puts "Error fetching drives for #{ctrl_name}: #{e.message}".yellow if options[:verbose]
375
+ puts "Error fetching drives for #{ctrl_name}: #{e.message}".yellow if opts[:verbosity] > 0
374
376
  end
375
377
  end
376
378
 
@@ -402,7 +404,7 @@ module Radfish
402
404
  all_volumes.concat(volumes) if volumes
403
405
  rescue => e
404
406
  ctrl_name = controller.respond_to?(:name) ? controller.name : 'controller'
405
- puts "Error fetching volumes for #{ctrl_name}: #{e.message}".yellow if options[:verbose]
407
+ puts "Error fetching volumes for #{ctrl_name}: #{e.message}".yellow if opts[:verbosity] > 0
406
408
  end
407
409
  end
408
410
 
@@ -652,17 +654,17 @@ module Radfish
652
654
  password: opts[:password],
653
655
  port: opts[:port],
654
656
  verify_ssl: !opts[:insecure],
655
- direct_mode: true
657
+ direct_mode: true,
658
+ verbosity: opts[:verbosity]
656
659
  }
657
660
 
658
661
  client_opts[:vendor] = opts[:vendor] if opts[:vendor]
659
662
 
660
663
  begin
661
664
  client = Radfish::Client.new(**client_opts)
662
- client.verbosity = 1 if opts[:verbose]
663
-
665
+
664
666
  client.login
665
- yield client
667
+ yield client, opts
666
668
  rescue => e
667
669
  error "Error: #{e.message}"
668
670
  exit 1
@@ -695,7 +697,16 @@ module Radfish
695
697
  opts[:port] = options[:port] if options[:port]
696
698
  opts[:insecure] = options[:insecure] if options.key?(:insecure)
697
699
  opts[:verbose] = options[:verbose] if options.key?(:verbose)
698
-
700
+ opts[:debug] = options[:debug] if options.key?(:debug)
701
+
702
+ if opts[:debug]
703
+ opts[:verbosity] = opts[:debug].to_i
704
+ elsif opts[:verbose]
705
+ opts[:verbosity] = 1
706
+ else
707
+ opts[:verbosity] = 0
708
+ end
709
+
699
710
  # Check environment variables as fallback
700
711
  opts[:host] ||= ENV['RADFISH_HOST']
701
712
  opts[:username] ||= ENV['RADFISH_USERNAME']
@@ -713,7 +724,7 @@ module Radfish
713
724
  def safe_call
714
725
  yield
715
726
  rescue => e
716
- options[:verbose] ? e.message : 'N/A'
727
+ (options[:verbose] || options[:debug].to_i > 0) ? e.message : 'N/A'
717
728
  end
718
729
 
719
730
  def error(message)
@@ -155,4 +155,4 @@ module Radfish
155
155
  end
156
156
  end
157
157
  end
158
- end
158
+ end
@@ -7,7 +7,7 @@ module Radfish
7
7
  raise NotImplementedError, "Adapter must implement #boot_config"
8
8
  end
9
9
 
10
- def set_boot_override(target, persistent: false)
10
+ def set_boot_override(target, persistence: nil, mode: nil)
11
11
  raise NotImplementedError, "Adapter must implement #set_boot_override"
12
12
  end
13
13
 
@@ -53,4 +53,4 @@ module Radfish
53
53
  end
54
54
  end
55
55
  end
56
- end
56
+ end
@@ -108,9 +108,7 @@ module Radfish
108
108
  false
109
109
  end
110
110
  end
111
-
112
- private
113
-
111
+
114
112
  def verbosity
115
113
  client.verbosity
116
114
  end
@@ -11,12 +11,27 @@ module Radfish
11
11
  class HttpClient
12
12
  include Debuggable
13
13
 
14
+ # Redirects we follow, and the methods we follow them for. GET/HEAD only:
15
+ # 307/308 preserve the method, but 301/302/303 do not, so re-sending a POST
16
+ # body after one would be wrong. Callers doing anything else get the 3xx back.
17
+ REDIRECT_STATUSES = [301, 302, 303, 307, 308].freeze
18
+ REDIRECT_METHODS = [:get, :head].freeze
19
+
20
+ # Methods we retry by default. A repeated GET or DELETE is harmless, but a
21
+ # repeated POST is not: on Redfish it can mean a second session, a second
22
+ # reset, a second job. Callers that know a POST is safe to repeat can widen
23
+ # this per client with retry_methods:.
24
+ IDEMPOTENT_METHODS = [:get, :head, :put, :delete].freeze
25
+ RETRY_STATUSES = [408, 429, 500, 502, 503, 504].freeze
26
+
14
27
  attr_reader :host, :port, :use_ssl, :verify_ssl
15
- attr_accessor :username, :password, :verbosity, :retry_count, :retry_delay
28
+ attr_accessor :username, :password, :verbosity, :retry_count, :retry_delay,
29
+ :retry_methods, :max_redirects
16
30
 
17
31
  def initialize(host:, port: 443, use_ssl: true, verify_ssl: false,
18
32
  username: nil, password: nil, verbosity: 0,
19
- retry_count: 3, retry_delay: 1, **options)
33
+ retry_count: 3, retry_delay: 1,
34
+ retry_methods: IDEMPOTENT_METHODS, max_redirects: 3, **options)
20
35
  @host = host
21
36
  @port = port
22
37
  @use_ssl = use_ssl
@@ -26,6 +41,8 @@ module Radfish
26
41
  @verbosity = verbosity
27
42
  @retry_count = retry_count
28
43
  @retry_delay = retry_delay
44
+ @retry_methods = retry_methods
45
+ @max_redirects = max_redirects
29
46
  @options = options
30
47
  end
31
48
 
@@ -54,8 +71,12 @@ module Radfish
54
71
  request(:delete, path, headers: headers, **options)
55
72
  end
56
73
 
57
- def request(method, path, body: nil, headers: {}, auth: true, timeout: nil, **options)
74
+ # max_redirects overrides the client default for one call; pass 0 to get the
75
+ # 3xx response itself rather than what it points at.
76
+ def request(method, path, body: nil, headers: {}, auth: true, timeout: nil,
77
+ max_redirects: nil, **options)
58
78
  debug "Starting HTTP #{method.upcase} request to #{path}", 2, :yellow
79
+ redirects_left = max_redirects.nil? ? @max_redirects.to_i : max_redirects.to_i
59
80
 
60
81
  # Add host header if specified (needed for SSH tunnels to iDRAC)
61
82
  if @options[:host_header]
@@ -90,6 +111,18 @@ module Radfish
90
111
 
91
112
  debug "Request completed with status: #{response.status}", 2, :green
92
113
 
114
+ if redirects_left > 0 && redirect?(method, response)
115
+ target = safe_redirect_path(response['location'])
116
+
117
+ if target.nil?
118
+ debug "Not following redirect to #{response['location'].inspect} - it leaves #{base_url}", 1, :yellow
119
+ else
120
+ debug "Following redirect to #{target}, #{redirects_left - 1} left", 2, :yellow
121
+ return request(method, target, body: body, headers: headers, auth: auth,
122
+ timeout: timeout, max_redirects: redirects_left - 1, **options)
123
+ end
124
+ end
125
+
93
126
  response
94
127
  rescue Faraday::ConnectionFailed => e
95
128
  debug "Connection failed: #{e.message}", 1, :red
@@ -127,8 +160,42 @@ module Radfish
127
160
  raise e
128
161
  end
129
162
 
163
+ def redirect?(method, response)
164
+ REDIRECT_METHODS.include?(method) && REDIRECT_STATUSES.include?(response.status)
165
+ end
166
+
167
+ # The path (with query) to request for +location+, or nil when we must not
168
+ # follow it. Every request we make carries credentials -- Basic auth here, an
169
+ # X-Auth-Token on the adapters' authenticated_request -- and Faraday honours an
170
+ # absolute URL by replacing the host, so a BMC answering with
171
+ # "Location: https://elsewhere/" would be handed those credentials. Accept a
172
+ # relative path, or an absolute URL for this same endpoint, and return only the
173
+ # path so the request stays on this connection.
174
+ #
175
+ # Note: with an SSH tunnel (host_header set) an absolute redirect to the BMC's
176
+ # own name is refused, since that name is not the host we connect to. Relative
177
+ # redirects are unaffected.
178
+ def safe_redirect_path(location)
179
+ location = location.to_s
180
+ return nil if location.empty?
181
+
182
+ uri = URI.parse(location)
183
+ return nil if uri.path.to_s.empty?
184
+ return nil unless uri.host.nil? || same_endpoint?(uri)
185
+
186
+ uri.query ? "#{uri.path}?#{uri.query}" : uri.path
187
+ rescue URI::InvalidURIError
188
+ nil
189
+ end
190
+
130
191
  private
131
192
 
193
+ def same_endpoint?(uri)
194
+ uri.host == host &&
195
+ uri.port == port &&
196
+ uri.scheme == (use_ssl ? 'https' : 'http')
197
+ end
198
+
132
199
  def connection(auth: true)
133
200
  @connections ||= {}
134
201
  cache_key = auth ? :with_auth : :without_auth
@@ -182,8 +249,8 @@ module Radfish
182
249
  Faraday::TimeoutError,
183
250
  Faraday::RetriableResponse
184
251
  ],
185
- methods: [:get, :put, :delete, :post, :patch],
186
- retry_statuses: [408, 429, 500, 502, 503, 504]
252
+ methods: retry_methods,
253
+ retry_statuses: RETRY_STATUSES
187
254
  # Removed retry_block to debug ArgumentError - can add back later
188
255
  }
189
256
 
@@ -192,8 +259,8 @@ module Radfish
192
259
  faraday.options.open_timeout = 10
193
260
 
194
261
  # Add logging if verbose
195
- if verbosity > 0
196
- faraday.response :logger, Logger.new(STDOUT), { bodies: verbosity >= 2 } do |logger|
262
+ if verbosity >= 2
263
+ faraday.response :logger, Logger.new(STDOUT), { bodies: verbosity >= 3 } do |logger|
197
264
  logger.filter(/(Authorization: Basic )([^,\n]+)/, '\1[FILTERED]')
198
265
  logger.filter(/(Password"=>"?)([^,"]+)/, '\1[FILTERED]')
199
266
  logger.filter(/("password":\s*")([^"]+)/, '\1[FILTERED]')
@@ -60,7 +60,29 @@ module Radfish
60
60
  end
61
61
 
62
62
  private
63
-
63
+
64
+ def check_response(response)
65
+ if response.status == 200
66
+ debug "Got 200 response, parsing JSON...", 2, :green
67
+ JSON.parse(response.body)
68
+ elsif HttpClient::REDIRECT_STATUSES.include?(response.status)
69
+ # HttpClient follows redirects that stay on this endpoint, so reaching
70
+ # here means it refused one or ran out of them.
71
+ debug "Redirect to #{response['location'].inspect} was not followed (HTTP #{response.status})", 1, :red
72
+ nil
73
+ elsif response.status == 401
74
+ debug "Authentication failed (HTTP 401) - check username/password", 1, :red
75
+ nil
76
+ elsif response.status == 404
77
+ debug "Redfish API not found at /redfish/v1 (HTTP 404)", 1, :red
78
+ nil
79
+ else
80
+ debug "Failed to fetch service root: HTTP #{response.status}", 1, :red
81
+ debug "Response body: #{response.body[0..200]}" if response.body && @verbosity >= 2
82
+ nil
83
+ end
84
+ end
85
+
64
86
  def fetch_service_root
65
87
  begin
66
88
  debug "About to make HTTP GET request to /redfish/v1", 2, :yellow
@@ -69,21 +91,8 @@ module Radfish
69
91
  debug "Using timeout: #{timeout}s (SSH tunnel detected)" if @host_header
70
92
  response = @http_client.get('/redfish/v1', timeout: timeout)
71
93
  debug "HTTP GET request completed", 2, :green
72
-
73
- if response.status == 200
74
- debug "Got 200 response, parsing JSON...", 2, :green
75
- JSON.parse(response.body)
76
- elsif response.status == 401
77
- debug "Authentication failed (HTTP 401) - check username/password", 1, :red
78
- nil
79
- elsif response.status == 404
80
- debug "Redfish API not found at /redfish/v1 (HTTP 404)", 1, :red
81
- nil
82
- else
83
- debug "Failed to fetch service root: HTTP #{response.status}", 1, :red
84
- debug "Response body: #{response.body[0..200]}" if response.body && @verbosity >= 2
85
- nil
86
- end
94
+
95
+ check_response(response)
87
96
  rescue ConnectionError, TimeoutError => e
88
97
  debug "Connection failed to #{host}:#{port} - #{e.message}", 1, :red
89
98
  nil
@@ -210,4 +219,4 @@ module Radfish
210
219
  end
211
220
  end
212
221
  end
213
- end
222
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Radfish
4
- VERSION = "0.2.12"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/radfish.rb CHANGED
@@ -58,8 +58,10 @@ module Radfish
58
58
  Client.connect(**options, &block)
59
59
  end
60
60
 
61
- def detect_vendor(host:, username:, password:, **options)
62
- VendorDetector.new(host: host, username: username, password: password, **options).detect
61
+ def detect_vendor(host:, username:, password:, verbosity: 0, **options)
62
+ vendor_detector = VendorDetector.new(host: host, username: username, password: password, **options)
63
+ vendor_detector.verbosity = verbosity
64
+ vendor_detector.detect
63
65
  end
64
66
 
65
67
  def register_adapter(vendor, adapter_class)
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: radfish
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.12
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel
8
+ autorequire:
8
9
  bindir: exe
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-09-10 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: thor
@@ -197,6 +198,7 @@ metadata:
197
198
  homepage_uri: https://github.com/buildio/radfish
198
199
  source_code_uri: https://github.com/buildio/radfish
199
200
  changelog_uri: https://github.com/buildio/radfish/blob/main/CHANGELOG.md
201
+ post_install_message:
200
202
  rdoc_options: []
201
203
  require_paths:
202
204
  - lib
@@ -211,7 +213,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
211
213
  - !ruby/object:Gem::Version
212
214
  version: '0'
213
215
  requirements: []
214
- rubygems_version: 3.6.9
216
+ rubygems_version: 3.5.22
217
+ signing_key:
215
218
  specification_version: 4
216
219
  summary: Unified Redfish API Client for Server Management
217
220
  test_files: []