If statements with nested grain returns nothing - salt-stack

I am trying to write an if statement based on a nested grain. I have tried this statement in multiple different ways:
System Services Needed:
module.run:
- name: service.systemctl_reload
- onchanges:
- file: /lib/systemd/system/salt-minion.service
{% if salt['grains.get']('Project:DeviceTypeID') == '2' %}
- file: /etc/rc.local
- file: /opt/interfaces_init.sh
{% endif %}
Returns:
Rendering SLS 'Development:System' failed: Jinja variable 'dict object' has no attribute 'Project:DeviceTypeID'
System Services Needed:
module.run:
- name: service.systemctl_reload
- onchanges:
- file: /lib/systemd/system/salt-minion.service
{% if grains['Project']['DeviceTypeID'] == '2' %}
- file: /etc/rc.local
- file: /opt/interfaces_init.sh
{% endif %}
System Services Needed:
module.run:
- name: service.systemctl_reload
- onchanges:
- file: /lib/systemd/system/salt-minion.service
{% if grains['Project:DeviceTypeID'] == '2' %}
- file: /etc/rc.local
- file: /opt/interfaces_init.sh
{% endif %}
As you can tell from the example their are multiple device type IDs. In this example DeviceTypeID = 2 I need to worry about rc.local and a shell script. I can not seem to get this work for the life of me. I know the grain exists as I can run the following:
sudo salt 'Dev-Box' grains.get Project
and I will get:
Dev-Box:
DeviceTypeID:
1
IsActive:
True
SoftwareEnvironmentName:
Production
SoftwareVersion:
Foo
This is either a bug or I am missing something (significantly more likely I am missing something). Any help would be much appreciated.
Edit 1:
Added ['grains.get']('Project:DeviceTypeID') example

in salt grains.get return a dictionary in the following format:
{'minion-id': value}
I believe if you change your code into something like bellow, it should works.
{% if salt['grains.get']('Project:DeviceTypeID')[minion-id] == '2' %}

If you can't do:
salt 'Dev-Box' grains.get 'Project:DeviceTypeID'
Then you don't actually have the proper grain set.
Try the following:
salt 'Dev-Box' grains.setval Project '{"DeviceTypeID": 2, "IsActive": True, "SoftwareEnvironmentName": "Production", "SoftwareVersion": "Foo"}'
Then the following state:
Do the {{ salt['grains.get']('Project:DeviceTypeID') }} things:
test.succeed_with_changes:
- some: thing
You should get:
ID: Do the 2 things
Function: test.succeed_with_changes
Result: True
Comment: Success!
Started: 17:10:42.739240
Duration: 0.491 ms
Changes:
----------
testing:
----------
new:
Something pretended to change
old:
Unchanged
Given what you wrote elsewhere
salt Dev-Box grains.setval BETTI "{'DeviceTypeID': 2, 'IsActive': True SoftwareEnvironmentName': 'Production', 'SoftwareVersion': 'Foo'}"
Your problem is that you have ' and " confused.
Wrapping the value with " makes it a string. Wrapping it with ' and providing valid JSON makes it a dictionary value.

Related

Dictionary value access using ansible

I have a situation where we have 2 dictionary defined in ansible role default and the selection of dictionary is based of an input variable. I want to set the fact with one of the dict's specific key value.
Below is the example code:
test.yml paybook content:
- hosts: localhost
gather_facts: true
roles:
- role1
tags: ['role1']
roles/role1/tasks/main.yml content:
- name: set fact
set_fact:
node_vip: "{% if node_vip_run == 'no' %}node_vip_no{% elif node_vip_run == 'yes' %}node_vip_yes{% endif %}"
- debug:
var: node_vip
verbosity: 1
- debug:
var: "{{ node_vip }}.ece_endpoint"
verbosity: 1
- name: set fact
set_fact:
ece_endpoint_fact: "{{ node_vip[ece_endpoint] }}"
- debug:
var: ece_endpoint
verbosity: 1
roles/role1/defaults/main.yml content:
node_vip_yes:
ece_endpoint: "https://1.1.1.1:8080"
cac_endpoint: "https:2.2.2.2:8080"
node_vip_no:
ece_endpoint: "http://3.3.3.3:8080"
cac_endpoint: "http:4.4.4.4:8080"
Run playbook:
ansible-playbook test.yaml --extra-vars 'node_vip_run=no' -v
The set fact of variable "ece_endpoint_fact" should have value "https://1.1.1.1:8080 OR http://3.3.3.3:8080" depending on the parameter input in ansible command. But I keep on getting below error:
TASK [role1 : set fact] *******************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'unicode object' has no attribute u'http://3.3.3.3:8080'\n\nThe error appears to be in '/root/roles/role1/tasks/main.yml': line 46, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: set fact\n ^ here\n"}
Please suggest what needs to be done to resolve this.
Thanks
Right now, you set node_vip to either the literal string "node_vip_no" or "node_vip_yes". But if you change it to do {{ node_vip_no }} / {{ node_vip_yes }}, then node_vip will have the value of the variable node_vip_no / node_vip_yes instead of being a literal string.
- name: set fact
set_fact:
node_vip: "{% if node_vip_run == 'no' %}{{ node_vip_no }}{% elif node_vip_run == 'yes' %}{{ node_vip_yes }}{% endif %}"
This will have node_vip's value be something like:
TASK [debug] ***************************************************************
ok: [localhost] => {
"node_vip": {
"cac_endpoint": "https:2.2.2.2:8080",
"ece_endpoint": "https://1.1.1.1:8080"
}
}
Then in your other set_fact, it should work if you put quotes around the property name:
- name: set fact
set_fact:
ece_endpoint_fact: "{{ node_vip['ece_endpoint'] }}"
# Added quotes ^ ^

Access listed grain values

I'm trying to access listed grain values from state file, need help on this.
State file is as below
{% set list = grains['selinux'] %}
echo {{ list }}:
cmd.run
But when i run the state file got the error.
# salt '*' state.sls list_grains
client1:
Data failed to compile:
----------
Rendering SLS 'base:list_grains' failed: mapping values are not allowed in this context
ERROR: Minions returned with non-zero exit code
[root#server ~]# vim /srv/salt/list_grains.sls
grain values accessed are as below
# salt '*' grains.item selinux
client1:
----------
selinux:
----------
enabled:
True
enforced:
Permissive
The selinux grain is a dictionary/map like:
selinux:
enabled: True
enforced: Permissive
So in a state ID you cannot have dictionary/map. You can pick the required dictionary key like list.enabled or list.enforced.
For example, the below state ID will output Permissive:
{% set list = grains['selinux'] %}
echo {{ list.enforced }}:
cmd.run
If you want to get the complete dict as output, you can use a module like test.echo:
{% set list = grains['selinux'] %}
show-selinux-grains:
module.run:
- name: test.echo
- text: "{{ list }}"

Salt State Rendering: Can I See the Output of the Jinja Phase?

Question:
Is there any way to see the output of the Jinja phase, before attempting to parse as YAML?
Background:
I was trying to debug a Salt problem where I was getting this error:
local:
Data failed to compile:
----------
Rendering SLS ':test.sls' failed: mapping values are not allowed in this context
Not very helpful: No line number? What's a 'mapping value'? etc.
The problem boiled down to something like this:
{%- for x in [1, 2] -%}
Test {{ x }}:
cmd.run:
- name: echo Test {{ x }}
{%- endfor -%}
A seasoned Salt person will recognize that I've messed up the whitespace so that the Jinja was producing the output below:
Test 1:
cmd.run:
- name: echo Test 1 Test 2:
cmd.run:
- name: echo Test 2
However, this was frustrating to find in a complex set of states with no information on where the problem was occurring and no clear description of what the problem even was.
In the process of debugging this I learned that you can get the YAML tree using slsutil.renderer, like this:
% salt-call --local slsutil.renderer `pwd`/test.sls 'jinja'
local:
----------
Test 1:
----------
cmd.run:
|_
----------
name:
echo Test 1
Test 2:
----------
cmd.run:
|_
----------
name:
echo Test 2
But this of course requires that the YAML is valid. So how can I get Salt to output the templates as in my third snippet above, AFTER the Jinja has been evaluated but BEFORE Salt tries to parse it as YAML?
Oh wow, as I revisit this I've learned something new. I believe the answer is cp.get_template.
Test file:
% cat test.sls
{%- for x in [1, 2] -%}
Test {{ x }}:
cmd.run:
- name: echo Test {{ x }}
{%- endfor -%}
Now cp.get_template renders the Jinja and shows the raw output:
% salt-call --local cp.get_template `pwd`/test.sls /dev/stdout
Test 1:
cmd.run:
- name: echo Test 1Test 2:
cmd.run:
- name: echo Test 2
local:
/dev/stdout

Salt state with check if file on salt master exists

I try to create the following state, but I don't know how to write the if clause? Maybe someone can help me with it. What I try to accomplish is that salt takes a configuration file if a file with the target hostname exists and else take the default config.
example:
{% if ??? test -f ??? salt://ntpd/ntp.conf_{{ salt['grains.get']('host') }} %}
ntpd-config:
file.managed:
- name: /etc/ntp.conf
- source: salt://ntpd/ntp.conf_{{ salt['grains.get']('host') }}
- user: root
- group: root
- file_mode: 644
- require:
- ntpd-pkgs
{% else %}
ntpd-config:
file.managed:
- name: /etc/ntp.conf
- source: salt://ntpd/ntp.conf
- user: root
- group: root
- file_mode: 644
- require:
- ntpd-pkgs
{% endif %}
Hope, someone could help me.
Thanks in advance!
Matthias
I just found the answer by myself.
Found out in the documentation that I can define multiple sources. The last one is then the default one if none of the others bevore exists.
This now works:
ntpd-config:
file.managed:
- name: /etc/ntp.conf
- source:
- salt://ntpd/ntp.conf_{{ salt['grains.get']('host') }}
- salt://ntpd/ntp.conf

How to get element from return statement with sls file

I have the following line in an .sls file, but it always seems to succeed whatever server I put in there:
{% if salt['network.connect']('server.network.com', 80).result == True -%}
When I run salt-call --local network.connect server.network.com 80 I get the following output:
local:
----------
comment:
Unable to connect to server.network.com on tcp port 80
result:
False
The result is structured.
How can I get to result in the sls state above?
There's no need for the == True.
I tested these both:
{% if salt['network.connect']('server.network.com', 80).result %}
as well as the inverse:
{% if not salt['network.connect']('server.network.com', 80).result %}

Resources