Jinja templates in ansible loop - ansible

I need to run an ansible loop based on input from a CSV file. I am using the following question / answer as reference. However, I cannot seem to figure out where to actually include the jinja part for the loop.
So far this is what I have, but it throws an error:
---
- hosts: localhost
connection: local
gather_facts: no
vars:
csv_var: "{{ lookup ('file', 'file.csv') }}"
tasks:
- debug:
msg: "{{ item }}"
with_items:
- {% set list = csv_var.split(",") %}
file.csv has the following content: 345,1234,1234
Ideally the message should print out the numbers above.
The syntax error I was getting is:
The offending line appears to be:
with_items:
- {% set list = csv_var.split(",") %}
^ here
exception type: <class 'yaml.scanner.ScannerError'>
exception: while scanning for the next token
found character that cannot start any token
in "<unicode string>", line 19, column 10

You should use Jinja2 expression not a statement.
You should also quote any string that starts with { in Ansible:
- debug:
msg: "{{ item }}"
with_items: "{{ csv_var.split(',') }}"
And there is no need to wrap the resulting list in another list (dash before element), although Ansible handles this automatically.

Related

Remove last character from variable

I am trying to remove the last character of the below output but I am having trouble doing it:
- debug:
msg: "{{ ansible_mounts|json_query('[?mount == `/`].device') }}"
register: rootpart
The below works with simple text:
- debug:
msg: "{{ '/dev/sda4'[:-1] }}"
But not with a variable:
- debug:
msg: "{{ rootpart[:-1] }}"
Error:
msg: Unexpected templating type error occurred on ({{ rootpart[:-1] }}): unhashable type: 'slice'
There is a big "don't" in your current attempt: you should not register on a debug task.
If you want to create a new variable, then use the set_fact module.
This is not only a better way to do it, it is also saving you from having a dictionary with the keys changed, failed and msg to dive into in order to get the variable you were expecting out of the msg property.
Then, your json_query is going to return you a list of devices, no matter if there is only one match thanks to your filter. So, you also need to get the first element of this list.
So, with all this, here are your two tasks:
- set_fact:
root_part: "{{ ansible_mounts | json_query(_query) }}"
vars:
_query: "[?mount == `/`].device | [0]"
- debug:
var: root_part[:-1]

how do I use an ansible string variable that contains jinja delimiters?

this
- name: ugly
hosts: localhost
vars:
badstr: "asdf{%jkl"
mydir: "."
mydict:
filea:
Value: "blue!42!"
fileb:
Value: "a{%isbad"
tasks:
- copy:
dest: "{{ item.key }}"
content: "{{ item.value.Value }}"
loop: "{{ mydict|default({})|dict2items }}"
gives me this error:
fatal: [localhost]: FAILED! => {"msg": "An unhandled exception occurred while templating 'asdf{%jkl'. Error was a <class 'ansible.errors.AnsibleError'>, original message: template error while templating string: Encountered unknown tag 'jkl'.. String: asdf{%jkl"}
The 'mydict' structure is returned from a plugin and I do not get to define the members. One of the 'Value's contains a "{%". Any reference to it will cause an error, whether as a variable, file content or in a template.
I have tried all kinds of quoting and combinations of unsafe, {{, %raw, etc. It either gives me the error or puts the name of the variable in the file.
How can I write the value to a file? Or just use it as a variable?
Ansible 2.8.4 on MacOS 11.3, also ansible 2.9 on RHEL 7.
You can use !unsafe for the variables expected to have these chars. Check this documentation. when !unsafe is used, the string/variable will never get templated.
- name: ugly
hosts: localhost
vars:
badstr: !unsafe "asdf{%jkl"
mydir: "."
mydict:
filea:
Value: !unsafe "blue!42!"
fileb:
Value: !unsafe "a{%isbad"
tasks:
- copy:
dest: "{{ item.key }}"
content: "{{ item.value.Value }}"
loop: "{{ mydict|default({})|dict2items }}"
When handling values returned by lookup plugins, Ansible uses a data
type called unsafe to block templating. Marking data as unsafe
prevents malicious users from abusing Jinja2 templates to execute
arbitrary code on target machines. The Ansible implementation ensures
that unsafe values are never templated. It is more comprehensive than
escaping Jinja2 with {% raw %} ... {% endraw %} tags.
You can use the same unsafe data type in variables you define, to
prevent templating errors and information disclosure. You can mark
values supplied by vars_prompts as unsafe. You can also use unsafe in
playbooks. The most common use cases include passwords that allow
special characters like { or %, and JSON arguments that look like
templates but should not be templated. For example:
---
mypassword: !unsafe 234%234{435lkj{{lkjsdf
The problem here is not in the copy task where the values are
evaluated; the problem is how they are being set. For example, if I
create a simple ansible module named example.sh that looks like
this:
#!/bin/sh
cat <<EOF
{
"files": {
"filea": {
"Value": "blue!42!"
},
"fileb": {
"Value": "a{%isbad"
}
}
}
EOF
I can write a playbook like this:
- name: ugly
hosts: localhost
tasks:
- example:
register: mydict
- copy:
dest: "{{ item.key }}"
content: "{{ item.value.Value }}"
loop: "{{ mydict.files|dict2items }}"
And this runs as expected, creating without any errors a file fileb with the content:
a{%isbad
Similarly, if I read the data from a JSON file and pass it through from_json, it also works fine:
- name: ugly
hosts: localhost
tasks:
- set_fact:
mydict: "{{ lookup('file', 'data.json')|from_json }}"
- copy:
dest: "{{ item.key }}"
content: "{{ item.value.Value }}"
loop: "{{ mydict.files|dict2items }}"
The problem only happens if you define the variables in a context in
which Ansible is looking for Jinja templating -- so, as the values of
variables in a playbook, a vars file, the arguments to set_fact,
etc.
You can potentially work around the problem by changing how you are
consuming these values.

Not picking up variable in expression correctly - Ansible

I'm trying the following -
---
- name: Test
hosts: "{{ hosts }}"
vars:
before: "groups.{{ hosts[0] }}_group_name"
after: "{{ before }}" # This equals {{ groups.test_group_name }}
roles:
- test-check
Just an explanation: I'm feeding hosts in when executing the playbook as a 'var'. In this case, var = test. The expected var string for before would be groups.test_group_name which is a group that contains multiple hosts in my inventory. However, when I execute this, after remains as groups.test_group_name instead of the expected array of hosts.
Does anybody know how I can remedy this? If I hard-code the host_name (test) into the after var, it picks it up, but if I don't, it doesn't. Thanks.
It appears you are trying to do pseudocode: {{ eval(before) }} but that is not how ansible, or jinja2, work. Thankfully, groups is a normal python dict and thus is subject to the __getitem__ syntax [] to dynamically look up keys
Thus, you likely want:
- hosts: "{{ hosts }}"
vars:
after: "{{ groups[ hosts[0]+'_group_name' ] }}"
tasks:
- debug: var=after

How to register with_items and act on conditional check result for each item

I'd like to register the contents of bashrc for two users and edit as/if required. My play is as follows.
- name: Check bashrc
shell: cat {{ item }}/.bashrc
register: bashrc
with_items:
- "{{ nodepool_home }}"
- "{{ zuul_home }}"
- name: Configure bashrc
shell:
cmd: |
cat >> {{ item }}/.bashrc <<EOF
STUFF
EOF
with_items:
- "{{ nodepool_home }}"
- "{{ zuul_home }}"
when: '"STUFF" not in bashrc.stdout'
It fails as follows:
fatal: [ca-o3lscizuul]: FAILED! => {"failed": true, "msg": "The conditional check '\"STUFF\" not in bashrc.stdout' failed. The error was: error while evaluating conditional (\"STUFF\" not in bashrc.stdout): Unable to look up a name or access an attribute in template string ({% if \"STUFF\" not in bashrc.stdout %} True {% else %} False {% endif %}).\nMake sure your variable name does not contain invalid characters like '-': argument of type 'StrictUndefined' is not iterable\n\nThe error appears to have been in '/root/openstack-ci/infrastructure-setup/staging/zuul/create-user.yml': line 35, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: Configure bashrc\n ^ here\n"}
I think, if I understand your requirement correctly, you can use the 'lineinfile' or 'blockinfile' modules and save yourself the hassle of testing for the existence of the content:
- name: Noddy example data
set_fact:
single_line: "STUFF"
multi_line: |
STUFF
STUFF
profile_dirs:
- "{{ nodepool_home }}"
- "{{ zuul_home }}"
- name: Ensure STUFF exists in file
lineinfile:
path: "{{ item }}/.bashrc"
line: "{{ single_line }}"
loop: "{{ profile_dirs }}"
- name: Ensure block of STUFF exists in file
blockinfile:
path: "{{ item }}/.bashrc"
block: "{{ multi_line }}"
loop: "{{ profile_dirs }}"
Both modules give a lot more control and you can find their docs here: lineinfile | blockinfile

Ansible - iterate over a list of dictionaries

I built the following list but I don't succeed to iterate over it.
Should I use with_items? with_elements? or something else?
My goal is to iterate over all the hosts in the inventory, get their name and their IP, and finally print it.
- set_fact:
list_of_hosts: |
{% set myList = [] %}
{% for host in groups['all'] %}
{% set ignored = myList.extend([{'server_name': host, 'server_ip': hostvars[host].ansible_eth0.ipv4.address }]) %}
{% endfor %}
{{ myList }}
- debug: msg="{{ item.server_name }}"
with_items: "{{ list_of_hosts }}"
Here is my list when I debug it:
TASK [common : debug] ************************************************************************************************
ok: [my1stServer] => {
"msg": " [{'server_ip': u'192.168.0.1', 'server_name': u'my1stServer'}, {'server_ip': u'192.168.0.2', 'server_name': u'my2ndServer'}]\n"
}
And here is the error but it is not really relevant :
fatal: [my1stServer]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'ansible.vars.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'server_name'\n\nThe error appears to have been in 'hosts.yml': line 19, 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- debug: msg=\"{{ item.server_name }}\"\n ^ here\nWe could be wrong, but this one looks like it might be an issue with\nmissing quotes. Always quote template expression brackets when they\nstart a value. For instance:\n\n with_items:\n - {{ foo }}\n\nShould be written as:\n\n with_items:\n - \"{{ foo }}\"\n"}
Please forgive me for bluntness, but the proposed implementation makes it an effort to understand what the idea actually is. which is simple: to print some variables anyway present in hostvars[host] for a list of hosts picked by various criteria.
If we keep implementation close to that idea, implementation is simpler.
So what I'd do to create a list of hosts picked by group membership, or possibly 'hand picked' is to actually do what I just wrote :).
Consider this task list:
# this task creates an empty list
- name: create my_cool_list
set_fact:
my_cool_list: []
# this task adds to the list all hosts in groups we're iterating over
- name: update my cool list with whole groups
set_fact: '{{my_cool_list + groups[curr_grp]}}'
with_items:
- grp1
- grp2
loop_control:
loop_var: curr_grp
# this task adds to the list all hosts we're iterating over
- name: update my cool list with specific hosts
set_fact: '{{my_cool_list + [curr_node]}}'
with_items:
- node001
- node101
loop_control:
loop_var: curr_node
# now we can iterate over the list, accessing specific fields on each host
- name: go over my cool list and print ansible_init_mgr
debug:
msg: 'hostvars["{{curr_host}}"].ansible_init_mgr: {{hostvars[curr_host].ansible_init_mgr}}'
with_items: '{{my_cool_list|default([], true)|list}}'
Furthermore, you can add safety when: by validating the key you're accessing is defined..
And, to print a selection of variables about each host, you should use jinja filter map('extract',...):
- name: print some vars from each host
debug:
msg: {'server_name': '{{hostvars[curr_node]|selectattr("ansible_hostname")}}', 'ip_address': '{{hostvars[curr_node]|selectattr("ansible_eth0.ipv4.address")}}'}
with_items: '{{my_cool_list|default([], true)|list}}'
loop_control:
loop_var: curr_node
IF you want to increase readability though, you better write a filter plugin, which would do the above stuff, and hide iteration ugliness in readable way, so you can have:
Either (For generic approach, i.e. without renaming attributes)
- name: print some vars from each host
debug:
msg: '{{my_cool_list|multi_select_in_dict(["ansible_hostname", "ansible_eth0.ipv4.address")}}'
Or specific approach (so that you are using specific hard coded remapping of attributes...)
- name: print some vars from each host
debug:
msg: '{{my_cool_list|my_cool_filter}}'

Resources