Chef ruby code - .rb cookbook - If statement to check config['variable'] exists

I have the following bit of code in my cookbook:

if File.file?('/etc/test.yml')
  config = YAML.load(File.read('/etc/test.yml'))
 else
  config = Hash.new
 end
 config['key'] = node['test']['license']
 config['verbose'] = 0
 config['sample_rate'] = 120
 file '/etc/test.yml' do
  content config.to_yaml
 end

My code works in that the .yml file will be created if it doesn’t already exist and will populate the file with three variables when creating the .yml file. But what I’m attempting to add to this bit of code is to confirm if the .yml file already exists that these three variables also exist in the .yml file. I’ll need to add a check to each config[‘variable’] = line confirm if this variable exists and if the variable is missing to add the variable to the .yml file.

Any advice on the code required to achieve this would be greatly appreciated.

Thank you.

  • To check if the file exists: File.exist?('filename')

  • To check if the file is readable and writable at the same time (say chmod 000 / 111 is unreadable and unwritable): File.readable?('filename') && File.writable?('filename'). Note that there’s also File.executable?('filename')

  • To check the file chmod and also check the permissions:

mode = File.stat('/etc/test.yaml').mode

owner = 7 & mode >> 6
# group = 7 & mode >> 3
# others = 7 & mode

owner_readable = owner & 4 != 0
owner_writable = owner & 2 != 0
owner_executable = owner & 1 != 0

p [
	owner_readable,
	owner_writable,
	owner_executable
]

From these data you can analyze that.

To check if the file already has the line, read the file in an array (beware this is not memory-friendly, and can take gigabyte of memory for a gigabyte file). You need to iterate over each line, use .rstrip() to strip any extra newlines from right, and check the line with ==() method, do that in a find() method with a block.

Take advantage of Hash. It has the method merge with gives you the option to write code when keys collide.

I’m going to skip the frontiers of your example (i.e. the Input/Output with your file). Let’s focus only when you have the data from the file.

That data is in a hash. Merge that with your default values for key, verbose and sample_rate, but use merge for that. That way, you can write arbitrary code for reacting when the keys collide.

I’ll give you an example:

# Setup
node = { 'test' => { 'license' => 'A LICENCE' }}
default_settings = { 'key'         => node.dig('test', 'license'),
                     'verbose'     => 0,
                     'sample_rate' => 120 }

# INPUT
# Somehow you get a Hash (from the file or not, I don't care)

user_settings = { 'key'         => 'ANOTHER LICENCE', 
                  'sample_rate' => 100 , 
                  'user_01'     => 333 }

# Process

what_to_do_when_keys_collide = lambda do |key, oldval, newval|
  # This is your call
  # It could be a simple output
  puts "Overwriting #{key}. From #{oldval} to #{newval}"
  # Remember to return the value you want. Maybe the newval
  newval
end

definitive_settings = default_settings.merge(user_settings, &what_to_do_when_keys_collide)

# definitive_settings will be:
# {"key"=>"ANOTHER LICENCE", "verbose"=>0, "sample_rate"=>100, "user_01"=>333}

# OUTPUT
# Maybe you'll overwrite your file... OR NOT (I would suggest don't overwrite it)

If you run this example as an script, you’ll get the output:

Overwriting key. From LICENCE to ANOTHER LICENCE
Overwriting sample_rate. From 120 to 100
1 Like