SaltStack: use `include:` twice in one sls file - salt-stack

I would like to use include: twice in a sls file:
include:
- foo.bar
{% for system in salt['foo.get_systems'](pillar) %}
...
{% endfor %}
include:
- this.is.the.end
But this fails with this message:
- Rendering SLS 'base:example.test' failed: while constructing a mapping
in "<unicode string>", line 4, column 1
found conflicting ID 'include'
in "<unicode string>", line 106, column 1
I guess conflicting ID 'include' means that I can't use include: twice.
What can I do to execute something after the for-loop?

State files are not guaranteed to be executed in a script-like order. They describe a data structure that dictates which state functions need to be run, but you should not think about them as though they are scripts, because they aren't.
You need to "include" all the SLS files that you will be including in a single include statement, and then if you need to make sure that certain states are run before or after other states, you should use the state requisite parameters like require

You can walk around this by working with two sls files.
Move your current sls file ("example/test.sls" which contains the for-loop) to "example/step_one.sls". Then create a new file with this content:
include:
- example.step_one
- this.is.the.end

Related

Execution module function similar to 'file.managed'?

I am trying to find an execution module that will allow me to pass a file through the Jinja templating engine and supply the arguments that should be replaced. There exists a file.managed state module that accomplishes this behavior:
my cool state:
file.managed:
- source: salt://my/cool/file.xml
- name: 'C:\Program Files\My dir\file.xml'
- template: jinja
- context:
someVar: 'some value'
another_var: 12345
However, I cannot find an execution module that can do this. There is file.manage_file but it has a bunch of required arguments that I don't care about: sfn, ret, source, source_sum, user, group, mode, attrs, saltenv, backup -- file.manage_file will fail if you do not provide values for these.
The closest module function I've found is cp.get_template but it doesn't allow you to pass in context or defaults, so I've had to templatize my XML file to read in data from the pillar like so:
{%- from "map.jinja" import my_vars with context %}
<?xml version="1.0" encoding="UTF-8"?>
<root>
<some-element>{{ my_vars.some-element }}</some-element>
<another-thing>{{ my_vars.another-thing }}</another-thing>
</root>
This works, but then I can only render my XML with pillar data -- I want to be able to pass in variables when calling my execution module and use those variables to render my XML. Is there any way to accomplish this behavior with an execution module?
I figured this out using a combination of execution modules:
# Get template file
templateContent = __salt__['cp.get_file_str'](
'salt://my/cool/file.xml'
)
# Render file with Jinja
renderedContent = __salt__['file.apply_template_on_contents'](
contents = templateContent,
template = 'jinja',
saltenv = 'base',
defaults = defaults,
context = context
)
# Write the rendered file to disk
__salt__['file.write'](
'/opt/some/path/on/minion/file.xml',
renderedContent
)
Two things to consider:
file.apply_template_on_contents requires that you supply both the defaults and context args. This actually worked out pretty well for my use-case because I set defaults using values from a pillar that is applied to all minions, and context is the user-supplied overrides.
cp.get_file_str returns the file contents as a string -- I don't know how this would perform on a very large file, but for smaller configuration files I don't see an issue.

SaltStack: include only one state of a file

I have a sls file foo/bar.sls.
Since running all states takes too long, I would like to call only foo/bar.sls.
This is works with state.sls.
But one simple state from a different sls file (root/big.sls) is needed.
Up to now I have no clue how to do this in salt-stack. I could use "include" but this would include and execute all states from root/big.sls. That's not what I want.
Compared to python: I want to call one method of a module, not every method of this module.
How to do this with saltstack?

How to assert that mine.get returns nonempty result?

We use the Salt Mine to discover other minions matching certain criteria, in order to build configuration files. However, for various reasons, typically caching at one level or another, or minions not connecting to the master, the result of mine.get could be wrong. The most obvious wrong result is an empty result, i.e. no minions matched the tgt argument. Is it possible to cause salt to fail to run a state (either state.highstate or state.sls) if the mine.get result is empty?
For example, consider a Jinja-templated configuration file (e.g. for Apache ZooKeeper):
# ...
{% set master_nodes = salt['mine.get']('roles:master', 'network.get_hostname', tgt_type='grain').values() | sort -%}
{% for master_node in master_nodes -%}
server.{{ loop.index }}={{ master_node }}:2888:3888
{% endif -%}
If the mine.get call matches no minions, then master_nodes will be an empty list and so no server lines will appear in the configuration file. I'd rather have the state fail to run than silently create a useless configuration. Even better would be to match the number of results against a pillar value (e.g., pillar says there are 3 masters, fail if mine.get returns more or less than 3 results).

Using Salt Variables

I am relatively new to Salt, YAML, Jinja etc. Learning as I go. I have a yaml file being used with SaltStack to distribute several files to servers. A few of the files have a version indicator in the name. I would like to enter this once in the yaml file and then use it in name variables throughout the file. I’m not clear from searching if this is possible?
Example. I want to distribute a set of files to install DB2 version 11 fixpack 2. I have an install script, a configure script, a response file and a zip file with “fp2” in the name. I’d like to define a “ver” variable at the top of the yaml and then use it when defining the other filenames. Is this possible?
You can create a variable on the top of your state file using jinja format,
{% set variable_name = 'variable_value' %}
and then use {{ }} to render it like, {{ variable_name }}
Or if you want to use variable in a logic or within a loop,
{% if variable_name %}

What's the best way for a formula to provide attribute defaults?

Chef has a very elaborate (maybe too much so) scheme for cookbooks to provide default values of attributes. I think Puppet does something similar with class parameters where defaults usually go into params.pp. With Salt, I've seen:
specifying default value in dictionary/pillar lookups.
the grains.filter_by merging of default attribute values with user-provided pillar data (e.g., map.jinja in apache-formula)
in a call to file.managed state, specifying default attribute values as the defaults parameter and user-specified pillar data as context.
Option 1 seems to be the most common, but has the drawback that the template file becomes very hard to read. It also requires repeating the default value whenever the lookup is done, making it very easy to make a mistake.
Option 2 feels closest in spirit to Chef's approach, but seems to expect the defaults broken down into a dictionary of cases based on some filtering attribute (e.g., the OS type recorded in grains).
Option 3 is not bad, but puts attribute defaults into the state file, instead of separating them into their own file as they are with option 2.
Saltstack's best practices doc endorses Option 2, except that it doesn't address how to merge defaults with user-specified values without having to use grains.filter_by. Is there any way around it?
Note: The behavior of defaults.get changed in version 2015.8, and so 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 coupled with a defaults.yaml file should do what you want. 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 with the configuration file placed at /etc/my-formula/something.conf (the pillar value) using salt://my-formula/files/template.jinja as the source (the default, for which no pillar override was supplied).
Note the unintuitive structure of the pillar and defaults files; defaults.get expects defaults.yaml to have its values at the root of the file, but expects the pillar overrides to be in a dictionary named after the formula, because consistency is for the weak.
The documentation for defaults.get gives its example using defaults.json instead of defaults.yaml. That works but I find yaml much more readable. And writable.
There is a bug using defaults.get from inside a managed template rather than within the state file, and as far as I know it's still open. It can still be made to work; the workaround is behind the link.
The behavior of defaults.get changed in 2015.8, possibly due to a bug. This answer describes a compatible method of getting the same results in (at least) 2015.8 and later.
Suppose 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 formula defaults are in defaults.yaml, but can be overridden in pillar. Anything not provided in pillar should use the value in defaults. 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 this does: The contents of defaults.yaml are loaded in as a nested dictionary. That nested dictionary is then merged with the contents of the something pillar key, with the pillar winning conflicts. The result is a nested dictionary containing both the defaults and any pillar overrides, which can then be used directly without concern to where a particular value came from.
slspath is not strictly required for this to work; it's a magic variable that contains the directory path to the currently-running sls. I like to use it because it decouples the formula from any particular location in the directory tree. It is not normally available from managed templates, which is why I pass it on as explicit context above. It may not work as expected in older versions, in which case you'll have to provide a path relative to the root of the salt tree.
The downside to this method is that, so far as I know, you can't access the final dictionary with salt's colon-based nested-keys syntax; you need to descend through it one level at a time. I have not had problems with that (dot syntax is easier to type anyway), but it is a downside. Another downside is the need for a few lines of boilerplate at the top of any .sls or template using the technique.
There are a few upsides. One is that you can loop over the final dictionary or its sub-dicts with .items() and the Right Thing will happen, which was not the case with defaults.get and which drove me insane. Another is that, if and when the salt team restores defaults.get's old functionality, the defaults/pillar structure suggested here is already compatible and they'll work fine side by side.

Resources