idrac 0.1.3 → 0.1.7
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 +82 -10
- data/Rakefile +1 -1
- data/bin/console +11 -0
- data/bin/idrac +179 -0
- data/bin/setup +8 -0
- data/idrac.gemspec +51 -0
- data/lib/idrac/client.rb +87 -26
- data/lib/idrac/firmware.rb +366 -0
- data/lib/idrac/screenshot.rb +41 -16
- data/lib/idrac/version.rb +2 -2
- data/lib/idrac.rb +16 -4
- metadata +130 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7861b7c63381663cd3f744ddeaf04f8dc05834511dd7d603890737df1f1d0e42
|
4
|
+
data.tar.gz: 10527f9d3a9247578727e6c50c4717427c676b4d9ff553b9cfa7d9ebf85f1e7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4b49ca40714ed11052d56d79cdf5b9b79d3a3006ca34a8fccc8b2d04cfd3323dcfcde3463aaf80ef7a4ce0ad5700f48fbb4e51d77a5c92b9a7783bdbcea2cd8b
|
7
|
+
data.tar.gz: 4ee880173ba0a0b477fb2f70fc81ebabd6586778985198f7e52e46c09460b50bd0dcbab97cb3234ea33e1cb065fb8afa238b574607d16c92db734e0884f0ae89
|
data/README.md
CHANGED
@@ -1,24 +1,96 @@
|
|
1
|
-
#
|
1
|
+
# IDRAC
|
2
2
|
|
3
|
-
|
3
|
+
A Ruby client for the Dell iDRAC API. This gem provides a command-line interface and a Ruby API for interacting with Dell iDRAC servers.
|
4
4
|
|
5
|
-
|
5
|
+
## Features
|
6
|
+
|
7
|
+
- Take screenshots of the iDRAC console
|
8
|
+
- Update firmware using Dell's catalog
|
9
|
+
- Check for firmware updates
|
10
|
+
- Interactive firmware update process
|
6
11
|
|
7
12
|
## Installation
|
8
13
|
|
9
|
-
|
14
|
+
Add this line to your application's Gemfile:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
gem 'idrac'
|
18
|
+
```
|
10
19
|
|
11
|
-
|
20
|
+
And then execute:
|
12
21
|
|
13
|
-
$ bundle
|
22
|
+
$ bundle install
|
14
23
|
|
15
|
-
|
24
|
+
Or install it yourself as:
|
16
25
|
|
17
|
-
$ gem install
|
26
|
+
$ gem install idrac
|
18
27
|
|
19
28
|
## Usage
|
20
29
|
|
21
|
-
|
30
|
+
### Command Line Interface
|
31
|
+
|
32
|
+
The gem provides a command-line interface for interacting with iDRAC servers:
|
33
|
+
|
34
|
+
```bash
|
35
|
+
# Take a screenshot of the iDRAC console
|
36
|
+
idrac screenshot --host=192.168.1.100 --username=root --password=calvin
|
37
|
+
# Specify a custom output filename
|
38
|
+
idrac screenshot --host=192.168.1.100 --username=root --password=calvin --output=my_screenshot.png
|
39
|
+
|
40
|
+
# Download the Dell firmware catalog
|
41
|
+
idrac firmware:catalog --host=192.168.1.100 --username=root --password=calvin
|
42
|
+
|
43
|
+
# Check firmware status and available updates
|
44
|
+
idrac firmware:status --host=192.168.1.100 --username=root --password=calvin
|
45
|
+
|
46
|
+
# Update firmware using a specific file
|
47
|
+
idrac firmware:update /path/to/firmware.exe --host=192.168.1.100 --username=root --password=calvin
|
48
|
+
|
49
|
+
# Interactive firmware update
|
50
|
+
idrac firmware:interactive --host=192.168.1.100 --username=root --password=calvin
|
51
|
+
```
|
52
|
+
|
53
|
+
### Ruby API
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
require 'idrac'
|
57
|
+
|
58
|
+
# Create a client
|
59
|
+
client = IDRAC.new(
|
60
|
+
host: '192.168.1.100',
|
61
|
+
username: 'root',
|
62
|
+
password: 'calvin'
|
63
|
+
)
|
64
|
+
|
65
|
+
# Take a screenshot (using the client convenience method)
|
66
|
+
filename = client.screenshot
|
67
|
+
puts "Screenshot saved to: #{filename}"
|
68
|
+
|
69
|
+
# Or use the Screenshot class directly for more control
|
70
|
+
screenshot = IDRAC::Screenshot.new(client)
|
71
|
+
filename = screenshot.capture
|
72
|
+
puts "Screenshot saved to: #{filename}"
|
73
|
+
|
74
|
+
# Firmware operations
|
75
|
+
firmware = IDRAC::Firmware.new(client)
|
76
|
+
|
77
|
+
# Download catalog
|
78
|
+
catalog_path = firmware.download_catalog
|
79
|
+
|
80
|
+
# Get system inventory
|
81
|
+
inventory = firmware.get_system_inventory
|
82
|
+
puts "Service Tag: #{inventory[:system][:service_tag]}"
|
83
|
+
|
84
|
+
# Check for updates
|
85
|
+
updates = firmware.check_updates(catalog_path)
|
86
|
+
updates.each do |update|
|
87
|
+
puts "#{update[:name]}: #{update[:current_version]} -> #{update[:available_version]}"
|
88
|
+
end
|
89
|
+
|
90
|
+
# Update firmware
|
91
|
+
job_id = firmware.update('/path/to/firmware.exe', wait: true)
|
92
|
+
puts "Update completed with job ID: #{job_id}"
|
93
|
+
```
|
22
94
|
|
23
95
|
## Development
|
24
96
|
|
@@ -28,4 +100,4 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
28
100
|
|
29
101
|
## Contributing
|
30
102
|
|
31
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
103
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/usiegj00/idrac.
|
data/Rakefile
CHANGED
@@ -12,6 +12,6 @@ task default: :spec
|
|
12
12
|
# Depend on the build task to ensure the gem is up to date.
|
13
13
|
task :release => [:build] do
|
14
14
|
system "git tag v#{Idrac::VERSION}"
|
15
|
-
system "git push
|
15
|
+
system "git push --tags"
|
16
16
|
system "gem push pkg/idrac-#{Idrac::VERSION}.gem"
|
17
17
|
end
|
data/bin/console
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "idrac"
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
|
10
|
+
require "irb"
|
11
|
+
IRB.start(__FILE__)
|
data/bin/idrac
ADDED
@@ -0,0 +1,179 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Attempt to load bundler/setup, but don't fail if it's not available
|
4
|
+
begin
|
5
|
+
require "bundler/setup"
|
6
|
+
rescue LoadError
|
7
|
+
# Continue without bundler
|
8
|
+
end
|
9
|
+
|
10
|
+
# Load required gems directly
|
11
|
+
require "thor"
|
12
|
+
require "idrac"
|
13
|
+
|
14
|
+
module IDRAC
|
15
|
+
class CLI < Thor
|
16
|
+
class_option :host, type: :string, required: true, desc: "iDRAC host address"
|
17
|
+
class_option :username, type: :string, required: true, desc: "iDRAC username"
|
18
|
+
class_option :password, type: :string, required: true, desc: "iDRAC password"
|
19
|
+
class_option :port, type: :numeric, default: 443, desc: "iDRAC port"
|
20
|
+
class_option :no_ssl, type: :boolean, default: false, desc: "Disable SSL"
|
21
|
+
class_option :no_verify_ssl, type: :boolean, default: false, desc: "Disable SSL verification"
|
22
|
+
|
23
|
+
desc "firmware:update PATH", "Update firmware using the specified file"
|
24
|
+
method_option :wait, type: :boolean, default: true, desc: "Wait for the update to complete"
|
25
|
+
method_option :timeout, type: :numeric, default: 3600, desc: "Timeout in seconds when waiting"
|
26
|
+
def firmware_update(path)
|
27
|
+
client = create_client
|
28
|
+
firmware = IDRAC::Firmware.new(client)
|
29
|
+
|
30
|
+
begin
|
31
|
+
job_id = firmware.update(path, wait: options[:wait], timeout: options[:timeout])
|
32
|
+
puts "Firmware update initiated with job ID: #{job_id}"
|
33
|
+
rescue IDRAC::Error => e
|
34
|
+
puts "Error: #{e.message}"
|
35
|
+
exit 1
|
36
|
+
ensure
|
37
|
+
client.logout
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
desc "firmware:catalog [DIRECTORY]", "Download Dell firmware catalog"
|
42
|
+
def firmware_catalog(directory = nil)
|
43
|
+
client = create_client
|
44
|
+
firmware = IDRAC::Firmware.new(client)
|
45
|
+
|
46
|
+
begin
|
47
|
+
catalog_path = firmware.download_catalog(directory)
|
48
|
+
puts "Catalog downloaded to: #{catalog_path}"
|
49
|
+
rescue IDRAC::Error => e
|
50
|
+
puts "Error: #{e.message}"
|
51
|
+
exit 1
|
52
|
+
ensure
|
53
|
+
client.logout
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "firmware:status", "Show current firmware status and available updates"
|
58
|
+
method_option :catalog, type: :string, desc: "Path to existing catalog file"
|
59
|
+
def firmware_status
|
60
|
+
client = create_client
|
61
|
+
firmware = IDRAC::Firmware.new(client)
|
62
|
+
|
63
|
+
begin
|
64
|
+
# Get system inventory
|
65
|
+
inventory = firmware.get_system_inventory
|
66
|
+
|
67
|
+
puts "System Information:"
|
68
|
+
puts " Model: #{inventory[:system][:model]}"
|
69
|
+
puts " Manufacturer: #{inventory[:system][:manufacturer]}"
|
70
|
+
puts " Service Tag: #{inventory[:system][:service_tag]}"
|
71
|
+
puts " BIOS Version: #{inventory[:system][:bios_version]}"
|
72
|
+
|
73
|
+
puts "\nInstalled Firmware:"
|
74
|
+
inventory[:firmware].each do |fw|
|
75
|
+
puts " #{fw[:name]}: #{fw[:version]} (#{fw[:updateable] ? 'Updateable' : 'Not Updateable'})"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Check for updates if catalog is available
|
79
|
+
if options[:catalog] || File.exist?(File.join(Dir.pwd, "Catalog.xml"))
|
80
|
+
catalog_path = options[:catalog] || File.join(Dir.pwd, "Catalog.xml")
|
81
|
+
puts "\nChecking for updates using catalog: #{catalog_path}"
|
82
|
+
|
83
|
+
updates = firmware.check_updates(catalog_path)
|
84
|
+
|
85
|
+
if updates.empty?
|
86
|
+
puts "No updates available."
|
87
|
+
else
|
88
|
+
puts "\nAvailable Updates:"
|
89
|
+
updates.each do |update|
|
90
|
+
puts " #{update[:name]}: #{update[:current_version]} -> #{update[:available_version]}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
else
|
94
|
+
puts "\nTo check for updates, download the catalog first with 'idrac firmware:catalog'"
|
95
|
+
end
|
96
|
+
rescue IDRAC::Error => e
|
97
|
+
puts "Error: #{e.message}"
|
98
|
+
exit 1
|
99
|
+
ensure
|
100
|
+
client.logout
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
desc "firmware:interactive", "Interactive firmware update"
|
105
|
+
method_option :catalog, type: :string, desc: "Path to existing catalog file"
|
106
|
+
def firmware_interactive
|
107
|
+
client = create_client
|
108
|
+
firmware = IDRAC::Firmware.new(client)
|
109
|
+
|
110
|
+
begin
|
111
|
+
catalog_path = options[:catalog]
|
112
|
+
|
113
|
+
# If no catalog specified, check if one exists in current directory
|
114
|
+
if catalog_path.nil? && File.exist?(File.join(Dir.pwd, "Catalog.xml"))
|
115
|
+
catalog_path = File.join(Dir.pwd, "Catalog.xml")
|
116
|
+
end
|
117
|
+
|
118
|
+
# If still no catalog, download it
|
119
|
+
if catalog_path.nil?
|
120
|
+
puts "No catalog found. Downloading..."
|
121
|
+
catalog_path = firmware.download_catalog
|
122
|
+
end
|
123
|
+
|
124
|
+
firmware.interactive_update(catalog_path)
|
125
|
+
rescue IDRAC::Error => e
|
126
|
+
puts "Error: #{e.message}"
|
127
|
+
exit 1
|
128
|
+
ensure
|
129
|
+
client.logout
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
desc "screenshot", "Take a screenshot of the current iDRAC console"
|
134
|
+
method_option :output, type: :string, desc: "Output filename (default: idrac_screenshot_timestamp.png)"
|
135
|
+
def screenshot
|
136
|
+
client = create_client
|
137
|
+
|
138
|
+
begin
|
139
|
+
# Create a Screenshot instance directly
|
140
|
+
screenshot = IDRAC::Screenshot.new(client)
|
141
|
+
filename = screenshot.capture
|
142
|
+
|
143
|
+
# Rename the file if output option is provided
|
144
|
+
if options[:output]
|
145
|
+
new_filename = options[:output]
|
146
|
+
File.rename(filename, new_filename)
|
147
|
+
filename = new_filename
|
148
|
+
end
|
149
|
+
|
150
|
+
puts "Screenshot saved to: #{filename}"
|
151
|
+
rescue IDRAC::Error => e
|
152
|
+
puts "Error: #{e.message}"
|
153
|
+
exit 1
|
154
|
+
ensure
|
155
|
+
client.logout
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
map "firmware:update" => :firmware_update
|
160
|
+
map "firmware:catalog" => :firmware_catalog
|
161
|
+
map "firmware:status" => :firmware_status
|
162
|
+
map "firmware:interactive" => :firmware_interactive
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def create_client
|
167
|
+
IDRAC::Client.new(
|
168
|
+
host: options[:host],
|
169
|
+
username: options[:username],
|
170
|
+
password: options[:password],
|
171
|
+
port: options[:port],
|
172
|
+
use_ssl: !options[:no_ssl],
|
173
|
+
verify_ssl: !options[:no_verify_ssl]
|
174
|
+
)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
IDRAC::CLI.start(ARGV)
|
data/bin/setup
ADDED
data/idrac.gemspec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/idrac/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "idrac"
|
7
|
+
spec.version = IDRAC::VERSION
|
8
|
+
spec.authors = ["Jonathan Siegel"]
|
9
|
+
spec.email = ["<248302+usiegj00@users.noreply.github.com>"]
|
10
|
+
|
11
|
+
spec.summary = "API Client for Dell iDRAC"
|
12
|
+
spec.description = "A Ruby client for the Dell iDRAC API"
|
13
|
+
spec.homepage = "http://github.com"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.2.0" # Updated to support Ruby 3.2.x
|
16
|
+
|
17
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
# spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
|
21
|
+
# spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(File.expand_path(f) == __FILE__) ||
|
28
|
+
f.start_with?(*%w[test/ spec/ features/ .git .circleci appveyor Gemfile])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
spec.bindir = "bin"
|
32
|
+
spec.executables = ["idrac"]
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
# Dependencies - Using more flexible version constraints
|
36
|
+
spec.add_dependency "httparty", ">= 0.21.0", "< 0.22.0"
|
37
|
+
spec.add_dependency "nokogiri", ">= 1.15.0", "< 1.19.0"
|
38
|
+
spec.add_dependency "faraday", ">= 2.7.0", "< 2.8.0"
|
39
|
+
spec.add_dependency "faraday-multipart", ">= 1.0.0", "< 1.1.0"
|
40
|
+
spec.add_dependency "thor", ">= 1.2.0", "< 1.4.0"
|
41
|
+
spec.add_dependency "base64", "~> 0.1", ">= 0.1.0"
|
42
|
+
|
43
|
+
# Development dependencies
|
44
|
+
spec.add_development_dependency "bundler", "~> 2.4", ">= 2.4.0"
|
45
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
46
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
47
|
+
spec.add_development_dependency "debug", "~> 1.8"
|
48
|
+
|
49
|
+
# For more information and examples about making a new gem, check out our
|
50
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
51
|
+
end
|
data/lib/idrac/client.rb
CHANGED
@@ -1,41 +1,91 @@
|
|
1
|
-
|
1
|
+
require 'faraday'
|
2
|
+
require 'faraday/multipart'
|
3
|
+
require 'nokogiri'
|
4
|
+
require 'base64'
|
5
|
+
require 'uri'
|
6
|
+
require 'httparty'
|
7
|
+
|
8
|
+
module IDRAC
|
2
9
|
class Client
|
3
|
-
|
4
|
-
|
10
|
+
attr_reader :host, :username, :password, :port, :use_ssl, :verify_ssl
|
11
|
+
|
12
|
+
def initialize(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: true)
|
13
|
+
@host = host
|
14
|
+
@username = username
|
15
|
+
@password = password
|
16
|
+
@port = port
|
17
|
+
@use_ssl = use_ssl
|
18
|
+
@verify_ssl = verify_ssl
|
19
|
+
@session_id = nil
|
5
20
|
@cookies = nil
|
6
21
|
end
|
7
22
|
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
def connection
|
24
|
+
@connection ||= Faraday.new(url: base_url, ssl: { verify: verify_ssl }) do |faraday|
|
25
|
+
faraday.request :multipart
|
26
|
+
faraday.request :url_encoded
|
27
|
+
faraday.adapter Faraday.default_adapter
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def login
|
32
|
+
response = connection.post('/data/login') do |req|
|
33
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
34
|
+
req.body = "user=#{username}&password=#{password}"
|
35
|
+
end
|
36
|
+
|
37
|
+
if response.status == 200
|
38
|
+
# Extract session ID from response
|
39
|
+
doc = Nokogiri::HTML(response.body)
|
40
|
+
@session_id = doc.at_css('script:contains("_this.SID")').text.match(/_this\.SID\s*=\s*"([^"]+)"/)[1] rescue nil
|
41
|
+
|
42
|
+
# Store cookies for screenshot functionality
|
43
|
+
@cookies = response.headers['set-cookie']
|
44
|
+
|
45
|
+
# Extract forward URL for screenshot functionality
|
46
|
+
xml_doc = Nokogiri::XML(response.body)
|
47
|
+
error_message = xml_doc.at_xpath('//errorMsg')&.text
|
48
|
+
|
49
|
+
if error_message && !error_message.empty?
|
50
|
+
raise Error, "Error Message: #{error_message}"
|
51
|
+
end
|
52
|
+
|
53
|
+
forward_url = xml_doc.xpath('//forwardUrl').text
|
54
|
+
return forward_url
|
55
|
+
else
|
56
|
+
raise Error, "Login failed with status #{response.status}: #{response.body}"
|
26
57
|
end
|
27
|
-
forward_url = xml_doc.xpath('//forwardUrl').text
|
28
58
|
end
|
29
59
|
|
30
60
|
def logout
|
31
|
-
|
32
|
-
|
33
|
-
|
61
|
+
return unless @session_id
|
62
|
+
|
63
|
+
response = connection.get('/data/logout') do |req|
|
64
|
+
req.headers['Cookie'] = "sessionid=#{@session_id}"
|
65
|
+
end
|
66
|
+
|
67
|
+
@session_id = nil
|
68
|
+
@cookies = nil
|
69
|
+
response.status == 200
|
34
70
|
end
|
71
|
+
|
72
|
+
def authenticated_request(method, path, options = {})
|
73
|
+
login unless @session_id
|
74
|
+
|
75
|
+
options[:headers] ||= {}
|
76
|
+
options[:headers]['Cookie'] = "sessionid=#{@session_id}"
|
77
|
+
|
78
|
+
response = connection.send(method, path, options[:params]) do |req|
|
79
|
+
req.headers.merge!(options[:headers])
|
80
|
+
req.body = options[:body] if options[:body]
|
81
|
+
end
|
35
82
|
|
83
|
+
response
|
84
|
+
end
|
85
|
+
|
36
86
|
def get(path:, headers: {})
|
37
87
|
response = HTTParty.get(
|
38
|
-
"#{
|
88
|
+
"#{base_url}/#{path}",
|
39
89
|
headers: {
|
40
90
|
"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",
|
41
91
|
"Accept-Encoding" => "deflate, gzip",
|
@@ -44,5 +94,16 @@ module Idrac
|
|
44
94
|
verify: false
|
45
95
|
)
|
46
96
|
end
|
97
|
+
|
98
|
+
def screenshot
|
99
|
+
# Create a Screenshot instance and capture a screenshot
|
100
|
+
screenshot_instance = Screenshot.new(self)
|
101
|
+
screenshot_instance.capture
|
102
|
+
end
|
103
|
+
|
104
|
+
def base_url
|
105
|
+
protocol = use_ssl ? 'https' : 'http'
|
106
|
+
"#{protocol}://#{host}:#{port}"
|
107
|
+
end
|
47
108
|
end
|
48
109
|
end
|
@@ -0,0 +1,366 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
require 'net/http'
|
3
|
+
require 'uri'
|
4
|
+
require 'json'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'fileutils'
|
7
|
+
require 'securerandom'
|
8
|
+
|
9
|
+
module IDRAC
|
10
|
+
class Firmware
|
11
|
+
attr_reader :client
|
12
|
+
|
13
|
+
CATALOG_URL = "https://downloads.dell.com/catalog/Catalog.xml.gz"
|
14
|
+
|
15
|
+
def initialize(client)
|
16
|
+
@client = client
|
17
|
+
end
|
18
|
+
|
19
|
+
def update(firmware_path, options = {})
|
20
|
+
# Validate firmware file exists
|
21
|
+
unless File.exist?(firmware_path)
|
22
|
+
raise Error, "Firmware file not found: #{firmware_path}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Login to iDRAC
|
26
|
+
client.login unless client.instance_variable_get(:@session_id)
|
27
|
+
|
28
|
+
# Upload firmware file
|
29
|
+
job_id = upload_firmware(firmware_path)
|
30
|
+
|
31
|
+
# Check if we should wait for the update to complete
|
32
|
+
if options[:wait]
|
33
|
+
wait_for_job_completion(job_id, options[:timeout] || 3600)
|
34
|
+
end
|
35
|
+
|
36
|
+
job_id
|
37
|
+
end
|
38
|
+
|
39
|
+
def download_catalog(output_dir = nil)
|
40
|
+
output_dir ||= Dir.pwd
|
41
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
42
|
+
|
43
|
+
catalog_gz_path = File.join(output_dir, "Catalog.xml.gz")
|
44
|
+
catalog_path = File.join(output_dir, "Catalog.xml")
|
45
|
+
|
46
|
+
puts "Downloading Dell catalog from #{CATALOG_URL}..."
|
47
|
+
|
48
|
+
uri = URI.parse(CATALOG_URL)
|
49
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
50
|
+
request = Net::HTTP::Get.new(uri)
|
51
|
+
http.request(request) do |response|
|
52
|
+
if response.code == "200"
|
53
|
+
File.open(catalog_gz_path, 'wb') do |file|
|
54
|
+
response.read_body do |chunk|
|
55
|
+
file.write(chunk)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
else
|
59
|
+
raise Error, "Failed to download catalog: #{response.code} #{response.message}"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
puts "Extracting catalog..."
|
65
|
+
system("gunzip -f #{catalog_gz_path}")
|
66
|
+
|
67
|
+
if File.exist?(catalog_path)
|
68
|
+
puts "Catalog downloaded and extracted to #{catalog_path}"
|
69
|
+
return catalog_path
|
70
|
+
else
|
71
|
+
raise Error, "Failed to extract catalog"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_system_inventory
|
76
|
+
puts "Retrieving system inventory..."
|
77
|
+
|
78
|
+
# Get basic system information
|
79
|
+
system_uri = URI.parse("#{client.base_url}/redfish/v1/Systems/System.Embedded.1")
|
80
|
+
system_response = client.authenticated_request(:get, "/redfish/v1/Systems/System.Embedded.1")
|
81
|
+
|
82
|
+
if system_response.status != 200
|
83
|
+
raise Error, "Failed to get system information: #{system_response.status}"
|
84
|
+
end
|
85
|
+
|
86
|
+
system_data = JSON.parse(system_response.body)
|
87
|
+
|
88
|
+
# Get firmware inventory
|
89
|
+
firmware_uri = URI.parse("#{client.base_url}/redfish/v1/UpdateService/FirmwareInventory")
|
90
|
+
firmware_response = client.authenticated_request(:get, "/redfish/v1/UpdateService/FirmwareInventory")
|
91
|
+
|
92
|
+
if firmware_response.status != 200
|
93
|
+
raise Error, "Failed to get firmware inventory: #{firmware_response.status}"
|
94
|
+
end
|
95
|
+
|
96
|
+
firmware_data = JSON.parse(firmware_response.body)
|
97
|
+
|
98
|
+
# Get detailed firmware information for each component
|
99
|
+
firmware_inventory = []
|
100
|
+
|
101
|
+
if firmware_data['Members'] && firmware_data['Members'].is_a?(Array)
|
102
|
+
firmware_data['Members'].each do |member|
|
103
|
+
if member['@odata.id']
|
104
|
+
component_uri = member['@odata.id']
|
105
|
+
component_response = client.authenticated_request(:get, component_uri)
|
106
|
+
|
107
|
+
if component_response.status == 200
|
108
|
+
component_data = JSON.parse(component_response.body)
|
109
|
+
firmware_inventory << {
|
110
|
+
name: component_data['Name'],
|
111
|
+
id: component_data['Id'],
|
112
|
+
version: component_data['Version'],
|
113
|
+
updateable: component_data['Updateable'] || false,
|
114
|
+
status: component_data['Status'] ? component_data['Status']['State'] : 'Unknown'
|
115
|
+
}
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
{
|
122
|
+
system: {
|
123
|
+
model: system_data['Model'],
|
124
|
+
manufacturer: system_data['Manufacturer'],
|
125
|
+
serial_number: system_data['SerialNumber'],
|
126
|
+
part_number: system_data['PartNumber'],
|
127
|
+
bios_version: system_data['BiosVersion'],
|
128
|
+
service_tag: system_data['SKU']
|
129
|
+
},
|
130
|
+
firmware: firmware_inventory
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
def check_updates(catalog_path = nil)
|
135
|
+
# Download catalog if not provided
|
136
|
+
catalog_path ||= download_catalog
|
137
|
+
|
138
|
+
# Get system inventory
|
139
|
+
inventory = get_system_inventory
|
140
|
+
|
141
|
+
# Parse catalog
|
142
|
+
catalog_doc = File.open(catalog_path) { |f| Nokogiri::XML(f) }
|
143
|
+
|
144
|
+
# Extract service tag
|
145
|
+
service_tag = inventory[:system][:service_tag]
|
146
|
+
|
147
|
+
puts "Checking updates for system with service tag: #{service_tag}"
|
148
|
+
|
149
|
+
# Find applicable updates
|
150
|
+
updates = []
|
151
|
+
|
152
|
+
# Get current firmware versions
|
153
|
+
current_versions = {}
|
154
|
+
inventory[:firmware].each do |fw|
|
155
|
+
current_versions[fw[:name]] = fw[:version]
|
156
|
+
end
|
157
|
+
|
158
|
+
# Find matching components in catalog
|
159
|
+
catalog_doc.xpath('//SoftwareComponent').each do |component|
|
160
|
+
name = component.at_xpath('Name')&.text
|
161
|
+
version = component.at_xpath('Version')&.text
|
162
|
+
path = component.at_xpath('Path')&.text
|
163
|
+
component_type = component.at_xpath('ComponentType')&.text
|
164
|
+
|
165
|
+
# Check if this component matches any of our firmware
|
166
|
+
inventory[:firmware].each do |fw|
|
167
|
+
if fw[:name].include?(name) || name.include?(fw[:name])
|
168
|
+
current_version = fw[:version]
|
169
|
+
|
170
|
+
# Simple version comparison (this could be improved)
|
171
|
+
if version != current_version
|
172
|
+
updates << {
|
173
|
+
name: name,
|
174
|
+
current_version: current_version,
|
175
|
+
available_version: version,
|
176
|
+
path: path,
|
177
|
+
component_type: component_type,
|
178
|
+
download_url: "https://downloads.dell.com/#{path}"
|
179
|
+
}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
updates
|
186
|
+
end
|
187
|
+
|
188
|
+
def interactive_update(catalog_path = nil)
|
189
|
+
updates = check_updates(catalog_path)
|
190
|
+
|
191
|
+
if updates.empty?
|
192
|
+
puts "No updates available for your system."
|
193
|
+
return
|
194
|
+
end
|
195
|
+
|
196
|
+
puts "\nAvailable updates:"
|
197
|
+
updates.each_with_index do |update, index|
|
198
|
+
puts "#{index + 1}. #{update[:name]}: #{update[:current_version]} -> #{update[:available_version]}"
|
199
|
+
end
|
200
|
+
|
201
|
+
puts "\nEnter the number of the update to install (or 'all' for all updates, 'q' to quit):"
|
202
|
+
choice = STDIN.gets.chomp
|
203
|
+
|
204
|
+
return if choice.downcase == 'q'
|
205
|
+
|
206
|
+
selected_updates = if choice.downcase == 'all'
|
207
|
+
updates
|
208
|
+
else
|
209
|
+
index = choice.to_i - 1
|
210
|
+
if index >= 0 && index < updates.size
|
211
|
+
[updates[index]]
|
212
|
+
else
|
213
|
+
puts "Invalid selection."
|
214
|
+
return
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
selected_updates.each do |update|
|
219
|
+
puts "Downloading #{update[:name]} version #{update[:available_version]}..."
|
220
|
+
|
221
|
+
# Create temp directory
|
222
|
+
temp_dir = Dir.mktmpdir
|
223
|
+
|
224
|
+
begin
|
225
|
+
# Download the update
|
226
|
+
update_filename = File.basename(update[:path])
|
227
|
+
update_path = File.join(temp_dir, update_filename)
|
228
|
+
|
229
|
+
uri = URI.parse(update[:download_url])
|
230
|
+
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
|
231
|
+
request = Net::HTTP::Get.new(uri)
|
232
|
+
http.request(request) do |response|
|
233
|
+
if response.code == "200"
|
234
|
+
File.open(update_path, 'wb') do |file|
|
235
|
+
response.read_body do |chunk|
|
236
|
+
file.write(chunk)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
else
|
240
|
+
puts "Failed to download update: #{response.code} #{response.message}"
|
241
|
+
next
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
puts "Installing #{update[:name]} version #{update[:available_version]}..."
|
247
|
+
job_id = update(update_path, wait: true)
|
248
|
+
puts "Update completed with job ID: #{job_id}"
|
249
|
+
|
250
|
+
ensure
|
251
|
+
# Clean up temp directory
|
252
|
+
FileUtils.remove_entry(temp_dir)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
|
259
|
+
def upload_firmware(firmware_path)
|
260
|
+
puts "Uploading firmware file: #{firmware_path}"
|
261
|
+
|
262
|
+
# Get the HttpPushUri from UpdateService
|
263
|
+
update_service_response = client.authenticated_request(:get, "/redfish/v1/UpdateService")
|
264
|
+
update_service_data = JSON.parse(update_service_response.body)
|
265
|
+
|
266
|
+
http_push_uri = update_service_data['HttpPushUri']
|
267
|
+
if http_push_uri.nil?
|
268
|
+
http_push_uri = "/redfish/v1/UpdateService/FirmwareInventory"
|
269
|
+
puts "HttpPushUri not found, using default: #{http_push_uri}"
|
270
|
+
else
|
271
|
+
puts "Found HttpPushUri: #{http_push_uri}"
|
272
|
+
end
|
273
|
+
|
274
|
+
# Get the ETag for the HttpPushUri
|
275
|
+
etag_response = client.authenticated_request(:get, http_push_uri)
|
276
|
+
etag = etag_response.headers['etag']
|
277
|
+
|
278
|
+
puts "Got ETag: #{etag}"
|
279
|
+
|
280
|
+
# Create a boundary for multipart/form-data
|
281
|
+
boundary = "----WebKitFormBoundary#{SecureRandom.hex(16)}"
|
282
|
+
|
283
|
+
# Read the file content
|
284
|
+
file_content = File.binread(firmware_path)
|
285
|
+
filename = File.basename(firmware_path)
|
286
|
+
|
287
|
+
# Create the multipart body
|
288
|
+
post_body = []
|
289
|
+
post_body << "--#{boundary}\r\n"
|
290
|
+
post_body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n"
|
291
|
+
post_body << "Content-Type: application/octet-stream\r\n\r\n"
|
292
|
+
post_body << file_content
|
293
|
+
post_body << "\r\n--#{boundary}--\r\n"
|
294
|
+
|
295
|
+
# Upload the firmware
|
296
|
+
response = client.authenticated_request(
|
297
|
+
:post,
|
298
|
+
http_push_uri,
|
299
|
+
{
|
300
|
+
headers: {
|
301
|
+
'Content-Type' => "multipart/form-data; boundary=#{boundary}",
|
302
|
+
'If-Match' => etag
|
303
|
+
},
|
304
|
+
body: post_body.join
|
305
|
+
}
|
306
|
+
)
|
307
|
+
|
308
|
+
if response.status < 200 || response.status >= 300
|
309
|
+
raise Error, "Firmware upload failed with status #{response.status}: #{response.body}"
|
310
|
+
end
|
311
|
+
|
312
|
+
# Extract job ID from response
|
313
|
+
response_data = JSON.parse(response.body)
|
314
|
+
job_id = response_data['Id'] || response_data['TaskId']
|
315
|
+
|
316
|
+
if job_id.nil?
|
317
|
+
raise Error, "Failed to extract job ID from firmware upload response"
|
318
|
+
end
|
319
|
+
|
320
|
+
puts "Firmware update job created with ID: #{job_id}"
|
321
|
+
job_id
|
322
|
+
end
|
323
|
+
|
324
|
+
def wait_for_job_completion(job_id, timeout)
|
325
|
+
puts "Waiting for firmware update job #{job_id} to complete..."
|
326
|
+
|
327
|
+
start_time = Time.now
|
328
|
+
loop do
|
329
|
+
status = get_job_status(job_id)
|
330
|
+
|
331
|
+
case status
|
332
|
+
when 'Completed'
|
333
|
+
puts "Firmware update completed successfully"
|
334
|
+
return true
|
335
|
+
when 'Failed'
|
336
|
+
raise Error, "Firmware update job failed"
|
337
|
+
when 'Scheduled', 'Running', 'Downloading', 'Pending'
|
338
|
+
# Job still in progress
|
339
|
+
else
|
340
|
+
puts "Unknown job status: #{status}"
|
341
|
+
end
|
342
|
+
|
343
|
+
if Time.now - start_time > timeout
|
344
|
+
raise Error, "Firmware update timed out after #{timeout} seconds"
|
345
|
+
end
|
346
|
+
|
347
|
+
# Wait before checking again
|
348
|
+
sleep 10
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def get_job_status(job_id)
|
353
|
+
response = client.authenticated_request(
|
354
|
+
:get,
|
355
|
+
"/redfish/v1/TaskService/Tasks/#{job_id}"
|
356
|
+
)
|
357
|
+
|
358
|
+
if response.status != 200
|
359
|
+
raise Error, "Failed to get job status with status #{response.status}: #{response.body}"
|
360
|
+
end
|
361
|
+
|
362
|
+
response_data = JSON.parse(response.body)
|
363
|
+
response_data['TaskState'] || 'Unknown'
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
data/lib/idrac/screenshot.rb
CHANGED
@@ -1,24 +1,49 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
10
26
|
timestamp_ms = (Time.now.to_f * 1000).to_i
|
27
|
+
|
28
|
+
# First request to trigger the screenshot capture
|
11
29
|
path = "data?get=consolepreview[manual%20#{timestamp_ms}]"
|
12
|
-
res = get(path: path, headers: tokens)
|
13
|
-
raise "
|
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
|
14
34
|
sleep 2
|
35
|
+
|
36
|
+
# Second request to get the actual screenshot image
|
15
37
|
path = "capconsole/scapture0.png?#{timestamp_ms}"
|
16
|
-
res = get(path: path, headers: tokens)
|
17
|
-
raise "
|
18
|
-
|
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"
|
19
43
|
File.open(filename, "wb") { |f| f.write(res.body) }
|
20
|
-
|
44
|
+
|
45
|
+
# Return the filename
|
21
46
|
filename
|
22
47
|
end
|
23
48
|
end
|
24
|
-
end
|
49
|
+
end
|
data/lib/idrac/version.rb
CHANGED
data/lib/idrac.rb
CHANGED
@@ -2,17 +2,29 @@
|
|
2
2
|
|
3
3
|
require 'httparty'
|
4
4
|
require 'nokogiri'
|
5
|
+
require 'faraday'
|
6
|
+
require 'faraday/multipart'
|
7
|
+
require 'base64'
|
8
|
+
require 'uri'
|
5
9
|
# If dev, required debug
|
6
10
|
require 'debug' if ENV['RUBY_ENV'] == 'development'
|
7
11
|
|
8
12
|
require_relative "idrac/version"
|
9
13
|
require_relative "idrac/client"
|
10
14
|
require_relative "idrac/screenshot"
|
15
|
+
require_relative "idrac/firmware"
|
11
16
|
|
12
|
-
module
|
17
|
+
module IDRAC
|
13
18
|
class Error < StandardError; end
|
14
|
-
|
15
|
-
def self.new(
|
16
|
-
Client.new(
|
19
|
+
|
20
|
+
def self.new(host:, username:, password:, port: 443, use_ssl: true, verify_ssl: true)
|
21
|
+
Client.new(
|
22
|
+
host: host,
|
23
|
+
username: username,
|
24
|
+
password: password,
|
25
|
+
port: port,
|
26
|
+
use_ssl: use_ssl,
|
27
|
+
verify_ssl: verify_ssl
|
28
|
+
)
|
17
29
|
end
|
18
30
|
end
|
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.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jonathan Siegel
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: httparty
|
@@ -16,101 +16,206 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 0.21.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.22.0
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
29
|
+
version: 0.21.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.22.0
|
27
33
|
- !ruby/object:Gem::Dependency
|
28
34
|
name: nokogiri
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
30
36
|
requirements:
|
31
37
|
- - ">="
|
32
38
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
39
|
+
version: 1.15.0
|
40
|
+
- - "<"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 1.19.0
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 1.15.0
|
50
|
+
- - "<"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 1.19.0
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: faraday
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 2.7.0
|
60
|
+
- - "<"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 2.8.0
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.7.0
|
70
|
+
- - "<"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 2.8.0
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: faraday-multipart
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: 1.0.0
|
80
|
+
- - "<"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 1.1.0
|
83
|
+
type: :runtime
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.0.0
|
90
|
+
- - "<"
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 1.1.0
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: thor
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: 1.2.0
|
100
|
+
- - "<"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.4.0
|
103
|
+
type: :runtime
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.2.0
|
110
|
+
- - "<"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 1.4.0
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: base64
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0.1'
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: 0.1.0
|
34
123
|
type: :runtime
|
35
124
|
prerelease: false
|
36
125
|
version_requirements: !ruby/object:Gem::Requirement
|
37
126
|
requirements:
|
127
|
+
- - "~>"
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0.1'
|
38
130
|
- - ">="
|
39
131
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
132
|
+
version: 0.1.0
|
41
133
|
- !ruby/object:Gem::Dependency
|
42
134
|
name: bundler
|
43
135
|
requirement: !ruby/object:Gem::Requirement
|
44
136
|
requirements:
|
137
|
+
- - "~>"
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: '2.4'
|
45
140
|
- - ">="
|
46
141
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
142
|
+
version: 2.4.0
|
48
143
|
type: :development
|
49
144
|
prerelease: false
|
50
145
|
version_requirements: !ruby/object:Gem::Requirement
|
51
146
|
requirements:
|
147
|
+
- - "~>"
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: '2.4'
|
52
150
|
- - ">="
|
53
151
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
152
|
+
version: 2.4.0
|
55
153
|
- !ruby/object:Gem::Dependency
|
56
154
|
name: rake
|
57
155
|
requirement: !ruby/object:Gem::Requirement
|
58
156
|
requirements:
|
59
|
-
- - "
|
157
|
+
- - "~>"
|
60
158
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
159
|
+
version: '13.0'
|
62
160
|
type: :development
|
63
161
|
prerelease: false
|
64
162
|
version_requirements: !ruby/object:Gem::Requirement
|
65
163
|
requirements:
|
66
|
-
- - "
|
164
|
+
- - "~>"
|
67
165
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
166
|
+
version: '13.0'
|
69
167
|
- !ruby/object:Gem::Dependency
|
70
168
|
name: rspec
|
71
169
|
requirement: !ruby/object:Gem::Requirement
|
72
170
|
requirements:
|
73
|
-
- - "
|
171
|
+
- - "~>"
|
74
172
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
173
|
+
version: '3.12'
|
76
174
|
type: :development
|
77
175
|
prerelease: false
|
78
176
|
version_requirements: !ruby/object:Gem::Requirement
|
79
177
|
requirements:
|
80
|
-
- - "
|
178
|
+
- - "~>"
|
81
179
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
180
|
+
version: '3.12'
|
83
181
|
- !ruby/object:Gem::Dependency
|
84
182
|
name: debug
|
85
183
|
requirement: !ruby/object:Gem::Requirement
|
86
184
|
requirements:
|
87
|
-
- - "
|
185
|
+
- - "~>"
|
88
186
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
187
|
+
version: '1.8'
|
90
188
|
type: :development
|
91
189
|
prerelease: false
|
92
190
|
version_requirements: !ruby/object:Gem::Requirement
|
93
191
|
requirements:
|
94
|
-
- - "
|
192
|
+
- - "~>"
|
95
193
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
194
|
+
version: '1.8'
|
97
195
|
description: A Ruby client for the Dell iDRAC API
|
98
196
|
email:
|
99
197
|
- "<248302+usiegj00@users.noreply.github.com>"
|
100
|
-
executables:
|
198
|
+
executables:
|
199
|
+
- idrac
|
101
200
|
extensions: []
|
102
201
|
extra_rdoc_files: []
|
103
202
|
files:
|
104
203
|
- ".rspec"
|
105
204
|
- README.md
|
106
205
|
- Rakefile
|
206
|
+
- bin/console
|
207
|
+
- bin/idrac
|
208
|
+
- bin/setup
|
209
|
+
- idrac.gemspec
|
107
210
|
- lib/idrac.rb
|
108
211
|
- lib/idrac/client.rb
|
212
|
+
- lib/idrac/firmware.rb
|
109
213
|
- lib/idrac/screenshot.rb
|
110
214
|
- lib/idrac/version.rb
|
111
215
|
- sig/idrac.rbs
|
112
216
|
homepage: http://github.com
|
113
|
-
licenses:
|
217
|
+
licenses:
|
218
|
+
- MIT
|
114
219
|
metadata:
|
115
220
|
homepage_uri: http://github.com
|
116
221
|
post_install_message:
|
@@ -121,14 +226,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
121
226
|
requirements:
|
122
227
|
- - ">="
|
123
228
|
- !ruby/object:Gem::Version
|
124
|
-
version: 3.
|
229
|
+
version: 3.2.0
|
125
230
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
231
|
requirements:
|
127
232
|
- - ">="
|
128
233
|
- !ruby/object:Gem::Version
|
129
234
|
version: '0'
|
130
235
|
requirements: []
|
131
|
-
rubygems_version: 3.
|
236
|
+
rubygems_version: 3.4.19
|
132
237
|
signing_key:
|
133
238
|
specification_version: 4
|
134
239
|
summary: API Client for Dell iDRAC
|