idrac 0.1.12 → 0.1.17
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 +63 -0
- data/lib/idrac/client.rb +248 -12
- 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: d38771d98568288f7d80b1f3686c511eed0f4d44cd7e66658cbbee897a0ab49b
|
4
|
+
data.tar.gz: ddf5029c40b5b3a57f138fe89e24d1dd8c055b9a07025e7be4fb49038a60abd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0516e7373204bfcc8b71f45ae2291f031a23a01135e956bf971663912a92833d66b0c802ea01e38813bdffbcd1a4a9463036a6ad8d3aa127fb2c8726920d1c39
|
7
|
+
data.tar.gz: 0bd081077d25c963c1a15b2f327916fa0d86774ca994607acbda2278d3326a677c7348e74b3cfea1ab6ccfe0c19f31dfdc1b462efd0e30aaafb8c3548c36514a
|
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,64 @@ 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.17
|
109
|
+
- **Enhanced Session Clearing**: Added aggressive session clearing functionality
|
110
|
+
- Implemented a force_clear_sessions method that attempts multiple login/logout cycles with both authentication methods
|
111
|
+
- Added automatic session clearing when maximum sessions error is encountered
|
112
|
+
- Improved retry logic after session clearing
|
113
|
+
- Added detailed logging during session clearing process for better troubleshooting
|
114
|
+
|
115
|
+
### Version 0.1.16
|
116
|
+
- **Implemented Redfish API Session Management**: Completely redesigned session handling to use the proper Redfish API
|
117
|
+
- Added support for X-Auth-Token authentication (the standard Redfish approach)
|
118
|
+
- Maintained backward compatibility with legacy session management for screenshot functionality
|
119
|
+
- Improved session creation and deletion with proper error handling
|
120
|
+
- Added fallback mechanism to legacy authentication if Redfish session creation fails
|
121
|
+
- Enhanced logging for better troubleshooting of session-related issues
|
122
|
+
|
123
|
+
### Version 0.1.15
|
124
|
+
- **Aggressive Session Management**: Implemented a more robust approach to handle persistent session issues
|
125
|
+
- Added retry counters with limits to prevent infinite loops
|
126
|
+
- Increased delays between logout and login operations (5 seconds)
|
127
|
+
- Added multiple logout attempts to ensure sessions are properly cleared
|
128
|
+
- Added pre-emptive logout before login to help clear existing sessions
|
129
|
+
- Improved error messages with attempt counters for better debugging
|
130
|
+
|
131
|
+
### Version 0.1.14
|
132
|
+
- **Enhanced Session Management**: Improved handling of "maximum number of user sessions" errors
|
133
|
+
- Added detection and handling of session limits during initial login
|
134
|
+
- Added retry mechanism with proper session cleanup
|
135
|
+
- Added delays between logout and login operations to allow server to process session changes
|
136
|
+
- Made logout operation more robust by handling potential errors
|
137
|
+
|
138
|
+
### Version 0.1.13
|
139
|
+
- **Fixed Maximum Sessions Error**: Added handling for "maximum number of user sessions" errors
|
140
|
+
- The client now properly logs out before attempting to create a new session
|
141
|
+
- Improved session management to prevent session buildup on the iDRAC server
|
142
|
+
|
143
|
+
### Version 0.1.12
|
144
|
+
- **Improved Session Handling**: Added automatic re-authentication when sessions expire (401 errors)
|
145
|
+
- The client now automatically re-logs in and retries the request when it encounters a 401 Unauthorized error
|
146
|
+
- This prevents interruptions during long-running operations when the iDRAC session times out
|
147
|
+
|
148
|
+
### Version 0.1.11
|
149
|
+
- Added support for Ruby 3.2.x (previously required Ruby 3.3.0+)
|
150
|
+
- Fixed SSL verification warnings by making SSL verification optional
|
151
|
+
- Improved error handling and reporting
|
152
|
+
|
153
|
+
### Version 0.1.10
|
154
|
+
- Added base64 gem as a dependency to address Ruby 3.4.0 compatibility warnings
|
155
|
+
- Updated dependency versions with bounded requirements
|
156
|
+
- Added proper license specification in gemspec
|
157
|
+
|
158
|
+
### Version 0.1.9
|
159
|
+
- Added screenshot functionality with custom output filename support
|
160
|
+
- Improved firmware update process with better progress reporting
|
161
|
+
- Enhanced error handling for network connectivity issues
|
162
|
+
- Added default credentials (root/calvin) to simplify command usage
|
163
|
+
|
101
164
|
## Contributing
|
102
165
|
|
103
166
|
Bug reports and pull requests are welcome on GitHub at https://github.com/usiegj00/idrac.
|
data/lib/idrac/client.rb
CHANGED
@@ -18,6 +18,7 @@ module IDRAC
|
|
18
18
|
@verify_ssl = verify_ssl
|
19
19
|
@session_id = nil
|
20
20
|
@cookies = nil
|
21
|
+
@x_auth_token = nil
|
21
22
|
end
|
22
23
|
|
23
24
|
def connection
|
@@ -28,7 +29,127 @@ module IDRAC
|
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
+
# Force clear all sessions by attempting multiple login/logout cycles
|
33
|
+
def force_clear_sessions
|
34
|
+
puts "Attempting to force clear all sessions..."
|
35
|
+
|
36
|
+
# Try multiple login/logout cycles with both methods
|
37
|
+
3.times do |i|
|
38
|
+
begin
|
39
|
+
# Try Redfish session first
|
40
|
+
begin
|
41
|
+
if create_redfish_session
|
42
|
+
puts "Successfully created Redfish session (attempt #{i+1})"
|
43
|
+
delete_redfish_session
|
44
|
+
puts "Deleted Redfish session (attempt #{i+1})"
|
45
|
+
sleep(2)
|
46
|
+
end
|
47
|
+
rescue => e
|
48
|
+
puts "Error during Redfish session cycle: #{e.message}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Then try legacy session
|
52
|
+
begin
|
53
|
+
legacy_login(i)
|
54
|
+
puts "Successfully created legacy session (attempt #{i+1})"
|
55
|
+
legacy_logout
|
56
|
+
puts "Deleted legacy session (attempt #{i+1})"
|
57
|
+
sleep(2)
|
58
|
+
rescue => e
|
59
|
+
# If we get "maximum sessions" error, that's expected
|
60
|
+
if e.message.include?("maximum number of user sessions")
|
61
|
+
puts "Maximum sessions error during legacy login (expected during clearing)"
|
62
|
+
else
|
63
|
+
puts "Error during legacy session cycle: #{e.message}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Add a longer delay between cycles
|
68
|
+
sleep(3) if i < 2
|
69
|
+
rescue => e
|
70
|
+
puts "Error during session clearing cycle #{i+1}: #{e.message}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
puts "Completed session clearing attempts"
|
75
|
+
end
|
76
|
+
|
77
|
+
# Create a Redfish session (preferred method for all Redfish API calls)
|
78
|
+
def create_redfish_session
|
79
|
+
url = '/redfish/v1/SessionService/Sessions'
|
80
|
+
payload = { "UserName" => username, "Password" => password }
|
81
|
+
|
82
|
+
response = connection.post(url) do |req|
|
83
|
+
req.headers['Content-Type'] = 'application/json'
|
84
|
+
req.body = payload.to_json
|
85
|
+
end
|
86
|
+
|
87
|
+
if response.status == 201 || response.status == 200
|
88
|
+
# Extract X-Auth-Token from response headers
|
89
|
+
@x_auth_token = response.headers['X-Auth-Token']
|
90
|
+
|
91
|
+
# Extract session location from response headers
|
92
|
+
@session_location = response.headers['Location']
|
93
|
+
|
94
|
+
puts "Redfish session created successfully"
|
95
|
+
return true
|
96
|
+
elsif response.status == 400 && response.body.include?("maximum number of user sessions")
|
97
|
+
puts "Maximum sessions reached during Redfish session creation"
|
98
|
+
force_clear_sessions
|
99
|
+
|
100
|
+
# Try one more time after clearing
|
101
|
+
response = connection.post(url) do |req|
|
102
|
+
req.headers['Content-Type'] = 'application/json'
|
103
|
+
req.body = payload.to_json
|
104
|
+
end
|
105
|
+
|
106
|
+
if response.status == 201 || response.status == 200
|
107
|
+
@x_auth_token = response.headers['X-Auth-Token']
|
108
|
+
@session_location = response.headers['Location']
|
109
|
+
puts "Redfish session created successfully after clearing sessions"
|
110
|
+
return true
|
111
|
+
else
|
112
|
+
puts "Failed to create Redfish session after clearing: #{response.status} - #{response.body}"
|
113
|
+
return false
|
114
|
+
end
|
115
|
+
else
|
116
|
+
puts "Failed to create Redfish session: #{response.status} - #{response.body}"
|
117
|
+
return false
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Delete the Redfish session
|
122
|
+
def delete_redfish_session
|
123
|
+
return unless @x_auth_token && @session_location
|
124
|
+
|
125
|
+
begin
|
126
|
+
response = connection.delete(@session_location) do |req|
|
127
|
+
req.headers['X-Auth-Token'] = @x_auth_token
|
128
|
+
end
|
129
|
+
|
130
|
+
if response.status == 200 || response.status == 204
|
131
|
+
puts "Redfish session deleted successfully"
|
132
|
+
else
|
133
|
+
puts "Warning: Failed to delete Redfish session: #{response.status}"
|
134
|
+
end
|
135
|
+
rescue => e
|
136
|
+
puts "Warning: Error during Redfish session deletion: #{e.message}"
|
137
|
+
ensure
|
138
|
+
@x_auth_token = nil
|
139
|
+
@session_location = nil
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Legacy login method for screenshot functionality
|
144
|
+
def legacy_login(retry_count = 0)
|
145
|
+
# Limit retries to prevent infinite loops
|
146
|
+
if retry_count >= 3
|
147
|
+
raise Error, "Failed to login after multiple attempts due to maximum sessions limit"
|
148
|
+
end
|
149
|
+
|
150
|
+
# Always try to logout first to clear any existing sessions
|
151
|
+
legacy_logout if retry_count > 0
|
152
|
+
|
32
153
|
response = connection.post('/data/login') do |req|
|
33
154
|
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
34
155
|
req.body = "user=#{username}&password=#{password}"
|
@@ -47,6 +168,17 @@ module IDRAC
|
|
47
168
|
error_message = xml_doc.at_xpath('//errorMsg')&.text
|
48
169
|
|
49
170
|
if error_message && !error_message.empty?
|
171
|
+
# Check for maximum sessions error
|
172
|
+
if error_message.include?("maximum number of user sessions")
|
173
|
+
puts "Maximum sessions reached, attempting to clear sessions (attempt #{retry_count + 1}/3)..."
|
174
|
+
# Try to clear any existing sessions by forcing a logout
|
175
|
+
legacy_logout
|
176
|
+
# Wait longer for the server to process the logout
|
177
|
+
sleep(5)
|
178
|
+
# Try logging in again with incremented retry counter
|
179
|
+
return legacy_login(retry_count + 1)
|
180
|
+
end
|
181
|
+
|
50
182
|
raise Error, "Error Message: #{error_message}"
|
51
183
|
end
|
52
184
|
|
@@ -57,34 +189,115 @@ module IDRAC
|
|
57
189
|
end
|
58
190
|
end
|
59
191
|
|
60
|
-
|
192
|
+
# Legacy logout method for screenshot functionality
|
193
|
+
def legacy_logout
|
61
194
|
return unless @session_id
|
62
195
|
|
63
|
-
|
64
|
-
|
196
|
+
begin
|
197
|
+
# Try multiple logout attempts to ensure session is cleared
|
198
|
+
3.times do |i|
|
199
|
+
response = connection.get('/data/logout') do |req|
|
200
|
+
req.headers['Cookie'] = "sessionid=#{@session_id}"
|
201
|
+
end
|
202
|
+
|
203
|
+
break if response.status == 200
|
204
|
+
sleep(1) if i < 2 # Sleep between attempts, but not after the last one
|
205
|
+
end
|
206
|
+
rescue => e
|
207
|
+
# Ignore errors during logout
|
208
|
+
puts "Warning: Error during logout: #{e.message}"
|
209
|
+
ensure
|
210
|
+
@session_id = nil
|
211
|
+
@cookies = nil
|
65
212
|
end
|
66
213
|
|
67
|
-
|
68
|
-
|
69
|
-
|
214
|
+
true
|
215
|
+
end
|
216
|
+
|
217
|
+
# Main login method that decides which approach to use
|
218
|
+
def login
|
219
|
+
# First try to create a Redfish session
|
220
|
+
if create_redfish_session
|
221
|
+
return true
|
222
|
+
else
|
223
|
+
# If we failed to create a Redfish session, try to clear sessions first
|
224
|
+
force_clear_sessions
|
225
|
+
|
226
|
+
# Try Redfish again
|
227
|
+
if create_redfish_session
|
228
|
+
return true
|
229
|
+
else
|
230
|
+
# Fall back to legacy login if Redfish session creation fails
|
231
|
+
puts "Falling back to legacy login method"
|
232
|
+
return legacy_login
|
233
|
+
end
|
234
|
+
end
|
70
235
|
end
|
71
236
|
|
72
|
-
|
73
|
-
|
237
|
+
# Main logout method that decides which approach to use
|
238
|
+
def logout
|
239
|
+
if @x_auth_token
|
240
|
+
delete_redfish_session
|
241
|
+
end
|
242
|
+
|
243
|
+
if @session_id
|
244
|
+
legacy_logout
|
245
|
+
end
|
246
|
+
|
247
|
+
true
|
248
|
+
end
|
249
|
+
|
250
|
+
def authenticated_request(method, path, options = {}, retry_count = 0)
|
251
|
+
# Limit retries to prevent infinite loops
|
252
|
+
if retry_count >= 3
|
253
|
+
raise Error, "Failed to complete request after multiple attempts due to session issues"
|
254
|
+
end
|
255
|
+
|
256
|
+
# Ensure we have a valid session
|
257
|
+
begin
|
258
|
+
login unless @x_auth_token || @session_id
|
259
|
+
rescue Error => e
|
260
|
+
# If login fails with maximum sessions error, try to handle it
|
261
|
+
if e.message.include?("maximum number of user sessions") && retry_count < 2
|
262
|
+
puts "Maximum sessions reached during initial login, attempting to clear sessions (attempt #{retry_count + 1}/3)..."
|
263
|
+
logout
|
264
|
+
sleep(5) # Longer delay
|
265
|
+
return authenticated_request(method, path, options, retry_count + 1)
|
266
|
+
else
|
267
|
+
# Re-raise other errors or if we've tried too many times
|
268
|
+
raise
|
269
|
+
end
|
270
|
+
end
|
74
271
|
|
75
272
|
options[:headers] ||= {}
|
76
|
-
|
273
|
+
|
274
|
+
# Use X-Auth-Token if available (preferred Redfish method)
|
275
|
+
if @x_auth_token
|
276
|
+
options[:headers]['X-Auth-Token'] = @x_auth_token
|
277
|
+
elsif @session_id
|
278
|
+
# Fall back to session cookie if needed
|
279
|
+
options[:headers]['Cookie'] = "sessionid=#{@session_id}"
|
280
|
+
end
|
77
281
|
|
78
282
|
response = connection.send(method, path, options[:params]) do |req|
|
79
283
|
req.headers.merge!(options[:headers])
|
80
284
|
req.body = options[:body] if options[:body]
|
81
285
|
end
|
82
286
|
|
83
|
-
#
|
287
|
+
# Handle authentication errors
|
84
288
|
if response.status == 401
|
85
289
|
puts "Session expired, re-authenticating..."
|
290
|
+
logout
|
291
|
+
sleep(3)
|
86
292
|
login
|
87
|
-
|
293
|
+
|
294
|
+
# Update headers with new authentication
|
295
|
+
options[:headers] ||= {}
|
296
|
+
if @x_auth_token
|
297
|
+
options[:headers]['X-Auth-Token'] = @x_auth_token
|
298
|
+
elsif @session_id
|
299
|
+
options[:headers]['Cookie'] = "sessionid=#{@session_id}"
|
300
|
+
end
|
88
301
|
|
89
302
|
response = connection.send(method, path, options[:params]) do |req|
|
90
303
|
req.headers.merge!(options[:headers])
|
@@ -92,10 +305,33 @@ module IDRAC
|
|
92
305
|
end
|
93
306
|
end
|
94
307
|
|
308
|
+
# Check for maximum sessions error in the response
|
309
|
+
if response.status == 200
|
310
|
+
begin
|
311
|
+
xml_doc = Nokogiri::XML(response.body)
|
312
|
+
error_message = xml_doc.at_xpath('//errorMsg')&.text
|
313
|
+
|
314
|
+
if error_message && error_message.include?("maximum number of user sessions") && retry_count < 2
|
315
|
+
puts "Maximum sessions reached, clearing sessions and retrying (attempt #{retry_count + 1}/3)..."
|
316
|
+
logout
|
317
|
+
sleep(5)
|
318
|
+
login
|
319
|
+
return authenticated_request(method, path, options, retry_count + 1)
|
320
|
+
end
|
321
|
+
rescue => e
|
322
|
+
# If we can't parse the response as XML, just continue
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
95
326
|
response
|
96
327
|
end
|
97
328
|
|
98
329
|
def get(path:, headers: {})
|
330
|
+
# For screenshot functionality, we need to use the legacy cookies
|
331
|
+
if @cookies.nil?
|
332
|
+
legacy_login unless @session_id
|
333
|
+
end
|
334
|
+
|
99
335
|
response = HTTParty.get(
|
100
336
|
"#{base_url}/#{path}",
|
101
337
|
headers: {
|
data/lib/idrac/version.rb
CHANGED