What's the best way for a formula to provide default attributes?

The chef has a very complex (perhaps too large) cookbook schema to provide default attribute values. I think Puppet is doing something similar with class parameters, where the default values ​​usually go to params.pp

. With Salt I saw:

  • specifying a default value in dictionary / column searches.
  • grains.filter_by

    merging default attribute values ​​with user-supplied pillar data (like map.jinja in apache-formula )
  • when invoking the state file.managed

    , specifying the default attribute values ​​as a parameter defaults

    and the user-specified column data as context

    .

Option 1 seems to be the most common, but has the disadvantage that the template file becomes very difficult to read. It also requires the default to be repeated whenever a search is performed, making it very easy to make a mistake.

Option 2 feels closest to a chef approach, but seems to expect the default defaults to break down into a dictionary of cases based on some filtering attribute (like OS type written in beans).

Option 3 isn't bad, but it puts the default attributes in the state file instead of splitting them into its own file like Option 2.

Saltstack best practice doc supports option 2, except that it does not decide how to combine the default values ​​with user-specified values ​​without using grains.filter_by

. Is there a way to get around this?

+3


source to share


2 answers


The defaults.get behavior changed in 2015 .8, possibly due to a bug. This answer describes a compatible method of getting the same results (minimum) 2015.8 and later.

Let's assume your formula tree looks like this:

something/
    files/
        template.jinja
    init.sls
    defaults.yaml

# defaults.yaml
conf_location: /etc/something.conf
conf_source: salt://something/files/template.jinja

# pillar/something.sls
something:
  conf_location: /etc/something/something.conf

      

The idea is that the defaults are in default. yaml, but can be overridden on a column. Anything not listed in the column should use the default value. You can accomplish this with a few lines at the top of any given .sls:

# something/init.sls
{%- set pget = salt['pillar.get'] %} # Convenience alias
{%- import_yaml slspath + "/defaults.yaml" as defaults %}
{%- set something = pget('something', defaults, merge=True) %}

something-conf-file:
  file.managed:
    - name: {{ something.conf_location }}
    - source: {{ something.conf_source }}
    - template: jinja
    - context:
        slspath: {{ slspath }}
    ... and so on.

      



What it does: The content of defaults.yaml is loaded as a nested dictionary. This nested dictionary is then concatenated with the content of the column key something

, with collisions causing the pillars. The result is a nested dictionary containing both default values ​​and any column overrides that can then be used directly without regard to where the particular value came from.

slspath

not strictly required for this; it is a magic variable that contains the directory path to the current sls. I like to use it because it separates the formula from any specific location in the directory tree. It is usually not available from managed templates, so I pass it as explicit context above. It may not work as expected in older versions, in which case you will have to specify a path relative to the root of the salt tree.

The downside to this method is that, as far as I know, you cannot access the final dictionary with a honeycomb colon-based nested key syntax; you need to go down through it one level at a time. I had no problem with this (the dot syntax is easier to type anyway), but it's a downside. Another disadvantage is the need for multiple lines of template at the top of any .sls or template using this technology.

There are several sides. One of them is that you can iterate over the final dictionary or its subcommands with the help .items()

and the right thing will happen, which was not the case with defaults.get, and it got me crazy. The other is that if and when the salt command restores the old defaults.get functions, the default / column structure suggested here is already compatible and they will work just fine with each other.

+1


source


Note. The behavior of defaults.get has changed in version 2015.8 and therefore the method described here no longer works. I am leaving this answer for users of older versions and will post a similar method for current versions.

defaults.get

combined with the file defaults.yaml

should do what you want. Let's assume your formula tree looks like this:

my-formula/
    files/
        template.jinja
    init.sls
    defaults.yaml

# my-formula/init.sls
my-formula-conf-file:
  file.managed:
    - name: {{ salt['defaults.get']('conf_location') }}
    - source: {{ salt['defaults.get']('conf_source') }}
    ... and so on.

# defaults.yaml
conf_location: /etc/my-formula.conf
conf_source: salt://my-formula/files/template.jinja

# pillar/my-formula.sls
my-formula:
  conf_location: /etc/my-formula/something.conf

      

This will end up with a config file placed in /etc/my-formula/something.conf

(column value) using salt://my-formula/files/template.jinja

as source (the default for which no column override was checked).



Note the default unintuitive column and file structure; defaults.get

expects defaults.yaml

to have its values ​​at the root of the file, but expects the column to be overridden in a dictionary named by the formula, since consistency is weak.

The documentation for defaults.get gives its example using defaults.json

instead defaults.yaml

. It works, but I find the yaml to be much more readable. And is available for recording.

There is a bug using defaults.get inside the managed template, not in the state file, and as far as I know it is still open. It can still be made to work; the workaround is behind the link.

+4


source







All Articles