Chef - Howto compute attributes from node specific values?

Let say I have a cookbook which configures and installs a magical deamon:

magical-deamon/recipes/default.rb:

template "/etc/magical-deamon/magical.conf" do
    source "magical.conf"
    mode 0644
    notifies :restart, resources(:service => "magical-deamon")
end

magical-deamon/attributes/default.rb:

default['magical-deamon']['memory'] = 1024

magical-deamon/templates/default/magical.conf.erb:

memory = <%= node['magical-deamon']['memory'] %>

As I understood Chef, i would use either the node-attributes to set the memory value like:

{
    "normal": {
        "tags": [],
        "magical-deamon": {
            "memory": 256
        }
    },
    "name": "server.example.com",
    "chef_environment": "production",
    "run_list": [
        "role[base]"
    ]
}

Or through a role:

{
    "name": "base",
    "default_attributes": {
        "magical-deamon": {
                "memory": 756
            }
    },
    "json_class": "Chef::Role",
    "env_run_lists": {
    },
    "run_list": [
    ],
    "description": "base role applied to all nodes",
    "chef_type": "role",
    "override_attributes": {
    },
  }
}

Or an Environment:

{
  "name": "production",
  "default_attributes": {
    "magical-deamon": {
        "memory": 756
    }
  },
  "json_class": "Chef::Environment",
  "description": "",
  "cookbook_versions": {
  },
  "override_attributes": {
  },
  "chef_type": "environment"
}

So far so good...

Now i had the silly idea to set 'memory' to a node specific dynamic value.

Lets say our magical deamon should consume 75% of the total Memory the node possesses.

value = total_memory * 0.75

Coming from a programmer background, i like to leave that knowledge out of the cookbook, because i like my cookbook reusable for other people.

I thought the right place would be somewhere where attributes are being set. But doing such a calculation within json or ruby dsl is not possible.

So my questions are:

  • Is my general approach (value = total_memory * 0.75) a stupid idea?
  • How would you set that kind of attribute? Keep in mind: There will be more than one value, and one node :) And there might be some calculation involved MB -> KB and rounding and so forth. Hardwiring every attribute into the recipe, should not be an option ;)

Solution 1:

I'm not so keen on the total_memory idea it's somewhat of an indirect quantity. Maybe it makes more sense in the context of whatever the magical daemon is though.

For tunable attributes like total memory, I pretty much would do as you have laid out in the question by adding a sensible default value to attributes/default.rb (cuts down on support questions when someone forgets to explicitly set a value) and override with environment, role or node-specific values where necessary.

It's possible to do arithmetic inside the ERB file like this:

memory = <%= (node['memory']['total'][0..-3].to_i / 1024) * 
             node['magical-daemon']['memory'] %>

Ohai makes available the statistics from free(1) which includes the total memory in kB. node['memory']['total'] = '12312432kB' on my workstation.

I also try and use attributes with the lowest priority as much as possible, i.e prefer default over normal attributes, and normal attributes over override attributes. So,

  • choose a sensible recipe default where possible
  • use a default environment attribute (you use an override attribute in the example)
  • use a role attribute for groups of nodes (again you use an override attribute)
  • and finally default node attribute

See the attribute precedence link in the Chef wiki) for the order in which attributes override each other.

Using the lowest precedence default attributes where possible allows you to set the attribute value depending on the environment, role and node, but frees up the upper levels of precedence when you need to do something tricky.