require 'json'
require 'colorize'
module IDRAC
module Lifecycle
# This follows from these Scripts "GetIdracLcSystemAttributesREDFISH.py" and "SetIdracLcSystemAttributesREDFISH.py"
# They can do more than just the lifecycle, but that's what we need right now.
# True or False if it's enabled or not
def get_lifecycle_status
# Check iDRAC version first to determine the right approach
idrac_version = get_idrac_version rescue 0
debug "Detected iDRAC version: #{idrac_version}", 1
# Use version-specific methods
if idrac_version > 9
debug "Using modern approach for iDRAC > 9", 1
return get_lifecycle_status_modern_firmware
elsif idrac_version == 9
debug "Using registry approach for iDRAC 9", 1
return get_lifecycle_status_from_registry
else
debug "Using SCP approach for older iDRAC (v#{idrac_version})", 1
return get_lifecycle_status_from_scp
end
end
# Get lifecycle status from SCP export (for older iDRAC firmware)
def get_lifecycle_status_from_scp
debug "Exporting System Configuration Profile to check LifecycleController state...", 1
begin
# Use the SCP export to get LifecycleController state
scp = get_system_configuration_profile(target: "LifecycleController")
# Check if we have data in the expected format
if scp && scp["SystemConfiguration"] && scp["SystemConfiguration"]["Components"]
# Find the LifecycleController component
lc_component = scp["SystemConfiguration"]["Components"].find do |component|
component["FQDD"] == "LifecycleController.Embedded.1"
end
if lc_component && lc_component["Attributes"]
# Find the LifecycleControllerState attribute
lc_state_attr = lc_component["Attributes"].find do |attr|
attr["Name"] == "LCAttributes.1#LifecycleControllerState"
end
if lc_state_attr
debug "Found LifecycleController state from SCP: #{lc_state_attr["Value"]}", 1
return lc_state_attr["Value"] == "Enabled"
end
end
end
debug "Could not find LifecycleController state in SCP export", 1, :yellow
return false
rescue => e
debug "Error getting Lifecycle Controller status from SCP: #{e.message}", 1, :red
debug e.backtrace.join("\n"), 3, :red
return false
end
end
# Get lifecycle status from registry (for iDRAC 9)
def get_lifecycle_status_from_registry
# This big JSON explains all the attributes:
path = "/redfish/v1/Registries/ManagerAttributeRegistry/ManagerAttributeRegistry.v1_0_0.json"
response = authenticated_request(:get, path)
if response.status != 200
debug "Failed to get any Lifecycle Controller Attributes".red, 1
return false
end
attributes = JSON.parse(response.body)
# This is the attribute we want:
target = attributes&.dig('RegistryEntries', 'Attributes')&.find {|q| q['AttributeName'] =~ /LCAttributes.1.LifecycleControllerState/ }
# This is the FQDN of the attribute we want to get the value of:
fqdn = target.dig('Id') # LifecycleController.Embedded.1#LCAttributes.1#LifecycleControllerState
# This is the Current Value:
response = authenticated_request(:get, "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellAttributes/#{fqdn}")
if response.status != 200
debug "Failed to get Lifecycle Controller Attributes".red, 1
return false
end
attributes = JSON.parse(response.body)
# There is a ValueName and a Value Display Name (e.g. Enabled, Disabled, Recovery)
display = attributes&.dig('Attributes','LCAttributes.1.LifecycleControllerState')
value = target&.dig('Value')&.find { |v| v['ValueDisplayName'] == display }&.dig('ValueName')&.to_i
value == 1
end
# Check if the Lifecycle Controller is enabled
def get_lifecycle_status_modern_firmware
# Try the standard Attributes endpoint first
path = "/redfish/v1/Managers/iDRAC.Embedded.1/Attributes"
response = authenticated_request(:get, path)
if response.status == 200
begin
attributes_data = JSON.parse(response.body)
if attributes_data["Attributes"] && attributes_data["Attributes"]["LCAttributes.1.LifecycleControllerState"]
lifecycle_state = attributes_data["Attributes"]["LCAttributes.1.LifecycleControllerState"]
debug "Lifecycle Controller state (from Attributes): #{lifecycle_state}".light_cyan, 1
return lifecycle_state == "Enabled"
end
rescue JSON::ParserError
debug "Failed to parse Attributes response".yellow, 1
# Fall through to registry method if parsing fails or attribute not found
end
else
debug "Failed to get Attributes endpoint (Status: #{response.status}), trying registry method...".yellow, 1
end
# Try getting the DellAttributes for LifecycleController directly
# The key insight is that we need to use just the base path without the fragment
attributes_path = "/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellAttributes/LifecycleController.Embedded.1"
attributes_response = authenticated_request(:get, attributes_path)
if attributes_response.status == 200
begin
dell_attr_data = JSON.parse(attributes_response.body)
if dell_attr_data["Attributes"] && dell_attr_data["Attributes"]["LCAttributes.1.LifecycleControllerState"]
lifecycle_state = dell_attr_data["Attributes"]["LCAttributes.1.LifecycleControllerState"]
debug "Lifecycle Controller state (from Dell Attributes): #{lifecycle_state}".light_cyan, 1
return lifecycle_state == "Enabled"
end
rescue JSON::ParserError
debug "Failed to parse Dell Attributes response".yellow, 1
# Fall through to registry method if parsing fails or attribute not found
end
else
debug "Failed to get Dell Attributes (Status: #{attributes_response.status}), trying registry method...".yellow, 1
end
# Fallback to the registry method if both Attributes endpoints fail
registry_response = authenticated_request(
:get,
"/redfish/v1/Registries/ManagerAttributeRegistry/ManagerAttributeRegistry.v1_0_0.json"
)
if registry_response.status != 200
debug "Failed to get Lifecycle Controller Attributes Registry", 0, :red
return false
end
begin
registry_data = JSON.parse(registry_response.body)
# This is the attribute we want:
target = registry_data['RegistryEntries']['Attributes'].find {|q| q['AttributeName'] =~ /LCAttributes.1.LifecycleControllerState/ }
if !target
debug "Could not find LCAttributes.1.LifecycleControllerState in registry", 0, :red
return false
end
debug "Found attribute in registry but couldn't access it via other endpoints".yellow, 1
return false
rescue JSON::ParserError, NoMethodError, StandardError => e
debug "Error during registry access: #{e.message}", 0, :red
return false
end
end
# Set the Lifecycle Controller status (enable/disable)
def set_lifecycle_status(status)
payload = { "Attributes": { "LCAttributes.1.LifecycleControllerState": status ? 'Enabled' : 'Disabled' } }
response = authenticated_request(
:patch,
"/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellAttributes/LifecycleController.Embedded.1",
body: payload.to_json,
headers: { 'Content-Type': 'application/json' }
)
code = response.status
case code
when 200..299
debug "Lifecycle Controller is now #{status ? 'Enabled' : 'Disabled'}".green, 1
when 400..499
debug "[#{code}] This iDRAC does not support Lifecycle Controller", 0, :red
when 500..599
debug "[#{code}] iDRAC does not support Lifecycle Controller", 0, :red
else
end
end
# Ensure the Lifecycle Controller is enabled
def ensure_lifecycle_controller!
if !get_lifecycle_status
debug "Lifecycle Controller is disabled, enabling...".yellow, 1
set_lifecycle_status(true)
# Verify it was enabled
if !get_lifecycle_status
raise Error, "Failed to enable Lifecycle Controller"
end
debug "Lifecycle Controller successfully enabled".green, 1
else
debug "Lifecycle Controller is already enabled".green, 1
end
return true
end
# Clear the Lifecycle log
def clear_lifecycle!
path = '/redfish/v1/Managers/iDRAC.Embedded.1/Oem/Dell/DellLCService/Actions/DellLCService.SystemErase'
payload = { "Component": ["LCData"] }
response = authenticated_request(
:post,
path,
body: payload.to_json,
headers: { 'Content-Type' => 'application/json' }
)
if response.status.between?(200, 299)
debug "Lifecycle log cleared", 0, :green
return true
else
debug "Failed to clear Lifecycle log", 0, :red
error_message = "Failed to clear Lifecycle log. Status code: #{response.status}"
begin
error_data = JSON.parse(response.body)
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
rescue
# Ignore JSON parsing errors
end
raise Error, error_message
end
end
# Get the system event logs
def get_system_event_logs
path = '/redfish/v1/Managers/iDRAC.Embedded.1/Logs/Sel?$expand=*($levels=1)'
response = authenticated_request(:get, path)
if response.status == 200
begin
data = JSON.parse(response.body)['Members'].map do |entry|
{
id: entry['Id'],
created: entry['Created'],
message: entry['Message'],
severity: entry['Severity']
}
end
return data # RecursiveOpenStruct.new(data, recurse_over_arrays: true)
rescue JSON::ParserError
raise Error, "Failed to parse system event logs response: #{response.body}"
end
else
raise Error, "Failed to get system event logs. Status code: #{response.status}"
end
end
# Clear the system event logs
def clear_system_event_logs!
path = '/redfish/v1/Managers/iDRAC.Embedded.1/LogServices/Sel/Actions/LogService.ClearLog'
response = authenticated_request(:post, path, body: {}.to_json, headers: { 'Content-Type' => 'application/json' })
if response.status.between?(200, 299)
debug "System Event Logs cleared", 0, :green
return true
else
debug "Failed to clear System Event Logs", 0, :red
error_message = "Failed to clear System Event Logs. Status code: #{response.status}"
begin
error_data = JSON.parse(response.body)
error_message += ", Message: #{error_data['error']['message']}" if error_data['error'] && error_data['error']['message']
rescue
# Ignore JSON parsing errors
end
raise Error, error_message
end
end
# Updates the status message for the lifecycle controller
def update_status_message(status)
debug "Lifecycle Controller is now #{status ? 'Enabled' : 'Disabled'}".green, 1
end
end
end