Does saltstack have an equivalent to puppets versioncmp() function? Or alternatively, is there a way to get the distutils.version or packaging.version.parse methods (as mentioned on Compare version strings in Python) available in a jinja+yaml rendered sls file?
you can use the module pkg.version_cmp:
# salt-call pkg.version_cmp '1.0.2' '1.1.1'
local:
-1
# salt-call pkg.version_cmp '0.2.4.1-0ubuntu1' '0.2.4-0ubuntu1'
local:
1
Inside jinja you can use it in a way similar to:
{% if salt['pkg.version_cmp']('1.1.0','1.0.5') > 0 %}
....
{% endif %}
Related
Salt stack state to recursively loop over a directory
Added single and multiple quotes, that didn't help. I don't wish to specify each file for a file replace. I want to loop all files under dir for replace.
{% for file in "/path/{{ to }}/dir" %}
{{ file }}:
file.replace:
- name: {{ file }}
- pattern: /from/
- repl: /{{ to }}/
- backup: False
{% endfor %}
I tried single quotes and multiple quotes, but see the error:
":"ERROR: Minions returned with non-zero exit code
d01xyz011:
Data failed to compile:
----------
Rendering SLS 'base:projects.xyz.p-ser' failed: Jinja syntax error: unexpected '/'; line 140
Can we specify a directory in FOR Loop where we have multiple files to be replaced? Is there additional code that I am missing in the above FOR Loop? Please advise.
You can specify a files list and then do a for loop on it, like
{% set file_list = ['foo', 'bar', 'baz'] %}
{% for f in file_list %}
...
{% endfor %}
But I don't think {% for file in "/path/{{ to }}/dir" %} will do what you think it does: it won't create a list of files in the directory, you will need to create this list elsewhere or in another way and then give it to your for loop in Jinja.
Imagine if you had a file like a standard Python requirements.txt but instead of listing Python packages it listed apt-get-able Ubuntu packages. In its simplest form it would just be a list of package names with newlines delimiters:
# apt-requirements.txt
git
python3.5
python3.5-dev
libssl-dev
Now what if you wanted to install these packages with a salt state by looking at the file at runtime? Here's one way I can imagine doing it:
apt-requirements.txt_installed:
pkg.installed:
- pkgs:
{% for line in salt['cmd.run']('cat ' + my_file).splitlines() %}
- {{ line.strip() }}
{% endfor %}
This seems terrible, though. In addition to being ugly, the file has to be present at render time, which is a serious nuisance.
Does anyone have a better recipe?
You can achieve your goal by using cp.get_file_str.
As you can see in the following example am trying to install two packages saved on a file located on the minion
# yum-requirements.txt
mlocate
screen
The state file will be as the following:
# packages.sls
{% set packages_to_be_installed = salt.cp.get_file_str('/home/yum-requirements.txt').splitlines() %}
install_packages:
pkg.latest:
- pkgs: {{ packages_to_be_installed }}
The result:
minion01:
----------
ID: install_packages
Function: pkg.latest
Result: True
Comment: All packages are up-to-date (mlocate, screen).
Started: 02:09:27.490644
Duration: 4036.966 ms
Changes:
Summary for minion01
------------
Succeeded: 1
Failed: 0
------------
Total states run: 1
Total run time: 4.037 s
Note: I assume that your requirements file doesn't have any commented lines.
Using salt i want to find the attribute(key) and replace it with value based on specific stanza. The attribute(key) is present in multiple times in a file under different stanzas. I want to find my attribute under specific stanza and replace with value.
Example:
output.kafka:
# Boolean flag to enable or disable the output module.
enabled:
I need to find enabled: under output.kafka: and replace it with value. The enabled: attribute present multiple times in my file.
Thanks
Bala.
Salt has a few commands like file.line, file.replace and file.blockreplace that can modify an existing file, but I highly recommend managing the whole file using file.managed. It makes for a less brittle experience.
Here's an example based off your question:
Pillar top file:
cat /srv/pillar/top.sls
base:
'*':
- common
'minion01':
- minion01kafkasettings
Set our pillar data:
cat /srv/pillar/minion01kafkasettings.sls
kafka_output: True
Here's our filebeat template:
cat /srv/salt/filebeat.tmpl
output.kafka:
# Boolean flag to enable or disable the output module.
enabled: {{ pillar.get('kafka_output', True) }}
Here's the filebeat Salt sls file:
cat /srv/salt/filebeat.sls
the_filebeat_file:
file.managed:
- name: /etc/filebeat/filebeat.yml
- template: jinja
- user: root
- group: root
Then we can run the following:
Refresh our pillar data
salt 'minion01' saltutil.refresh_pillar
Then apply the sls file:
salt 'minion01' state.sls filebeat
I have another theory using file.seralize that might work but not in its current state, Maybe Dave could help.
{% set json_data = salt.cp.get_file_str('/etc/filebeat/filebeat.yml') | load_yaml %}
{% do json_data.update({'enabled': pillar.get('kafka_output', True) }) %}
update_config:
file.serialize:
- name: /etc/filebeat/filebeat.yml
- user: root
- group: root
- mode: 644
- formatter: yaml
- dataset: |
{{ json_data | yaml(False)| indent(8) }}
This state should load the whole configuration file then you can modify any of its values based on your pillar setting using the do statement in your case it could be
{% do json_data.update({'enabled': pillar.get('kafka_output', True) }) %}
The config file is populated but not as exepcted as the result will be as following:
'enabled: true
status: active
'
Note there are quotes and the yaml is not intended correctly, is there another way to make it work ? I will update this answer if I found any new results
Is there any way to cause rendering of a salt state to output a warning or error when a requested pillar is not found?
I'm setting up Saltstack for a system with a lot of components, and we use pillars to make sure different Salt states have the same values where appropriate. We also aim to keep all values that will differ from installation to installation in pillars. So there are many pillar files and the total amount of pillar variables is slightly unhinged. When we're installing this system again we'll have to make sure that all all pillars are defined and have an appropriate value. In this process it would greatly help to have warnings or errors when pillars are undefined, instead of having to grep for "None" through the entire minion, then working out where things went wrong. Is there any way to accomplish such warnings or errors?
We can't possibly be the only company with a complicated salt installation.
SOLUTION:
As linked in the approved answer, the solution is to add the following to /etc/salt/minion:
pillar_raise_on_missing: True
PILLAR_RAISE_ON_MISSING: True
Remember to restart salt-minion
You can make sure a pillar return an error if not defined like that:
{%- set port = pillar['db']['host'] %}
Or you can specify default value if there is none.
{%- set host = salt['pillar.get']('db:host', 'localhost') %}
If the pillar is defined but without value, you can use the pillar module, to get an error.
Check here: https://docs.saltstack.com/en/latest/ref/modules/all/salt.modules.pillar.html#salt.modules.pillar.get
Attempt to retrieve the named value from pillar, if the named value is
not available return the passed default. The default return is an
empty string except opts['pillar_raise_on_missing'] is set to
True, in which case a KeyError will be raised.
The default behavior is to produce an error if a requested pillar value isn't present. The documentation recommends using pillar.get with a default value so that missing values get filled in, but it's not required. To get hard errors from states, do an attribute lookup on the pillar instead.
Suppose you have a pillar file that looks like this:
some_app:
opt1: 23
opt2: null
Here are some methods of accessing them and what they will do:
{{ pillar['some_app']['opt1'] }} # Prints 23
{{ pillar.some_app.opt1 }} # Alternate syntax, also prints 23
{{ pillar['some_app']['missing_opt'] }} # ERROR
{{ pillar.some_app.missing_opt }} # ERROR
{{ pillar.get('some_app:missing_opt', 17) }} # No error, prints 17
{{ pillar['some_app']['opt2'] }} # Also no error. I think it prints None.
The methods marked as 'error' will make the state fail noisily when it is run, which I gather is what you want.
I have a pillar file containing data:
zones:
['us-east-1a','us-east-1b']
Now I want to apply a loop in one of the sls files. This is what I am trying:
{% for zone in salt['pillar.get']('zones') %}<br>
- {{zone}}<br>
{ %endfor %}
But it is throwing an error:
Bad Request: Value (['us-west-1a', 'us-west-1b'])
Can you please help me with that?
Use:
zones:
- 'us-east-1a'
- 'us-east-1b'