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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dc168577884bf076d1472ba870c60262e0cc59fcab318bffa9bd57eaa3dc040b
4
- data.tar.gz: 887c5990aed81c2addf6ecf6e7e8b229a8c57fced5090a57ca193fc07a7a5a96
3
+ metadata.gz: d38771d98568288f7d80b1f3686c511eed0f4d44cd7e66658cbbee897a0ab49b
4
+ data.tar.gz: ddf5029c40b5b3a57f138fe89e24d1dd8c055b9a07025e7be4fb49038a60abd9
5
5
  SHA512:
6
- metadata.gz: 9f8714784e0bd9324b03086aaa4dbad64cda93a4f941b6bf6d8b1d36d8e66d892a7ad10fc3cac887c9868ddd1369fe17abce12e192e3e5f60bea1bb6bcb26e0b
7
- data.tar.gz: f9f444b78243d5668eec03c717dd46b479d14deddc293c09cd30c79dd2e099777e698eadbd053d4cf13ee2d227b764df473c340ada870a3e1a470167002de05c
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
- def login
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
- def logout
192
+ # Legacy logout method for screenshot functionality
193
+ def legacy_logout
61
194
  return unless @session_id
62
195
 
63
- response = connection.get('/data/logout') do |req|
64
- req.headers['Cookie'] = "sessionid=#{@session_id}"
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
- @session_id = nil
68
- @cookies = nil
69
- response.status == 200
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
- def authenticated_request(method, path, options = {})
73
- login unless @session_id
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
- options[:headers]['Cookie'] = "sessionid=#{@session_id}"
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
- # If we get a 401 Unauthorized, try to re-login and retry the request once
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
- options[:headers]['Cookie'] = "sessionid=#{@session_id}"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IDRAC
4
- VERSION = "0.1.12"
4
+ VERSION = "0.1.17"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idrac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.12
4
+ version: 0.1.17
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel