Search code examples
salt-stackdevopsconfiguration-management

SaltStack: how do I repeat other states with context?


I created a complex state for API service, it involves git checkouts, python venv, uwsgi, nginx, etc etc. It works fine.

Now I would like to turn it into a template and execute it several times per minion, with variables supplied from pillar - i.e something like.

    {% for apiserver in pillar.apiservers %}
      include apiserver_template.sls, locals: apiserver.config
    {% endfor %}

where apiserver_template will work with context supplied to it, with apiserver.config having all config data for each API instance. I know syntax is wrong but hopefully I am communicating the idea - ideally, something like executing ruby partials with supplying local variables.

How is it done properly in saltland?


Solution

  • It sounds to me like Jinja Macro is something you want to use for this. You can find more information about usage here: https://docs.saltstack.com/en/2015.8/topics/development/conventions/formulas.html#jinja-macros

    In short what you will have in your case may look like:

    {% macro api_server(git_repo, python_venv_path, python_venv_requirements) %}
    {{python_venv_path}}:
      virtualenv.managed:
        - system_site_packages: False
        - requirements: salt://{{python_venv_requirements}}
    
    {{git_repo}}:
      git.latest:
        - name: {{git_repo}}
    {% endmacro %}
    

    Assuming you have a pillar apiservers where each api server has git_repo, python_venv_path and python_venv_requirements values, you can use the macro like this:

    {% for server in salt.pillar.get('apiservers', []) %}
    {{ api_server(server['git_repo'], server['python_venv_path'], server['python_venv_requirements']) }}
    {% endfor %}
    

    If you want - you can also put a macro in a separate state file and then import a marco as a regular salt resource.

    Please also not that instead of pillar.apiservers I used salt.pillar.get('apiservers', []). This is a safer way to get data from pillar. If for some reason a pillar is unavailable - the later code will result in empty dict instead of failure in first case.