Recently we had a challenge to make a very complex deployment, defined with the SaltStack, more usable. The goal was to find a way to pass additional parameter to the state.apply
command call to control if we are doing a full deploy or a fast deploy. Long story short, we ended up reading the source code of the Salt and yes, the solution was found.
What made things even more complicated for us: we need to catch the additional parameter value inside the pillar’s Jinja, and that pillar is not directly attached to the minion, but is being read with import
command from another pillar.
Here is the list of all wrong approaches we tried and failed (and why) with the working solution in the end.
- Pass the additional pillar via cmd:
state.apply app.deploy pillar='{fastdeploy: True}'
. The first thing that comes to a mind. Works great if we have the ability to process conditions in the state’s Jinja code. But doesn’t work if you need to process conditions in the pillar’s Jinja code, as pillar passed via args are not available yet during processing the pillar inside sls. - Use the
localconfig
parameter likestate.apply app.deploy localconfig=config.yaml
. Something that looks very close according to the docs. But it doesn’t, this parameter doesn’t really changes anything at all and reading the source code proved that. - Use the
pillarenv
parameter. Just to complete the picture. Not really our case. - Use Pillar Stack. Theoretically it is possible, this feature allows to read this kind of additional pillar in pillar. But that approach doesn’t directly gives the ability to pass additional parameter to the
state.apply
call. - Get the
argv
. Luckilystate.apply
ignores additionalparameter=value
it doesn’t know. So you can callstate.apply app.deploy fastdeploy=True
and there will be no error. And you can catch this inside the pillar’s Jinja: either with{% set fastdeploy = "fastdeploy=True" in opts.get("argv", []) or "fastdeploy=true" in opts.get("argv", []) %}
, or with{% set fastdeploy = "fastdeploy=True" in salt['config.get']('argv') or "fastdeploy=true" in salt['config.get']('argv') %}
. That approach looked very promising but failed as well. Those argv values are available during pillar value evaluation, so you can, for example, pass the PHP version and use it in the pillar’s Jinja code. But argv value is not yet available when pillar page is being constructed with the usageimport
keyword. - Use the previous approach in the first pillar attached via the pillar’s top sls for the minion. Set the flag somewhere (a tmp file maybe). Read the flag value in the next pillar attached to the minion via pillar’s top sls. Workable, but ugly.
- Finally, use
minion_opts:grains:
in the roster file to pass custom grains to the minion. As we are usingsalt-ssh
to call the deploy, we have a roster file (/etc/salt/roster
). Moreover we are using Docker and salt-ssh call is usually prefixed with aux helper command like./.docker_run.sh salt-ssh --wipe minion state.apply app.deploy
. We have modifiedentrypoint.sh
inside the Docker and our accounting code that generates the roster file. This allows us to call the deploy like./.docker_run.sh salt-ssh --wipe minion state.apply app.deploy grains="{fastdeploy: True}"
. And then you can get it inside pillar’s Jinja with{% if grains.get("fastdeploy", False) %}
. And it works during the construction of the pillar code withimport
.
Also, as we are using yq
to process grains=
additional parameter, we can pass complex grains like grains='{a: b, b: {c: d, e: f}, fastdeploy: True}
, the same way that passing additional pillars works (salt’s builtin feature).