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.
- checksums.yaml +4 -4
- data/README.md +76 -14
- data/bin/idrac +246 -89
- data/lib/idrac/client.rb +72 -382
- data/lib/idrac/firmware.rb +26 -2
- data/lib/idrac/jobs.rb +212 -0
- data/lib/idrac/lifecycle.rb +300 -0
- data/lib/idrac/power.rb +195 -0
- data/lib/idrac/session.rb +717 -0
- data/lib/idrac/version.rb +1 -1
- data/lib/idrac/web.rb +187 -0
- data/lib/idrac.rb +29 -7
- metadata +8 -4
- data/lib/idrac/screenshot.rb +0 -49
data/lib/idrac/version.rb
CHANGED
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.
|
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
|
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/
|
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.
|
253
|
+
rubygems_version: 3.5.16
|
250
254
|
signing_key:
|
251
255
|
specification_version: 4
|
252
256
|
summary: API Client for Dell iDRAC
|
data/lib/idrac/screenshot.rb
DELETED
@@ -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
|