idrac 0.1.12 → 0.1.19
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 +78 -0
- data/bin/idrac +3 -1
- data/lib/idrac/client.rb +411 -19
- data/lib/idrac/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 287cc837870fd1a004f10303ac8a0b60d594f3b2f4561c322e9a56b8cfa3de78
|
4
|
+
data.tar.gz: a34978f43c1157eb3d66a7e3399f057b238d2de777e79c1bfc5a3533a178cc6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6af64f193514911149d8f902ee0f9bab6a380abc9ad740461f0903cd3dc627f45fdd13e16511da646413922e950aa9c853768acfd8c6692d27c47804a9b70d01
|
7
|
+
data.tar.gz: 8391f69048343693a387bf469f6b71de1322b994435ce3410df031f4171d2fc202607ff5ee3e75df399d5f2ffa630ba3349af798bb54eb561f5cb6c0584b26bb
|
data/README.md
CHANGED
@@ -50,6 +50,8 @@ idrac firmware:update /path/to/firmware.exe --host=192.168.1.100 --username=root
|
|
50
50
|
idrac firmware:interactive --host=192.168.1.100 --username=root --password=calvin
|
51
51
|
```
|
52
52
|
|
53
|
+
All commands automatically handle session expiration by re-authenticating when necessary, ensuring that long-running operations like firmware updates complete successfully even if the iDRAC session times out.
|
54
|
+
|
53
55
|
### Ruby API
|
54
56
|
|
55
57
|
```ruby
|
@@ -62,6 +64,9 @@ client = IDRAC.new(
|
|
62
64
|
password: 'calvin'
|
63
65
|
)
|
64
66
|
|
67
|
+
# The client automatically handles session expiration (401 errors)
|
68
|
+
# by re-authenticating and retrying the request
|
69
|
+
|
65
70
|
# Take a screenshot (using the client convenience method)
|
66
71
|
filename = client.screenshot
|
67
72
|
puts "Screenshot saved to: #{filename}"
|
@@ -98,6 +103,79 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
98
103
|
|
99
104
|
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
100
105
|
|
106
|
+
## Changelog
|
107
|
+
|
108
|
+
### Version 0.1.19
|
109
|
+
- **Basic Auth Session Clearing**: Implemented direct session management using Basic Authentication
|
110
|
+
- Added ability to list and delete all active sessions without requiring a session
|
111
|
+
- Improved session clearing by directly accessing the Redfish Sessions API with Basic Auth
|
112
|
+
- Enhanced force_clear_sessions to first try the direct Basic Auth approach before falling back to login/logout cycles
|
113
|
+
- Added detailed logging of session clearing operations
|
114
|
+
|
115
|
+
### Version 0.1.18
|
116
|
+
- **Direct Mode Implementation**: Added a fallback mechanism for environments with persistent session issues
|
117
|
+
- Implemented automatic switching to direct mode (Basic Auth) when session creation repeatedly fails
|
118
|
+
- Added detection of maximum sessions condition to avoid unnecessary session creation attempts
|
119
|
+
- Improved handling of authentication failures with graceful degradation to direct mode
|
120
|
+
- Added Basic Auth support for all request types when in direct mode
|
121
|
+
- Enhanced logging for better visibility into authentication mode changes
|
122
|
+
|
123
|
+
### Version 0.1.17
|
124
|
+
- **Enhanced Session Clearing**: Added aggressive session clearing functionality
|
125
|
+
- Implemented a force_clear_sessions method that attempts multiple login/logout cycles with both authentication methods
|
126
|
+
- Added automatic session clearing when maximum sessions error is encountered
|
127
|
+
- Improved retry logic after session clearing
|
128
|
+
- Added detailed logging during session clearing process for better troubleshooting
|
129
|
+
|
130
|
+
### Version 0.1.16
|
131
|
+
- **Implemented Redfish API Session Management**: Completely redesigned session handling to use the proper Redfish API
|
132
|
+
- Added support for X-Auth-Token authentication (the standard Redfish approach)
|
133
|
+
- Maintained backward compatibility with legacy session management for screenshot functionality
|
134
|
+
- Improved session creation and deletion with proper error handling
|
135
|
+
- Added fallback mechanism to legacy authentication if Redfish session creation fails
|
136
|
+
- Enhanced logging for better troubleshooting of session-related issues
|
137
|
+
|
138
|
+
### Version 0.1.15
|
139
|
+
- **Aggressive Session Management**: Implemented a more robust approach to handle persistent session issues
|
140
|
+
- Added retry counters with limits to prevent infinite loops
|
141
|
+
- Increased delays between logout and login operations (5 seconds)
|
142
|
+
- Added multiple logout attempts to ensure sessions are properly cleared
|
143
|
+
- Added pre-emptive logout before login to help clear existing sessions
|
144
|
+
- Improved error messages with attempt counters for better debugging
|
145
|
+
|
146
|
+
### Version 0.1.14
|
147
|
+
- **Enhanced Session Management**: Improved handling of "maximum number of user sessions" errors
|
148
|
+
- Added detection and handling of session limits during initial login
|
149
|
+
- Added retry mechanism with proper session cleanup
|
150
|
+
- Added delays between logout and login operations to allow server to process session changes
|
151
|
+
- Made logout operation more robust by handling potential errors
|
152
|
+
|
153
|
+
### Version 0.1.13
|
154
|
+
- **Fixed Maximum Sessions Error**: Added handling for "maximum number of user sessions" errors
|
155
|
+
- The client now properly logs out before attempting to create a new session
|
156
|
+
- Improved session management to prevent session buildup on the iDRAC server
|
157
|
+
|
158
|
+
### Version 0.1.12
|
159
|
+
- **Improved Session Handling**: Added automatic re-authentication when sessions expire (401 errors)
|
160
|
+
- The client now automatically re-logs in and retries the request when it encounters a 401 Unauthorized error
|
161
|
+
- This prevents interruptions during long-running operations when the iDRAC session times out
|
162
|
+
|
163
|
+
### Version 0.1.11
|
164
|
+
- Added support for Ruby 3.2.x (previously required Ruby 3.3.0+)
|
165
|
+
- Fixed SSL verification warnings by making SSL verification optional
|
166
|
+
- Improved error handling and reporting
|
167
|
+
|
168
|
+
### Version 0.1.10
|
169
|
+
- Added base64 gem as a dependency to address Ruby 3.4.0 compatibility warnings
|
170
|
+
- Updated dependency versions with bounded requirements
|
171
|
+
- Added proper license specification in gemspec
|
172
|
+
|
173
|
+
### Version 0.1.9
|
174
|
+
- Added screenshot functionality with custom output filename support
|
175
|
+
- Improved firmware update process with better progress reporting
|
176
|
+
- Enhanced error handling for network connectivity issues
|
177
|
+
- Added default credentials (root/calvin) to simplify command usage
|
178
|
+
|
101
179
|
## Contributing
|
102
180
|
|
103
181
|
Bug reports and pull requests are welcome on GitHub at https://github.com/usiegj00/idrac.
|
data/bin/idrac
CHANGED
@@ -19,6 +19,7 @@ module IDRAC
|
|
19
19
|
class_option :port, type: :numeric, default: 443, desc: "iDRAC port"
|
20
20
|
class_option :no_ssl, type: :boolean, default: false, desc: "Disable SSL"
|
21
21
|
class_option :verify_ssl, type: :boolean, default: false, desc: "Enable SSL verification (not recommended for iDRAC's self-signed certificates)"
|
22
|
+
class_option :direct_mode, type: :boolean, default: false, desc: "Use direct mode with Basic Auth instead of sessions (for environments with session limits)"
|
22
23
|
|
23
24
|
desc "firmware:update PATH", "Update firmware using the specified file"
|
24
25
|
method_option :wait, type: :boolean, default: true, desc: "Wait for the update to complete"
|
@@ -191,7 +192,8 @@ module IDRAC
|
|
191
192
|
password: options[:password],
|
192
193
|
port: options[:port],
|
193
194
|
use_ssl: !options[:no_ssl],
|
194
|
-
verify_ssl: options[:verify_ssl]
|
195
|
+
verify_ssl: options[:verify_ssl],
|
196
|
+
direct_mode: options[:direct_mode]
|
195
197
|
)
|
196
198
|
end
|
197
199
|
end
|
data/lib/idrac/client.rb
CHANGED
@@ -4,12 +4,14 @@ require 'nokogiri'
|
|
4
4
|
require 'base64'
|
5
5
|
require 'uri'
|
6
6
|
require 'httparty'
|
7
|
+
require 'json'
|
7
8
|
|
8
9
|
module IDRAC
|
9
10
|
class Client
|
10
11
|
attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl
|
12
|
+
attr_accessor :direct_mode
|
11
13
|
|
12
|
-
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: true)
|
14
|
+
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: true, direct_mode: false)
|
13
15
|
@host = host
|
14
16
|
@username = username
|
15
17
|
@password = password
|
@@ -18,6 +20,10 @@ module IDRAC
|
|
18
20
|
@verify_ssl = verify_ssl
|
19
21
|
@session_id = nil
|
20
22
|
@cookies = nil
|
23
|
+
@x_auth_token = nil
|
24
|
+
@direct_mode = direct_mode
|
25
|
+
@sessions_maxed = false
|
26
|
+
@tried_clearing_sessions = false
|
21
27
|
end
|
22
28
|
|
23
29
|
def connection
|
@@ -28,7 +34,234 @@ module IDRAC
|
|
28
34
|
end
|
29
35
|
end
|
30
36
|
|
31
|
-
|
37
|
+
# Force clear all sessions by attempting multiple login/logout cycles
|
38
|
+
def force_clear_sessions
|
39
|
+
puts "Attempting to force clear all sessions..."
|
40
|
+
|
41
|
+
# First try to delete sessions directly using Basic Auth
|
42
|
+
if delete_all_sessions_with_basic_auth
|
43
|
+
puts "Successfully cleared sessions using Basic Auth"
|
44
|
+
return true
|
45
|
+
end
|
46
|
+
|
47
|
+
# If direct deletion fails, try the login/logout cycle approach
|
48
|
+
puts "Falling back to login/logout cycle approach for clearing sessions..."
|
49
|
+
|
50
|
+
# Try multiple login/logout cycles with both methods
|
51
|
+
3.times do |i|
|
52
|
+
begin
|
53
|
+
# Try Redfish session first
|
54
|
+
begin
|
55
|
+
if create_redfish_session
|
56
|
+
puts "Successfully created Redfish session (attempt #{i+1})"
|
57
|
+
delete_redfish_session
|
58
|
+
puts "Deleted Redfish session (attempt #{i+1})"
|
59
|
+
sleep(2)
|
60
|
+
end
|
61
|
+
rescue => e
|
62
|
+
puts "Error during Redfish session cycle: #{e.message}"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Then try legacy session
|
66
|
+
begin
|
67
|
+
legacy_login(i)
|
68
|
+
puts "Successfully created legacy session (attempt #{i+1})"
|
69
|
+
legacy_logout
|
70
|
+
puts "Deleted legacy session (attempt #{i+1})"
|
71
|
+
sleep(2)
|
72
|
+
rescue => e
|
73
|
+
# If we get "maximum sessions" error, that's expected
|
74
|
+
if e.message.include?("maximum number of user sessions")
|
75
|
+
puts "Maximum sessions error during legacy login (expected during clearing)"
|
76
|
+
else
|
77
|
+
puts "Error during legacy session cycle: #{e.message}"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Add a longer delay between cycles
|
82
|
+
sleep(3) if i < 2
|
83
|
+
rescue => e
|
84
|
+
puts "Error during session clearing cycle #{i+1}: #{e.message}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
puts "Completed session clearing attempts"
|
89
|
+
end
|
90
|
+
|
91
|
+
# Delete all sessions using Basic Authentication
|
92
|
+
def delete_all_sessions_with_basic_auth
|
93
|
+
puts "Attempting to delete all sessions using Basic Authentication..."
|
94
|
+
|
95
|
+
# First, get the list of sessions
|
96
|
+
sessions_url = '/redfish/v1/SessionService/Sessions'
|
97
|
+
|
98
|
+
# Create a connection with Basic Auth
|
99
|
+
basic_auth_headers = {
|
100
|
+
'Authorization' => "Basic #{Base64.strict_encode64("#{username}:#{password}")}",
|
101
|
+
'Content-Type' => 'application/json'
|
102
|
+
}
|
103
|
+
|
104
|
+
begin
|
105
|
+
# Get the list of sessions
|
106
|
+
response = connection.get(sessions_url) do |req|
|
107
|
+
req.headers.merge!(basic_auth_headers)
|
108
|
+
end
|
109
|
+
|
110
|
+
if response.status != 200
|
111
|
+
puts "Failed to get sessions list: #{response.status} - #{response.body}"
|
112
|
+
return false
|
113
|
+
end
|
114
|
+
|
115
|
+
# Parse the response to get session IDs
|
116
|
+
begin
|
117
|
+
sessions_data = JSON.parse(response.body)
|
118
|
+
|
119
|
+
if sessions_data['Members'] && sessions_data['Members'].any?
|
120
|
+
puts "Found #{sessions_data['Members'].count} active sessions"
|
121
|
+
|
122
|
+
# Delete each session
|
123
|
+
success = true
|
124
|
+
sessions_data['Members'].each do |session|
|
125
|
+
session_url = session['@odata.id']
|
126
|
+
|
127
|
+
# Skip if no URL
|
128
|
+
next unless session_url
|
129
|
+
|
130
|
+
# Delete the session
|
131
|
+
delete_response = connection.delete(session_url) do |req|
|
132
|
+
req.headers.merge!(basic_auth_headers)
|
133
|
+
end
|
134
|
+
|
135
|
+
if delete_response.status == 200 || delete_response.status == 204
|
136
|
+
puts "Successfully deleted session: #{session_url}"
|
137
|
+
else
|
138
|
+
puts "Failed to delete session #{session_url}: #{delete_response.status}"
|
139
|
+
success = false
|
140
|
+
end
|
141
|
+
|
142
|
+
# Small delay between deletions
|
143
|
+
sleep(1)
|
144
|
+
end
|
145
|
+
|
146
|
+
return success
|
147
|
+
else
|
148
|
+
puts "No active sessions found"
|
149
|
+
return true
|
150
|
+
end
|
151
|
+
rescue JSON::ParserError => e
|
152
|
+
puts "Error parsing sessions response: #{e.message}"
|
153
|
+
return false
|
154
|
+
end
|
155
|
+
rescue => e
|
156
|
+
puts "Error during session deletion with Basic Auth: #{e.message}"
|
157
|
+
return false
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Create a Redfish session (preferred method for all Redfish API calls)
|
162
|
+
def create_redfish_session
|
163
|
+
# Skip if we're in direct mode or we know sessions are maxed out
|
164
|
+
if @direct_mode || @sessions_maxed
|
165
|
+
puts "Skipping Redfish session creation (direct mode or sessions maxed)"
|
166
|
+
return false
|
167
|
+
end
|
168
|
+
|
169
|
+
url = '/redfish/v1/SessionService/Sessions'
|
170
|
+
payload = { "UserName" => username, "Password" => password }
|
171
|
+
|
172
|
+
response = connection.post(url) do |req|
|
173
|
+
req.headers['Content-Type'] = 'application/json'
|
174
|
+
req.body = payload.to_json
|
175
|
+
end
|
176
|
+
|
177
|
+
if response.status == 201 || response.status == 200
|
178
|
+
# Extract X-Auth-Token from response headers
|
179
|
+
@x_auth_token = response.headers['X-Auth-Token']
|
180
|
+
|
181
|
+
# Extract session location from response headers
|
182
|
+
@session_location = response.headers['Location']
|
183
|
+
|
184
|
+
puts "Redfish session created successfully"
|
185
|
+
@sessions_maxed = false
|
186
|
+
return true
|
187
|
+
elsif response.status == 400 && response.body.include?("maximum number of user sessions")
|
188
|
+
puts "Maximum sessions reached during Redfish session creation"
|
189
|
+
@sessions_maxed = true
|
190
|
+
|
191
|
+
# Only try to clear sessions if we haven't tried too many times
|
192
|
+
if !@tried_clearing_sessions
|
193
|
+
@tried_clearing_sessions = true
|
194
|
+
force_clear_sessions
|
195
|
+
|
196
|
+
# Try one more time after clearing
|
197
|
+
response = connection.post(url) do |req|
|
198
|
+
req.headers['Content-Type'] = 'application/json'
|
199
|
+
req.body = payload.to_json
|
200
|
+
end
|
201
|
+
|
202
|
+
if response.status == 201 || response.status == 200
|
203
|
+
@x_auth_token = response.headers['X-Auth-Token']
|
204
|
+
@session_location = response.headers['Location']
|
205
|
+
puts "Redfish session created successfully after clearing sessions"
|
206
|
+
@sessions_maxed = false
|
207
|
+
return true
|
208
|
+
else
|
209
|
+
puts "Failed to create Redfish session after clearing: #{response.status} - #{response.body}"
|
210
|
+
# If we still can't create a session, switch to direct mode
|
211
|
+
@direct_mode = true
|
212
|
+
return false
|
213
|
+
end
|
214
|
+
else
|
215
|
+
# If we've already tried clearing sessions, switch to direct mode
|
216
|
+
@direct_mode = true
|
217
|
+
return false
|
218
|
+
end
|
219
|
+
else
|
220
|
+
puts "Failed to create Redfish session: #{response.status} - #{response.body}"
|
221
|
+
return false
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
# Delete the Redfish session
|
226
|
+
def delete_redfish_session
|
227
|
+
return unless @x_auth_token && @session_location
|
228
|
+
|
229
|
+
begin
|
230
|
+
response = connection.delete(@session_location) do |req|
|
231
|
+
req.headers['X-Auth-Token'] = @x_auth_token
|
232
|
+
end
|
233
|
+
|
234
|
+
if response.status == 200 || response.status == 204
|
235
|
+
puts "Redfish session deleted successfully"
|
236
|
+
else
|
237
|
+
puts "Warning: Failed to delete Redfish session: #{response.status}"
|
238
|
+
end
|
239
|
+
rescue => e
|
240
|
+
puts "Warning: Error during Redfish session deletion: #{e.message}"
|
241
|
+
ensure
|
242
|
+
@x_auth_token = nil
|
243
|
+
@session_location = nil
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Legacy login method for screenshot functionality
|
248
|
+
def legacy_login(retry_count = 0)
|
249
|
+
# Skip if we're in direct mode
|
250
|
+
if @direct_mode
|
251
|
+
puts "Skipping legacy login (direct mode)"
|
252
|
+
return false
|
253
|
+
end
|
254
|
+
|
255
|
+
# Limit retries to prevent infinite loops
|
256
|
+
if retry_count >= 3
|
257
|
+
# If we've tried too many times, switch to direct mode
|
258
|
+
@direct_mode = true
|
259
|
+
raise Error, "Failed to login after multiple attempts due to maximum sessions limit"
|
260
|
+
end
|
261
|
+
|
262
|
+
# Always try to logout first to clear any existing sessions
|
263
|
+
legacy_logout if retry_count > 0
|
264
|
+
|
32
265
|
response = connection.post('/data/login') do |req|
|
33
266
|
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
34
267
|
req.body = "user=#{username}&password=#{password}"
|
@@ -47,6 +280,17 @@ module IDRAC
|
|
47
280
|
error_message = xml_doc.at_xpath('//errorMsg')&.text
|
48
281
|
|
49
282
|
if error_message && !error_message.empty?
|
283
|
+
# Check for maximum sessions error
|
284
|
+
if error_message.include?("maximum number of user sessions")
|
285
|
+
puts "Maximum sessions reached, attempting to clear sessions (attempt #{retry_count + 1}/3)..."
|
286
|
+
# Try to clear any existing sessions by forcing a logout
|
287
|
+
legacy_logout
|
288
|
+
# Wait longer for the server to process the logout
|
289
|
+
sleep(5)
|
290
|
+
# Try logging in again with incremented retry counter
|
291
|
+
return legacy_login(retry_count + 1)
|
292
|
+
end
|
293
|
+
|
50
294
|
raise Error, "Error Message: #{error_message}"
|
51
295
|
end
|
52
296
|
|
@@ -57,52 +301,200 @@ module IDRAC
|
|
57
301
|
end
|
58
302
|
end
|
59
303
|
|
60
|
-
|
304
|
+
# Legacy logout method for screenshot functionality
|
305
|
+
def legacy_logout
|
61
306
|
return unless @session_id
|
62
307
|
|
63
|
-
|
64
|
-
|
308
|
+
begin
|
309
|
+
# Try multiple logout attempts to ensure session is cleared
|
310
|
+
3.times do |i|
|
311
|
+
response = connection.get('/data/logout') do |req|
|
312
|
+
req.headers['Cookie'] = "sessionid=#{@session_id}"
|
313
|
+
end
|
314
|
+
|
315
|
+
break if response.status == 200
|
316
|
+
sleep(1) if i < 2 # Sleep between attempts, but not after the last one
|
317
|
+
end
|
318
|
+
rescue => e
|
319
|
+
# Ignore errors during logout
|
320
|
+
puts "Warning: Error during logout: #{e.message}"
|
321
|
+
ensure
|
322
|
+
@session_id = nil
|
323
|
+
@cookies = nil
|
65
324
|
end
|
66
325
|
|
67
|
-
|
68
|
-
|
69
|
-
|
326
|
+
true
|
327
|
+
end
|
328
|
+
|
329
|
+
# Main login method that decides which approach to use
|
330
|
+
def login
|
331
|
+
# If we're in direct mode, skip login attempts
|
332
|
+
if @direct_mode
|
333
|
+
puts "Operating in direct mode (no session)"
|
334
|
+
return true
|
335
|
+
end
|
336
|
+
|
337
|
+
# First try to create a Redfish session
|
338
|
+
if create_redfish_session
|
339
|
+
return true
|
340
|
+
else
|
341
|
+
# If we failed to create a Redfish session, try to clear sessions first
|
342
|
+
if !@tried_clearing_sessions
|
343
|
+
@tried_clearing_sessions = true
|
344
|
+
force_clear_sessions
|
345
|
+
|
346
|
+
# Try Redfish again
|
347
|
+
if create_redfish_session
|
348
|
+
return true
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# Fall back to legacy login if Redfish session creation fails
|
353
|
+
if !@direct_mode
|
354
|
+
puts "Falling back to legacy login method"
|
355
|
+
return legacy_login
|
356
|
+
else
|
357
|
+
puts "Operating in direct mode (no session)"
|
358
|
+
return true
|
359
|
+
end
|
360
|
+
end
|
70
361
|
end
|
71
362
|
|
72
|
-
|
73
|
-
|
363
|
+
# Main logout method that decides which approach to use
|
364
|
+
def logout
|
365
|
+
if @x_auth_token
|
366
|
+
delete_redfish_session
|
367
|
+
end
|
368
|
+
|
369
|
+
if @session_id
|
370
|
+
legacy_logout
|
371
|
+
end
|
372
|
+
|
373
|
+
true
|
374
|
+
end
|
375
|
+
|
376
|
+
def authenticated_request(method, path, options = {}, retry_count = 0)
|
377
|
+
# Limit retries to prevent infinite loops
|
378
|
+
if retry_count >= 3
|
379
|
+
# If we've tried too many times, switch to direct mode
|
380
|
+
@direct_mode = true
|
381
|
+
puts "Switching to direct mode after multiple authentication failures"
|
382
|
+
end
|
383
|
+
|
384
|
+
# Ensure we have a valid session (unless in direct mode)
|
385
|
+
unless @direct_mode
|
386
|
+
begin
|
387
|
+
login unless @x_auth_token || @session_id
|
388
|
+
rescue Error => e
|
389
|
+
# If login fails with maximum sessions error, try to handle it
|
390
|
+
if e.message.include?("maximum number of user sessions") && retry_count < 2
|
391
|
+
puts "Maximum sessions reached during initial login, attempting to clear sessions (attempt #{retry_count + 1}/3)..."
|
392
|
+
logout
|
393
|
+
sleep(5) # Longer delay
|
394
|
+
return authenticated_request(method, path, options, retry_count + 1)
|
395
|
+
else
|
396
|
+
# If we've tried too many times, switch to direct mode
|
397
|
+
if retry_count >= 2
|
398
|
+
@direct_mode = true
|
399
|
+
puts "Switching to direct mode after multiple authentication failures"
|
400
|
+
else
|
401
|
+
# Re-raise other errors or if we've tried too many times
|
402
|
+
raise
|
403
|
+
end
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
74
407
|
|
75
408
|
options[:headers] ||= {}
|
76
|
-
|
409
|
+
|
410
|
+
# Use X-Auth-Token if available (preferred Redfish method)
|
411
|
+
if @x_auth_token
|
412
|
+
options[:headers]['X-Auth-Token'] = @x_auth_token
|
413
|
+
elsif @session_id
|
414
|
+
# Fall back to session cookie if needed
|
415
|
+
options[:headers]['Cookie'] = "sessionid=#{@session_id}"
|
416
|
+
elsif @direct_mode
|
417
|
+
# In direct mode, use Basic Auth
|
418
|
+
options[:headers]['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
419
|
+
end
|
77
420
|
|
78
421
|
response = connection.send(method, path, options[:params]) do |req|
|
79
422
|
req.headers.merge!(options[:headers])
|
80
423
|
req.body = options[:body] if options[:body]
|
81
424
|
end
|
82
425
|
|
83
|
-
#
|
84
|
-
if response.status == 401
|
426
|
+
# Handle authentication errors
|
427
|
+
if response.status == 401 && !@direct_mode
|
85
428
|
puts "Session expired, re-authenticating..."
|
429
|
+
logout
|
430
|
+
sleep(3)
|
86
431
|
login
|
87
|
-
|
432
|
+
|
433
|
+
# Update headers with new authentication
|
434
|
+
options[:headers] ||= {}
|
435
|
+
if @x_auth_token
|
436
|
+
options[:headers]['X-Auth-Token'] = @x_auth_token
|
437
|
+
elsif @session_id
|
438
|
+
options[:headers]['Cookie'] = "sessionid=#{@session_id}"
|
439
|
+
elsif @direct_mode
|
440
|
+
options[:headers]['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
441
|
+
end
|
88
442
|
|
89
443
|
response = connection.send(method, path, options[:params]) do |req|
|
90
444
|
req.headers.merge!(options[:headers])
|
91
445
|
req.body = options[:body] if options[:body]
|
92
446
|
end
|
447
|
+
|
448
|
+
# If we still get a 401, switch to direct mode
|
449
|
+
if response.status == 401 && retry_count < 2
|
450
|
+
@direct_mode = true
|
451
|
+
puts "Switching to direct mode after authentication failure"
|
452
|
+
return authenticated_request(method, path, options, retry_count + 1)
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
# Check for maximum sessions error in the response
|
457
|
+
if response.status == 200
|
458
|
+
begin
|
459
|
+
xml_doc = Nokogiri::XML(response.body)
|
460
|
+
error_message = xml_doc.at_xpath('//errorMsg')&.text
|
461
|
+
|
462
|
+
if error_message && error_message.include?("maximum number of user sessions") && retry_count < 2
|
463
|
+
puts "Maximum sessions reached, clearing sessions and retrying (attempt #{retry_count + 1}/3)..."
|
464
|
+
logout
|
465
|
+
sleep(5)
|
466
|
+
login
|
467
|
+
return authenticated_request(method, path, options, retry_count + 1)
|
468
|
+
end
|
469
|
+
rescue => e
|
470
|
+
# If we can't parse the response as XML, just continue
|
471
|
+
end
|
93
472
|
end
|
94
473
|
|
95
474
|
response
|
96
475
|
end
|
97
476
|
|
98
477
|
def get(path:, headers: {})
|
478
|
+
# For screenshot functionality, we need to use the legacy cookies
|
479
|
+
if @cookies.nil? && !@direct_mode
|
480
|
+
legacy_login unless @session_id
|
481
|
+
end
|
482
|
+
|
483
|
+
headers_to_use = {
|
484
|
+
"User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
485
|
+
"Accept-Encoding" => "deflate, gzip"
|
486
|
+
}
|
487
|
+
|
488
|
+
if @cookies
|
489
|
+
headers_to_use["Cookie"] = @cookies
|
490
|
+
elsif @direct_mode
|
491
|
+
# In direct mode, use Basic Auth
|
492
|
+
headers_to_use["Authorization"] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
|
493
|
+
end
|
494
|
+
|
99
495
|
response = HTTParty.get(
|
100
496
|
"#{base_url}/#{path}",
|
101
|
-
headers:
|
102
|
-
"User-Agent" => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36",
|
103
|
-
"Accept-Encoding" => "deflate, gzip",
|
104
|
-
"Cookie" => @cookies
|
105
|
-
}.merge(headers),
|
497
|
+
headers: headers_to_use.merge(headers),
|
106
498
|
verify: false
|
107
499
|
)
|
108
500
|
end
|
data/lib/idrac/version.rb
CHANGED