idrac 0.1.38 → 0.1.41

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/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module IDRAC
4
- VERSION = "0.1.38"
4
+ VERSION = "0.1.41"
5
5
  end
data/lib/idrac/web.rb ADDED
@@ -0,0 +1,187 @@
1
+ require 'httparty'
2
+ require 'nokogiri'
3
+ require 'uri'
4
+ require 'colorize'
5
+
6
+ module IDRAC
7
+ class Web
8
+ attr_reader :client, :session_id, :cookies
9
+
10
+ def initialize(client)
11
+ @client = client
12
+ @session_id = nil
13
+ @cookies = nil
14
+ @tried_clearing_sessions = false
15
+ end
16
+
17
+ # Login to the WebUI
18
+ def login(retry_count = 0)
19
+ # Limit retries to prevent infinite loops
20
+ if retry_count >= 3
21
+ puts "Maximum retry count reached for WebUI login".red
22
+ return false
23
+ end
24
+
25
+ # Skip if we already have a session ID
26
+ return true if @session_id
27
+
28
+ begin
29
+ puts "Logging in to WebUI...".light_cyan
30
+
31
+ # Create the login URL
32
+ login_url = "#{base_url}/data/login"
33
+
34
+ # Create the login payload
35
+ payload = {
36
+ 'user' => client.username,
37
+ 'password' => client.password
38
+ }
39
+
40
+ # Make the login request
41
+ response = HTTParty.post(
42
+ login_url,
43
+ body: payload,
44
+ verify: client.verify_ssl,
45
+ headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }
46
+ )
47
+
48
+ # Check if the login was successful
49
+ if response.code == 200
50
+ # Extract the session ID from the response
51
+ if response.body.include?('ST2')
52
+ @session_id = response.body.match(/ST2=([^;]+)/)[1]
53
+ @cookies = response.headers['set-cookie']
54
+ puts "WebUI login successful".green
55
+ return response.body
56
+ else
57
+ puts "WebUI login failed: No session ID found in response".red
58
+ return false
59
+ end
60
+ elsif response.code == 400 && response.body.include?("maximum number of user sessions")
61
+ puts "Maximum sessions reached during WebUI login".light_red
62
+
63
+ # Try to clear sessions if auto_delete_sessions is enabled
64
+ if client.auto_delete_sessions && !@tried_clearing_sessions
65
+ puts "Auto-delete sessions is enabled, attempting to clear sessions".light_cyan
66
+ @tried_clearing_sessions = true
67
+
68
+ if client.session.force_clear_sessions
69
+ puts "Successfully cleared sessions, trying WebUI login again".green
70
+ return login(retry_count + 1)
71
+ else
72
+ puts "Failed to clear sessions for WebUI login".red
73
+ return false
74
+ end
75
+ else
76
+ puts "Auto-delete sessions is disabled or already tried clearing".light_yellow
77
+ return false
78
+ end
79
+ else
80
+ puts "WebUI login failed: #{response.code} - #{response.body}".red
81
+ return false
82
+ end
83
+ rescue => e
84
+ puts "Error during WebUI login: #{e.message}".red.bold
85
+ return false
86
+ end
87
+ end
88
+
89
+ # Logout from the WebUI
90
+ def logout
91
+ return unless @session_id
92
+
93
+ begin
94
+ puts "Logging out from WebUI...".light_cyan
95
+
96
+ # Create the logout URL
97
+ logout_url = "#{base_url}/data/logout"
98
+
99
+ # Make the logout request
100
+ response = HTTParty.get(
101
+ logout_url,
102
+ verify: client.verify_ssl,
103
+ headers: { 'Cookie' => @cookies }
104
+ )
105
+
106
+ # Check if the logout was successful
107
+ if response.code == 200
108
+ puts "WebUI logout successful".green
109
+ @session_id = nil
110
+ @cookies = nil
111
+ return true
112
+ else
113
+ puts "WebUI logout failed: #{response.code} - #{response.body}".red
114
+ return false
115
+ end
116
+ rescue => e
117
+ puts "Error during WebUI logout: #{e.message}".red.bold
118
+ return false
119
+ end
120
+ end
121
+
122
+ # Capture a screenshot
123
+ def capture_screenshot
124
+ # Login to get the forward URL and cookies
125
+ forward_url = login
126
+ return nil unless forward_url
127
+
128
+ # Extract the key-value pairs from the forward URL (format: index?ST1=ABC,ST2=DEF)
129
+ tokens = forward_url.split("?").last.split(",").inject({}) do |acc, kv|
130
+ k, v = kv.split("=")
131
+ acc[k] = v
132
+ acc
133
+ end
134
+
135
+ # Generate a timestamp for the request
136
+ timestamp_ms = (Time.now.to_f * 1000).to_i
137
+
138
+ # First request to trigger the screenshot capture
139
+ path = "data?get=consolepreview[manual%20#{timestamp_ms}]"
140
+ res = get(path: path, headers: tokens)
141
+ raise Error, "Failed to trigger screenshot capture." unless res.code.between?(200, 299)
142
+
143
+ # Wait for the screenshot to be generated
144
+ sleep 2
145
+
146
+ # Second request to get the actual screenshot image
147
+ path = "capconsole/scapture0.png?#{timestamp_ms}"
148
+ res = get(path: path, headers: tokens)
149
+ raise Error, "Failed to retrieve screenshot image." unless res.code.between?(200, 299)
150
+
151
+ # Save the screenshot to a file
152
+ filename = "idrac_screenshot_#{timestamp_ms}.png"
153
+ File.open(filename, "wb") { |f| f.write(res.body) }
154
+
155
+ # Return the filename
156
+ filename
157
+ end
158
+
159
+ # HTTP GET request for WebUI operations
160
+ def get(path:, headers: {})
161
+ headers_to_use = {
162
+ "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",
163
+ "Accept-Encoding" => "deflate, gzip"
164
+ }
165
+
166
+ if @cookies
167
+ headers_to_use["Cookie"] = @cookies
168
+ elsif client.direct_mode
169
+ # In direct mode, use Basic Auth
170
+ headers_to_use["Authorization"] = "Basic #{Base64.strict_encode64("#{client.username}:#{client.password}")}"
171
+ end
172
+
173
+ HTTParty.get(
174
+ "#{base_url}/#{path}",
175
+ headers: headers_to_use.merge(headers),
176
+ verify: false
177
+ )
178
+ end
179
+
180
+ private
181
+
182
+ def base_url
183
+ protocol = client.use_ssl ? 'https' : 'http'
184
+ "#{protocol}://#{client.host}:#{client.port}"
185
+ end
186
+ end
187
+ end
data/lib/idrac.rb CHANGED
@@ -10,17 +10,39 @@ require 'colorize'
10
10
  # If dev, required debug
11
11
  require 'debug' if ENV['RUBY_ENV'] == 'development'
12
12
 
13
- require_relative "idrac/version"
14
- require_relative "idrac/error"
15
- require_relative "idrac/client"
16
- require_relative "idrac/screenshot"
17
- require_relative "idrac/firmware"
18
- require_relative "idrac/firmware_catalog"
19
-
20
13
  module IDRAC
14
+ # Provides debugging functionality to IDRAC classes
15
+ module Debuggable
16
+ # Debug output helper - only outputs if verbosity level is high enough
17
+ def debug(message, level = 0, color = :light_cyan)
18
+ return unless respond_to?(:verbosity) && verbosity >= level
19
+ color_method = color.is_a?(Symbol) && String.method_defined?(color) ? color : :to_s
20
+ puts message.send(color_method)
21
+
22
+ # For highest verbosity, also print stack trace
23
+ if respond_to?(:verbosity) && verbosity >= 3 && caller.length > 1
24
+ puts " Called from:".light_yellow
25
+ caller[1..3].each do |call|
26
+ puts " #{call}".light_yellow
27
+ end
28
+ end
29
+ end
30
+ end
31
+
21
32
  class Error < StandardError; end
22
33
 
23
34
  def self.new(options = {})
24
35
  Client.new(options)
25
36
  end
26
37
  end
38
+
39
+ require_relative "idrac/version"
40
+ require_relative "idrac/error"
41
+ require_relative "idrac/session"
42
+ require_relative "idrac/web"
43
+ require_relative "idrac/power"
44
+ require_relative "idrac/jobs"
45
+ require_relative "idrac/lifecycle"
46
+ require_relative "idrac/client"
47
+ require_relative "idrac/firmware"
48
+ require_relative "idrac/firmware_catalog"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: idrac
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.38
4
+ version: 0.1.41
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Siegel
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-02 00:00:00.000000000 Z
11
+ date: 2025-04-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httparty
@@ -224,8 +224,12 @@ files:
224
224
  - lib/idrac/error.rb
225
225
  - lib/idrac/firmware.rb
226
226
  - lib/idrac/firmware_catalog.rb
227
- - lib/idrac/screenshot.rb
227
+ - lib/idrac/jobs.rb
228
+ - lib/idrac/lifecycle.rb
229
+ - lib/idrac/power.rb
230
+ - lib/idrac/session.rb
228
231
  - lib/idrac/version.rb
232
+ - lib/idrac/web.rb
229
233
  homepage: http://github.com
230
234
  licenses:
231
235
  - MIT
@@ -246,7 +250,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
246
250
  - !ruby/object:Gem::Version
247
251
  version: '0'
248
252
  requirements: []
249
- rubygems_version: 3.4.19
253
+ rubygems_version: 3.5.16
250
254
  signing_key:
251
255
  specification_version: 4
252
256
  summary: API Client for Dell iDRAC
@@ -1,49 +0,0 @@
1
- require 'httparty'
2
- require 'nokogiri'
3
-
4
- module IDRAC
5
- # Reverse engineered screenshot functionality for iDRAC
6
- # This uses introspection on how the web UI creates screenshots rather than the Redfish API
7
- class Screenshot
8
- attr_reader :client
9
-
10
- def initialize(client)
11
- @client = client
12
- end
13
-
14
- def capture
15
- # Login to get the forward URL and cookies
16
- forward_url = client.login
17
-
18
- # Extract the key-value pairs from the forward URL (format: index?ST1=ABC,ST2=DEF)
19
- tokens = forward_url.split("?").last.split(",").inject({}) do |acc, kv|
20
- k, v = kv.split("=")
21
- acc[k] = v
22
- acc
23
- end
24
-
25
- # Generate a timestamp for the request
26
- timestamp_ms = (Time.now.to_f * 1000).to_i
27
-
28
- # First request to trigger the screenshot capture
29
- path = "data?get=consolepreview[manual%20#{timestamp_ms}]"
30
- res = client.get(path: path, headers: tokens)
31
- raise Error, "Failed to trigger screenshot capture." unless res.code.between?(200, 299)
32
-
33
- # Wait for the screenshot to be generated
34
- sleep 2
35
-
36
- # Second request to get the actual screenshot image
37
- path = "capconsole/scapture0.png?#{timestamp_ms}"
38
- res = client.get(path: path, headers: tokens)
39
- raise Error, "Failed to retrieve screenshot image." unless res.code.between?(200, 299)
40
-
41
- # Save the screenshot to a file
42
- filename = "idrac_screenshot_#{timestamp_ms}.png"
43
- File.open(filename, "wb") { |f| f.write(res.body) }
44
-
45
- # Return the filename
46
- filename
47
- end
48
- end
49
- end