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 +4 -4
- data/README.md +99 -9
- data/lib/radfish/boot_info.rb +22 -0
- data/lib/radfish/cli.rb +20 -9
- data/lib/radfish/core/base_client.rb +1 -1
- data/lib/radfish/core/boot.rb +2 -2
- data/lib/radfish/core/session.rb +1 -3
- data/lib/radfish/http_client.rb +74 -7
- data/lib/radfish/vendor_detector.rb +26 -17
- data/lib/radfish/version.rb +1 -1
- data/lib/radfish.rb +4 -2
- metadata +6 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ddba2c0632175f4f75abe456074d912b7d6644371ca78699b7979abb4e9dd3de
|
|
4
|
+
data.tar.gz: 44eedf5868fbea6171d2714a922f1ab35dec320293208c5472e32d7d2104ece7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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
|
|
190
|
-
--
|
|
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",
|
|
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, #
|
|
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 #
|
|
472
|
-
client.verbosity = 2 #
|
|
473
|
-
client.verbosity = 3 #
|
|
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:
|
data/lib/radfish/boot_info.rb
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
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)
|
data/lib/radfish/core/boot.rb
CHANGED
|
@@ -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,
|
|
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
|
data/lib/radfish/core/session.rb
CHANGED
data/lib/radfish/http_client.rb
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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:
|
|
186
|
-
retry_statuses:
|
|
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
|
|
196
|
-
faraday.response :logger, Logger.new(STDOUT), { bodies: verbosity >=
|
|
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
|
-
|
|
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
|
data/lib/radfish/version.rb
CHANGED
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)
|
|
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.
|
|
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:
|
|
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.
|
|
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: []
|