Skip to content

Instantly share code, notes, and snippets.

@jullle3
Created October 30, 2020 10:10
Show Gist options
  • Save jullle3/206a47b4a7b638027a848d3161145cd0 to your computer and use it in GitHub Desktop.
Save jullle3/206a47b4a7b638027a848d3161145cd0 to your computer and use it in GitHub Desktop.
changes
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