Hi.
I’m writing a recipe for chef-zero 12.3.0. I’d like to use attributes stored in data bags, but if the data bag item doesn’t define the attribute, a default defined as an attribute in the cookbook should be used.
What’s the best way to do this? I now have:
ipa_node = data_bag_item("ipa", node['hostname'])
ipaddress = (ipa_node['ipaddress'].empty?) ? (default[:ipa][:server][:ipaddress]) : (ipa_node['ipaddress'])
Or even:
values = {}
default[:ipa][:server].each do |key, default_val|
values[key.to_s] = (ipa_node[key].empty?) ? (default_val) : (ipa_node[key])
end
This seems to be a bit „awkward“, especially if numerous attributes from a data bag item are to be used.
How do I use values from data bags with a fallback to defaults in a „canonical” way?
Thanks a lot,
Alexander
There is no “standard” way to do it but here is how I solved this:
Create a library in some base cookbook with a code like this (I just copy/pasted some code here, so this might not work out of the box but the concepts should be there):
module MySettings
# Loads settings from node attributes and data_bags/vaults.
#
# @param [String] The app to load. App is the key in data_bags/my_settings/<NODE_NAME>.json
def app_settings_for(app)
# Return 'chached' settings from run_state
settings_name = "MY_SETTINGS_FOR_#{app}"
return node.run_state[settings_name] unless node.run_state[settings_name].nil?
# No cached run_state, thus get settings from vault/data_bag
# Create a copy of node attributes to be merged with vault settings
# If node attribute does not exist use empty hash
# In ruby dup should do the same, but we want a real hash and not a node object
settings = node["company-#{app}"].to_hash if node.key? "company-#{app}"
settings ||= {}
node_vault = load_vault_if_exists('bag_name', node.name)
# hash_only_merge!(a, b) will merge b into a _AND_ overwrite a
Chef::Mixin::DeepMerge.hash_only_merge!(settings, node_vault[app]) if node_vault.key? app
# Set run_state 'cache' and return settings
node.run_state[settings_name] = settings
settings
end
def load_vault_if_exists(bag, name)
begin
s = chef_vault_item(bag, name).raw_data if Chef::DataBag.load(bag).include?(name)
rescue ChefVault::Exceptions::KeysNotFound, ChefVault::Exceptions::SecretDecryption
raise "This node does not have access to vault item #{app}"
end
s ||= {} # Return empty hash if item does not exist
# Cache vault data in run_state
node.run_state[vault_settings_name] = s
s
end
end
Chef::Recipe.send(:include, MySettings)
Chef::Resource.send(:include, MySettings)
In my recipes where I use these settings I always have this in one of the first lines:
settings = app_settings_for('jenkins')
template '/watever.json' do
variables(
example: settings['merged']
)
end
This allows us to place the merge logic and general settings usage in one place. If we decide to switch from DataBags to some Database or Hashicorp Vault this could relatively easily be done in the one single library.