Created
October 30, 2020 10:10
-
-
Save jullle3/206a47b4a7b638027a848d3161145cd0 to your computer and use it in GitHub Desktop.
changes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'puppet_x', 'racadm', 'racadm.rb')) | |
Puppet::Type.type(:dell_bios).provide(:racadm7) do | |
desc 'Manage DELL bios via racadm7.' | |
mk_resource_methods | |
confine osfamily: [:redhat, :debian] | |
confine exists: '/opt/dell/srvadmin/bin/idracadm7' | |
# Used to check for dependencies in values=() | |
$bios_attributes = {} | |
def self.prefetch(resources) | |
# This is needed because DELL api is a bit inconsistent in it's password annotation. | |
# | |
# ie | |
# /opt/dell/srvadmin/bin/idracadm7 get BIOS.SysSecurity | |
# SetupPassword=******** (Write-Only) | |
# SysPassword=******** (Write-Only) | |
# | |
# /opt/dell/srvadmin/bin/idracadm7 get iDRAC.Users.2 | |
# !!Password=******** (Write-Only) | |
# | |
real_password_fields = { 'SysSecurity' => ['SetupPassword', 'SysPassword'] } | |
resources.each do |name, type| | |
# warning original_parameters is part of the private API | |
groups = type.original_parameters[:values] | |
current_values = {} | |
groups.each do |groupname, group_details| | |
racadm_out = Racadm.racadm_call( | |
{ | |
bmc_username: type.value(:bmc_username), | |
bmc_password: type.value(:bmc_password), | |
bmc_server_host: type.value(:bmc_server_host), | |
}, | |
['get', "BIOS.#{groupname}"], | |
true, | |
) | |
settings = Racadm.parse_racadm racadm_out | |
$bios_attributes["BIOS.#{groupname}"] = settings | |
# Also include read-only keys appended with "#" as we also wish to compare equality with those | |
relevant_settings = settings.select { |k, _v| group_details.key? k.to_s.tr("#", "")} | |
real_password_fields_group = real_password_fields[groupname] | |
# Checks if password has changed. | |
unless real_password_fields_group.nil? | |
relevant_settings.each do |k, v| | |
relevant_settings[k] = if real_password_fields_group.include? k | |
if Racadm.password_changed?( | |
group_details[k], | |
settings["SHA256#{k}"], | |
settings["SHA256#{k}Salt"], | |
) | |
# to ensure that puppet notice the password is changed | |
group_details[k] + '_' | |
else | |
group_details[k] | |
end | |
else | |
v | |
end | |
end | |
end | |
current_values[groupname] = relevant_settings | |
end | |
provider = new( | |
name: name, | |
values: current_values, | |
) | |
resources[name].provider = provider | |
end | |
end | |
# Setter | |
def values=(value) | |
# Because each change is a isolated call that can be very expensive we only call if value has changed. | |
# Possible performance gain is to write all changes to a ".ini" file, and modify multiple objects using said file. | |
# see page 69 for more details. https://topics-cdn.dell.com/pdf/idrac7-8-lifecycle-controller-v2.50.50.50_reference-guide_en-us.pdf | |
all_dependencies = { | |
# read as: "Tpm2Hierarchy" has a dependency on "SysSecurity.TpmSecurity" having the value of "On" etc | |
Tpm2Hierarchy: { | |
"SysSecurity.TpmSecurity": "On", | |
}, | |
BootMode: { | |
"SysSecurity.SecureBoot": "Disabled" | |
} | |
} | |
# values() can be called without there being any changes. This is due to the fact that some attributes when retrieved | |
# via racadm7 are appended with "#", thus invalidating puppets comparison algorithm. | |
# This variable seeks to remove unessecary reboots when there are applied no changes. | |
applied_changes = false | |
value.each do |groupname, group_details| | |
group_details.each do |detail, detail_value| | |
# Look for two entries, with and without "#". | |
# This check ensures we dont apply changes to attributes who already has the correct value | |
next if [@property_hash[:values][groupname][detail], @property_hash[:values][groupname]["#" + detail]].include? detail_value | |
# If the key does not have the correct value AND it exists with "#" appended, it is read-only and thus some dependencies are not met! | |
# Tries to fix dependencies, if this fails we explain further details in a warning, and continue execution | |
if $bios_attributes["BIOS." + groupname].key? "#" + detail | |
if all_dependencies.key? detail.to_sym | |
all_dependencies[detail.to_sym].map do |dependency_name, dependency_value| | |
Racadm.racadm_call( | |
@resource, | |
["set", "BIOS.#{dependency_name}", dependency_value], | |
) | |
end | |
else | |
warning("'#{detail}' is read-only and no dependencies are known for this attribute! Thus changes won't be | |
saved. Hidden dependencies are most likely the cause of this issue") | |
end | |
end | |
# Set the new attribute value | |
Racadm.racadm_call( | |
@resource, | |
['set', "BIOS.#{groupname}.#{detail}", detail_value], | |
) | |
applied_changes = true | |
#TODO: consider a try/catch and handle reply | |
end | |
end | |
create_job_and_restart if applied_changes | |
end | |
def create_job_and_restart | |
wait_seconds = 60 | |
racadm_out = Racadm.racadm_call( | |
@resource, | |
['getractime', '-d'], | |
) | |
rac_execute_at = Time.new( | |
racadm_out[0, 4], # year | |
racadm_out[4, 2], # month | |
racadm_out[6, 2], # day | |
racadm_out[8, 2], # hour | |
racadm_out[10, 2], # minute | |
racadm_out[12, 2], # second | |
) + wait_seconds | |
Racadm.racadm_call( | |
@resource, | |
[ | |
'jobqueue', | |
'create', | |
'BIOS.Setup.1-1', | |
'-s', rac_execute_at.strftime('%Y%m%d%H%M%S'), | |
'-e', (rac_execute_at + 600).strftime('%Y%m%d%H%M%S'), | |
'-r', 'forced' | |
], | |
) | |
# TODO: parse output | |
notice("Will reboot machine and configure BIOS in #{wait_seconds} sec") | |
end | |
end | |
require 'puppet/provider' | |
require 'open3' | |
require 'digest' | |
# Racadm specific Utilily class | |
class Racadm | |
def self.racadm_call(racadm_args, cmd_args, suppress_error = false) | |
cmd = ['/opt/dell/srvadmin/bin/idracadm7'] | |
unless racadm_args[:bmc_server_host].nil? || | |
racadm_args[:bmc_username].nil? || | |
racadm_args[:bmc_password].nil? | |
cmd.push('-u').push(racadm_args[:bmc_username]) if racadm_args[:bmc_username] | |
cmd.push('-p').push(racadm_args[:bmc_password]) if racadm_args[:bmc_password] | |
cmd.push('-r').push(racadm_args[:bmc_server_host]) if racadm_args[:bmc_server_host] | |
end | |
cmd += cmd_args | |
stdout, stderr, status = Open3.capture3(cmd.join(' ')) | |
nr = cmd.index('-p') | |
cmd.fill('<secret>', nr + 1, 1) unless nr.nil? # password is not logged. | |
Puppet.debug("#{cmd.join(' ')} executed with stdout: '#{stdout}' stderr: '#{stderr}' status: '#{status}'") | |
if !status.success? && !suppress_error | |
raise(Puppet::Error, "#{cmd.join(' ')} failed with #{stdout}") | |
end | |
stdout | |
end | |
def self.parse_racadm(reply) | |
parsed = {} | |
reply.each_line do |line| | |
sub_line_array = line.split('=') | |
if sub_line_array.length > 1 | |
if line.start_with? '[Key=' | |
parsed[:Key] = sub_line_array[1].strip[0..-2] | |
elsif sub_line_array[0].end_with? '[Key' | |
subkey = sub_line_array.slice!(0).strip.chomp(' [Key') | |
subvalue = '[Key=' + sub_line_array.join('=').strip | |
parsed[subkey] = subvalue | |
elsif !sub_line_array[0].start_with? '!!' | |
subkey = sub_line_array.slice!(0).strip.gsub(%r{\s+}, '') | |
subvalue = sub_line_array.join('=').strip | |
parsed[subkey] = subvalue | |
end | |
end | |
end | |
parsed | |
end | |
def self.password_changed?(new_password, sha256, salt) | |
new_password_sha = Digest::SHA256.hexdigest( | |
#new_password + salt.gsub(%r{..}) { |pair| pair.hex.chr }, | |
new_password + salt, | |
).upcase | |
!new_password_sha.eql? sha256 | |
end | |
def self.parse_jobqueue(reply) | |
parsed = {} | |
reply.each_line do |line| | |
if line.start_with? 'Reboot JID' | |
parsed['reboot_id'] = line.split(' ').last | |
elsif line.start_with? 'Commit JID' | |
parsed['commit_id'] = line.split(' ').last | |
end | |
end | |
parsed | |
end | |
def self.bool_to_s(value) | |
(value.to_s == 'true') ? 'Enabled' : 'Disabled' | |
end | |
def self.s_to_bool(value) | |
'Enabled'.eql? value | |
end | |
def self.s_to_role(value) | |
case value | |
when '1' | |
'callback' | |
when '2' | |
'user' | |
when '3' | |
'operator' | |
when '4' | |
'administrator' | |
when '5' | |
'oem_proprietary' | |
when '15' | |
'no_access' | |
end | |
end | |
def self.role_to_s(value) | |
case value | |
when 'none' | |
'0' | |
when 'callback' | |
'1' | |
when 'user' | |
'2' | |
when 'operator' | |
'3' | |
when 'administrator' | |
'4' | |
when 'oem_proprietary' | |
'5' | |
when 'no_access' | |
'15' | |
end | |
end | |
end | |
# TODO: Tjek på kontoret | |
# 1. Virker password tjekket? Det må kun "sette" nyt password hvis passworded har ændret sig | |
# Verificer at password_changed? virker korrekt. Er bange for den benyttet HASHED SALT? | |
# 2. Næste skridt er at oprette puppet resources. Skal jeg eller en anden gøre det? | |
# | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment