SaltStack Patterns: Grain/State

It’s occasionally necessary to do actions in configuration management that aren’t easy to define in an idempotent way. For instance, sometimes you need to do an action only the first time your configuration management runs, or you need to fetch some static information from an external source, or you want to put instances in a specific state for a temporary period of time.

In SaltStack (Salt) a common pattern for handling this is what I call the Grain/State pattern. Salt’s grains are relatively static, but it’s possible to add, update, or delete custom grains during a state run, or outside of a state run either by salt-call locally or through remote execution. Grains can be used for conditionals inside of state runs to control the state of the system dynamically.

For instance, it’s possible to have a command run only on initial run:

{% if salt['grains.get']('initial_run', True) %}
Ensure instance has been registered with the ELB:
  module.run:
    - name: boto_elb.register_instances
    - m_name: {{ grains['cluster_name'] }}
    - instances:
      - {{ grains['ec2_instance-id'] }}

Ensure initial_run grain is set:
  grains.present:
    - name: initial_run
    - value: False
{% endif %}

If you wanted to cache an external call that always returns static data, it’s possible to use Jinja and the grains state module:

{% if salt['grains.get']('static_auth_endpoint', False) %}
{% set static_auth_endpoint = salt['my_fake_module.get_auth_endpoint'](grains['cluster_name']) %}
Ensure initial_run grain is set:
  grains.present:
    - name: static_auth_endpoint
    - value: {{ static_auth_endpoint }}
{% endif %}

This is obviously a contrived example and it’s likely better to store this information in a pillar or an external pillar, but it shows how it’s possible to use this pattern when necessary.

If you wanted to set grains using salt-call or remote execution to modify your state run behavior, see the highstate killswitch example I recently posted.

This pattern offers a lot of flexibility, especially for caching expensive calls that would normally occur every run when using Python based grains, or the analogous equivalent in other systems.

This pattern can also be combined with another Salt pattern I call the Returner/External Pillar model. I’ll cover that in a followup post in the future.