idrac 0.1.29 → 0.1.31

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.
data/lib/idrac/client.rb CHANGED
@@ -5,6 +5,7 @@ require 'base64'
5
5
  require 'uri'
6
6
  require 'httparty'
7
7
  require 'json'
8
+ require 'colorize'
8
9
 
9
10
  module IDRAC
10
11
  class Client
@@ -37,21 +38,21 @@ module IDRAC
37
38
 
38
39
  # Force clear all sessions by directly using Basic Auth
39
40
  def force_clear_sessions
40
- puts "Attempting to force clear all sessions..."
41
+ puts "Attempting to force clear all sessions...".light_cyan
41
42
 
42
43
  # Try to delete sessions directly using Basic Auth
43
44
  if delete_all_sessions_with_basic_auth
44
- puts "Successfully cleared sessions using Basic Auth"
45
+ puts "Successfully cleared sessions using Basic Auth".green
45
46
  return true
46
47
  else
47
- puts "Failed to clear sessions using Basic Auth"
48
+ puts "Failed to clear sessions using Basic Auth".red
48
49
  return false
49
50
  end
50
51
  end
51
52
 
52
53
  # Delete all sessions using Basic Authentication
53
54
  def delete_all_sessions_with_basic_auth
54
- puts "Attempting to delete all sessions using Basic Authentication..."
55
+ puts "Attempting to delete all sessions using Basic Authentication...".light_cyan
55
56
 
56
57
  # First, get the list of sessions
57
58
  sessions_url = '/redfish/v1/SessionService/Sessions'
@@ -69,7 +70,7 @@ module IDRAC
69
70
  end
70
71
 
71
72
  if response.status != 200
72
- puts "Failed to get sessions list: #{response.status} - #{response.body}"
73
+ puts "Failed to get sessions list: #{response.status} - #{response.body}".red
73
74
  return false
74
75
  end
75
76
 
@@ -78,7 +79,7 @@ module IDRAC
78
79
  sessions_data = JSON.parse(response.body)
79
80
 
80
81
  if sessions_data['Members'] && sessions_data['Members'].any?
81
- puts "Found #{sessions_data['Members'].count} active sessions"
82
+ puts "Found #{sessions_data['Members'].count} active sessions".light_yellow
82
83
 
83
84
  # Delete each session
84
85
  success = true
@@ -94,9 +95,9 @@ module IDRAC
94
95
  end
95
96
 
96
97
  if delete_response.status == 200 || delete_response.status == 204
97
- puts "Successfully deleted session: #{session_url}"
98
+ puts "Successfully deleted session: #{session_url}".green
98
99
  else
99
- puts "Failed to delete session #{session_url}: #{delete_response.status}"
100
+ puts "Failed to delete session #{session_url}: #{delete_response.status}".red
100
101
  success = false
101
102
  end
102
103
 
@@ -106,15 +107,15 @@ module IDRAC
106
107
 
107
108
  return success
108
109
  else
109
- puts "No active sessions found"
110
+ puts "No active sessions found".light_yellow
110
111
  return true
111
112
  end
112
113
  rescue JSON::ParserError => e
113
- puts "Error parsing sessions response: #{e.message}"
114
+ puts "Error parsing sessions response: #{e.message}".red.bold
114
115
  return false
115
116
  end
116
117
  rescue => e
117
- puts "Error during session deletion with Basic Auth: #{e.message}"
118
+ puts "Error during session deletion with Basic Auth: #{e.message}".red.bold
118
119
  return false
119
120
  end
120
121
  end
@@ -123,7 +124,7 @@ module IDRAC
123
124
  def create_redfish_session
124
125
  # Skip if we're in direct mode
125
126
  if @direct_mode
126
- puts "Skipping Redfish session creation (direct mode)"
127
+ puts "Skipping Redfish session creation (direct mode)".light_yellow
127
128
  return false
128
129
  end
129
130
 
@@ -144,12 +145,12 @@ module IDRAC
144
145
  # Extract session location from response headers
145
146
  @session_location = response.headers['Location']
146
147
 
147
- puts "Redfish session created successfully"
148
+ puts "Redfish session created successfully".green
148
149
  @sessions_maxed = false
149
150
  return true
150
151
  end
151
152
  rescue => e
152
- puts "First session creation attempt failed: #{e.message}"
153
+ puts "First session creation attempt failed: #{e.message}".light_red
153
154
  end
154
155
 
155
156
  # If that fails, try with Basic Auth
@@ -172,18 +173,18 @@ module IDRAC
172
173
  # Extract session location from response headers
173
174
  @session_location = response.headers['Location']
174
175
 
175
- puts "Redfish session created successfully with Basic Auth"
176
+ puts "Redfish session created successfully with Basic Auth".green
176
177
  @sessions_maxed = false
177
178
  return true
178
179
  elsif response.status == 400 && response.body.include?("maximum number of user sessions")
179
- puts "Maximum sessions reached during Redfish session creation"
180
+ puts "Maximum sessions reached during Redfish session creation".light_red
180
181
  @sessions_maxed = true
181
182
 
182
183
  # Try to clear sessions if auto_delete_sessions is enabled
183
184
  if @auto_delete_sessions
184
- puts "Auto-delete sessions is enabled, attempting to clear sessions"
185
+ puts "Auto-delete sessions is enabled, attempting to clear sessions".light_cyan
185
186
  if force_clear_sessions
186
- puts "Successfully cleared sessions, trying to create a new session"
187
+ puts "Successfully cleared sessions, trying to create a new session".green
187
188
 
188
189
  # Try one more time after clearing
189
190
  response = connection.post(url) do |req|
@@ -194,31 +195,31 @@ module IDRAC
194
195
  if response.status == 201 || response.status == 200
195
196
  @x_auth_token = response.headers['X-Auth-Token']
196
197
  @session_location = response.headers['Location']
197
- puts "Redfish session created successfully after clearing sessions"
198
+ puts "Redfish session created successfully after clearing sessions".green
198
199
  @sessions_maxed = false
199
200
  return true
200
201
  else
201
- puts "Failed to create Redfish session after clearing: #{response.status} - #{response.body}"
202
+ puts "Failed to create Redfish session after clearing: #{response.status} - #{response.body}".red
202
203
  # If we still can't create a session, switch to direct mode
203
204
  @direct_mode = true
204
205
  return false
205
206
  end
206
207
  else
207
- puts "Failed to clear sessions, switching to direct mode"
208
+ puts "Failed to clear sessions, switching to direct mode".light_yellow
208
209
  @direct_mode = true
209
210
  return false
210
211
  end
211
212
  else
212
- puts "Auto-delete sessions is disabled, switching to direct mode"
213
+ puts "Auto-delete sessions is disabled, switching to direct mode".light_yellow
213
214
  @direct_mode = true
214
215
  return false
215
216
  end
216
217
  else
217
- puts "Failed to create Redfish session: #{response.status} - #{response.body}"
218
+ puts "Failed to create Redfish session: #{response.status} - #{response.body}".red
218
219
 
219
220
  # If we get a 415 error, try with form-urlencoded
220
221
  if response.status == 415
221
- puts "Trying with form-urlencoded content type"
222
+ puts "Trying with form-urlencoded content type".light_cyan
222
223
  response = connection.post(url) do |req|
223
224
  req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
224
225
  req.headers['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
@@ -228,11 +229,11 @@ module IDRAC
228
229
  if response.status == 201 || response.status == 200
229
230
  @x_auth_token = response.headers['X-Auth-Token']
230
231
  @session_location = response.headers['Location']
231
- puts "Redfish session created successfully with form-urlencoded"
232
+ puts "Redfish session created successfully with form-urlencoded".green
232
233
  @sessions_maxed = false
233
234
  return true
234
235
  else
235
- puts "Failed with form-urlencoded too: #{response.status} - #{response.body}"
236
+ puts "Failed with form-urlencoded too: #{response.status} - #{response.body}".red
236
237
  @direct_mode = true
237
238
  return false
238
239
  end
@@ -242,7 +243,7 @@ module IDRAC
242
243
  end
243
244
  end
244
245
  rescue => e
245
- puts "Error during Redfish session creation: #{e.message}"
246
+ puts "Error during Redfish session creation: #{e.message}".red.bold
246
247
  @direct_mode = true
247
248
  return false
248
249
  end
@@ -253,116 +254,160 @@ module IDRAC
253
254
  return unless @x_auth_token && @session_location
254
255
 
255
256
  begin
257
+ puts "Deleting Redfish session...".light_cyan
258
+
259
+ # Use the X-Auth-Token for authentication
260
+ headers = { 'X-Auth-Token' => @x_auth_token }
261
+
256
262
  response = connection.delete(@session_location) do |req|
257
- req.headers['X-Auth-Token'] = @x_auth_token
263
+ req.headers.merge!(headers)
258
264
  end
259
265
 
260
266
  if response.status == 200 || response.status == 204
261
- puts "Redfish session deleted successfully"
267
+ puts "Redfish session deleted successfully".green
268
+ @x_auth_token = nil
269
+ @session_location = nil
270
+ return true
262
271
  else
263
- puts "Warning: Failed to delete Redfish session: #{response.status}"
272
+ puts "Failed to delete Redfish session: #{response.status} - #{response.body}".red
273
+ return false
264
274
  end
265
275
  rescue => e
266
- puts "Warning: Error during Redfish session deletion: #{e.message}"
267
- ensure
268
- @x_auth_token = nil
269
- @session_location = nil
276
+ puts "Error during Redfish session deletion: #{e.message}".red.bold
277
+ return false
270
278
  end
271
279
  end
272
280
 
273
- # WebUI login method for screenshot functionality only
281
+ # Login to the WebUI (for screenshot functionality)
274
282
  def webui_login(retry_count = 0)
275
283
  # Limit retries to prevent infinite loops
276
284
  if retry_count >= 3
277
- raise Error, "Failed to login to WebUI after multiple attempts"
285
+ puts "Maximum retry count reached for WebUI login".red
286
+ return false
278
287
  end
279
288
 
280
- response = connection.post('/data/login') do |req|
281
- req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
282
- req.body = "user=#{username}&password=#{password}"
283
- end
284
-
285
- if response.status == 200
286
- # Extract session ID from response
287
- doc = Nokogiri::HTML(response.body)
288
- @session_id = doc.at_css('script:contains("_this.SID")').text.match(/_this\.SID\s*=\s*"([^"]+)"/)[1] rescue nil
289
+ # Skip if we already have a session ID
290
+ return true if @session_id
291
+
292
+ begin
293
+ puts "Logging in to WebUI...".light_cyan
294
+
295
+ # Create the login URL
296
+ login_url = "#{base_url}/data/login"
289
297
 
290
- # Store cookies for screenshot functionality
291
- @cookies = response.headers['set-cookie']
298
+ # Create the login payload
299
+ payload = {
300
+ 'user' => username,
301
+ 'password' => password
302
+ }
292
303
 
293
- # Extract forward URL for screenshot functionality
294
- xml_doc = Nokogiri::XML(response.body)
295
- error_message = xml_doc.at_xpath('//errorMsg')&.text
304
+ # Make the login request
305
+ response = HTTParty.post(
306
+ login_url,
307
+ body: payload,
308
+ verify: verify_ssl,
309
+ headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }
310
+ )
296
311
 
297
- if error_message && !error_message.empty?
298
- # Check for maximum sessions error
299
- if error_message.include?("maximum number of user sessions")
300
- puts "Maximum sessions reached for WebUI, attempting to clear sessions (attempt #{retry_count + 1}/3)..."
301
- # Try to clear any existing sessions if auto_delete_sessions is enabled
302
- if @auto_delete_sessions
303
- force_clear_sessions
304
- # Wait for the server to process the session changes
305
- sleep(3)
306
- # Try logging in again with incremented retry counter
312
+ # Check if the login was successful
313
+ if response.code == 200
314
+ # Extract the session ID from the response
315
+ if response.body.include?('ST2')
316
+ @session_id = response.body.match(/ST2=([^;]+)/)[1]
317
+ @cookies = response.headers['set-cookie']
318
+ puts "WebUI login successful".green
319
+ return true
320
+ else
321
+ puts "WebUI login failed: No session ID found in response".red
322
+ return false
323
+ end
324
+ elsif response.code == 400 && response.body.include?("maximum number of user sessions")
325
+ puts "Maximum sessions reached during WebUI login".light_red
326
+
327
+ # Try to clear sessions if auto_delete_sessions is enabled
328
+ if @auto_delete_sessions && !@tried_clearing_sessions
329
+ puts "Auto-delete sessions is enabled, attempting to clear sessions".light_cyan
330
+ @tried_clearing_sessions = true
331
+
332
+ if force_clear_sessions
333
+ puts "Successfully cleared sessions, trying WebUI login again".green
307
334
  return webui_login(retry_count + 1)
308
335
  else
309
- raise Error, "Maximum sessions reached and auto-delete sessions is disabled"
336
+ puts "Failed to clear sessions for WebUI login".red
337
+ return false
310
338
  end
339
+ else
340
+ puts "Auto-delete sessions is disabled or already tried clearing".light_yellow
341
+ return false
311
342
  end
312
-
313
- raise Error, "Error Message: #{error_message}"
343
+ else
344
+ puts "WebUI login failed: #{response.code} - #{response.body}".red
345
+ return false
314
346
  end
315
-
316
- forward_url = xml_doc.xpath('//forwardUrl').text
317
- return forward_url
318
- else
319
- raise Error, "WebUI login failed with status #{response.status}: #{response.body}"
347
+ rescue => e
348
+ puts "Error during WebUI login: #{e.message}".red.bold
349
+ return false
320
350
  end
321
351
  end
322
352
 
323
- # WebUI logout method for screenshot functionality
353
+ # Logout from the WebUI
324
354
  def webui_logout
325
355
  return unless @session_id
326
356
 
327
357
  begin
328
- response = connection.get('/data/logout') do |req|
329
- req.headers['Cookie'] = "sessionid=#{@session_id}"
358
+ puts "Logging out from WebUI...".light_cyan
359
+
360
+ # Create the logout URL
361
+ logout_url = "#{base_url}/data/logout"
362
+
363
+ # Make the logout request
364
+ response = HTTParty.get(
365
+ logout_url,
366
+ verify: verify_ssl,
367
+ headers: { 'Cookie' => @cookies }
368
+ )
369
+
370
+ # Check if the logout was successful
371
+ if response.code == 200
372
+ puts "WebUI logout successful".green
373
+ @session_id = nil
374
+ @cookies = nil
375
+ return true
376
+ else
377
+ puts "WebUI logout failed: #{response.code} - #{response.body}".red
378
+ return false
330
379
  end
331
380
  rescue => e
332
- # Ignore errors during logout
333
- puts "Warning: Error during WebUI logout: #{e.message}"
334
- ensure
335
- @session_id = nil
336
- @cookies = nil
381
+ puts "Error during WebUI logout: #{e.message}".red.bold
382
+ return false
337
383
  end
338
-
339
- true
340
384
  end
341
385
 
342
- # Main login method
386
+ # Login to iDRAC
343
387
  def login
344
388
  # If we're in direct mode, skip login attempts
345
389
  if @direct_mode
346
- puts "Operating in direct mode (no session)"
390
+ puts "Using direct mode (Basic Auth) for all requests".light_yellow
347
391
  return true
348
392
  end
349
393
 
350
394
  # Try to create a Redfish session
351
395
  if create_redfish_session
396
+ puts "Successfully logged in to iDRAC using Redfish session".green
352
397
  return true
353
398
  else
354
- # If we failed to create a Redfish session and direct_mode is set, just return
355
- if @direct_mode
356
- puts "Operating in direct mode (no session)"
357
- return true
399
+ if @sessions_maxed
400
+ puts "Maximum sessions reached and could not clear sessions".light_red
358
401
  else
359
- # If we're here, something went wrong with session creation
360
- raise Error, "Failed to create a session and direct mode is not enabled"
402
+ puts "Failed to create Redfish session, falling back to direct mode".light_yellow
361
403
  end
404
+
405
+ @direct_mode = true
406
+ return true
362
407
  end
363
408
  end
364
409
 
365
- # Main logout method
410
+ # Logout from iDRAC
366
411
  def logout
367
412
  if @x_auth_token
368
413
  delete_redfish_session
@@ -372,77 +417,94 @@ module IDRAC
372
417
  webui_logout
373
418
  end
374
419
 
375
- true
420
+ puts "Logged out from iDRAC".green
421
+ return true
376
422
  end
377
423
 
424
+ # Make an authenticated request to the iDRAC
378
425
  def authenticated_request(method, path, options = {}, retry_count = 0)
379
426
  # Limit retries to prevent infinite loops
380
427
  if retry_count >= 3
381
- # If we've tried too many times, switch to direct mode
382
- @direct_mode = true
383
- puts "Switching to direct mode after multiple authentication failures"
428
+ puts "Maximum retry count reached for authenticated request".red.bold
429
+ raise Error, "Maximum retry count reached for authenticated request"
384
430
  end
385
431
 
386
- # Ensure we have a valid session (unless in direct mode)
387
- unless @direct_mode
432
+ # If we're in direct mode, use Basic Auth
433
+ if @direct_mode
434
+ # Create Basic Auth header
435
+ auth_header = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
436
+
437
+ # Add the Authorization header to the request
438
+ options[:headers] ||= {}
439
+ options[:headers]['Authorization'] = auth_header
440
+
441
+ # Make the request
388
442
  begin
389
- login unless @x_auth_token
390
- rescue Error => e
391
- # If login fails and we've tried too many times, switch to direct mode
392
- if retry_count >= 2
393
- @direct_mode = true
394
- puts "Switching to direct mode after multiple authentication failures"
395
- else
396
- # Re-raise other errors
397
- raise
443
+ response = connection.send(method, path) do |req|
444
+ req.headers.merge!(options[:headers])
445
+ req.body = options[:body] if options[:body]
398
446
  end
447
+
448
+ return response
449
+ rescue => e
450
+ puts "Error during authenticated request (direct mode): #{e.message}".red.bold
451
+ raise Error, "Error during authenticated request: #{e.message}"
399
452
  end
400
- end
401
-
402
- options[:headers] ||= {}
403
-
404
- # Use X-Auth-Token if available (preferred Redfish method)
405
- if @x_auth_token
406
- options[:headers]['X-Auth-Token'] = @x_auth_token
407
- elsif @direct_mode
408
- # In direct mode, use Basic Auth
409
- options[:headers]['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
410
- end
411
-
412
- response = connection.send(method, path, options[:params]) do |req|
413
- req.headers.merge!(options[:headers])
414
- req.body = options[:body] if options[:body]
415
- end
416
-
417
- # Handle authentication errors
418
- if response.status == 401 && !@direct_mode
419
- puts "Session expired, re-authenticating..."
420
- logout
421
- sleep(2)
422
- login
423
-
424
- # Update headers with new authentication
425
- options[:headers] ||= {}
453
+ else
454
+ # Use X-Auth-Token if available
426
455
  if @x_auth_token
456
+ # Add the X-Auth-Token header to the request
457
+ options[:headers] ||= {}
427
458
  options[:headers]['X-Auth-Token'] = @x_auth_token
428
- elsif @direct_mode
429
- options[:headers]['Authorization'] = "Basic #{Base64.strict_encode64("#{username}:#{password}")}"
430
- end
431
-
432
- response = connection.send(method, path, options[:params]) do |req|
433
- req.headers.merge!(options[:headers])
434
- req.body = options[:body] if options[:body]
435
- end
436
-
437
- # If we still get a 401, switch to direct mode
438
- if response.status == 401 && retry_count < 2
439
- @direct_mode = true
440
- puts "Switching to direct mode after authentication failure"
441
- return authenticated_request(method, path, options, retry_count + 1)
459
+
460
+ # Make the request
461
+ begin
462
+ response = connection.send(method, path) do |req|
463
+ req.headers.merge!(options[:headers])
464
+ req.body = options[:body] if options[:body]
465
+ end
466
+
467
+ # Check if the session is still valid
468
+ if response.status == 401 || response.status == 403
469
+ puts "Session expired or invalid, attempting to create a new session...".light_yellow
470
+
471
+ # Try to create a new session
472
+ if create_redfish_session
473
+ puts "Successfully created a new session, retrying request...".green
474
+ return authenticated_request(method, path, options, retry_count + 1)
475
+ else
476
+ puts "Failed to create a new session, falling back to direct mode...".light_yellow
477
+ @direct_mode = true
478
+ return authenticated_request(method, path, options, retry_count + 1)
479
+ end
480
+ end
481
+
482
+ return response
483
+ rescue => e
484
+ puts "Error during authenticated request (token mode): #{e.message}".red.bold
485
+
486
+ # Try to create a new session
487
+ if create_redfish_session
488
+ puts "Successfully created a new session after error, retrying request...".green
489
+ return authenticated_request(method, path, options, retry_count + 1)
490
+ else
491
+ puts "Failed to create a new session after error, falling back to direct mode...".light_yellow
492
+ @direct_mode = true
493
+ return authenticated_request(method, path, options, retry_count + 1)
494
+ end
495
+ end
496
+ else
497
+ # If we don't have a token, try to create a session
498
+ if create_redfish_session
499
+ puts "Successfully created a new session, making request...".green
500
+ return authenticated_request(method, path, options, retry_count + 1)
501
+ else
502
+ puts "Failed to create a session, falling back to direct mode...".light_yellow
503
+ @direct_mode = true
504
+ return authenticated_request(method, path, options, retry_count + 1)
505
+ end
442
506
  end
443
507
  end
444
-
445
- response
446
508
  end
447
509
 
448
510
  def get(path:, headers: {})