Iterating over vars **only** defined in inventory for a host - ansible

I would like to iterate over the vars defined in the inventory file of an ansible playbook. Indeed, in my inventory I define some vars to put in a .ini
file on the target host. I have something like this in my inventory file:
[myhost:vars]
VAR1=VALUE1
VAR2=VALUE2
I tried something like this:
- name: fill ini file with variables
ini_file:
path: "{{ myfile.ini }}"
section: vars
option: "{{ item.key }}"
value: "{{ item.value }}"
create: yes
with_dict: "{{ hostvars[inventory_hostname] }}"
myfile.ini should look like this at the end:
[vars]
VAR1=VALUE1
VAR2=VALUE2
The problem is: I properly have 'VAR1' and 'VAR2', but also all the Ansible variables for the host. I would like to iterate only on those defined in
the inventory (inventory_vars ? Does not exist).
Any help would be appreciated :)

There is no way to determine were a certain fact/variable was set, or filter them by their origin.
Instead, you can define a dictionary (JSON) in your inventory:
[myhost]
localhost
[myhost:vars]
my_dict={"VAR1": "VALUE1", "VAR2": "VALUE2"}
and use it in the iterator:
with_dict: "{{ my_dict }}"
Mind that in your example with_dict is wrongly indented.

Related

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.

Ansible loop over inventory file (two dimensional list)

I would like to parse a two dimensional list from the inventory file with ansible playbook
Inventory file: .ini would have a list of macs and IPs
mac1=b8:27:eb:12:53:1b ip1=192.168.8.101
mac2=b8:27:eb:f1:65:32 ip2=192.168.8.102
...
and the ansible task would be to add a line everytime in the `/etc/ethers``file in this form
b8:27:eb:f1:65:32 192.168.8.102
this is the task
- name: Assign static IPs to MACs
lineinfile:
path: /etc/ethers
line: "{{ mac }} {{ ip }}"
mode: 0644
loop: "{{ listname }}"
become: yes
Any recommendations please on how to set my list in the inventory that it will work with the playbook?
Thank you!
I would add the list as a variable in group_var folder or in your playbook.
list:
- mac: b8:27:eb:12:53:1b
ip: 192.168.8.101
- mac: b8:27:eb:f1:65:32
ip: 192.168.8.102
Your task can then look something like this:
- name: Assign static IPs to MACs
lineinfile:
path: /etc/ethers
line: "{{ item.mac }} {{ item.ip }}"
mode: 0644
loop: "{{ list }}"
become: yes

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

Ansible conditionally loop through with_items?

Is it possible to loop through a list of items if a string is defined in a variable i will specify.
Essentially i want to have a list of variables defined and utilized the aws_s3 module to download the files only if they are defined when running the playbook
e.g
say i have the list "var1,var2"
and I have the following variables defined:
apps_location:
- { name: 'vars1', src: 'vars1.tgz', dest: '/tmp/vars1_file.tgz' }
- { name: 'vars2', src: 'vars2.tgz', dest: '/tmp/vars2_file.tgz' }
- { name: 'vars3', src: 'vars3.tgz', dest: '/tmp/vars3_file.tgz' }
Task:
- name: "Splunk Search Head | Download Splunk Apps from S3"
aws_s3:
bucket: "{{ resource_bucket_name }}"
object: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: get
with_items: "{{ apps_location }}"
I want to run the command:
ansible-playbook -i inventory -e "var1,var2"
and download only var1 and var2 on that specific run.
I tried utilizing "lookups" but couldnt get the syntax right. Im not entirely sure if this best way of doing this, but i want to have a predefined list of file locations and only download the ones that i'm passing during runtime.
Note the only reason "name" exists in apps_location is to see if i can do a lookup and only install that one but i couldnt get the syntax right.
Define a variable containing a list of defined apps. I'm trying:
- name: "Set Fact"
set_fact:
dict: "{{ apps_location[item].dest }}"
with_items: "{{ my_vars|default([]) }}"
However whenever I output dict I only get the last value.
Any help would be appreciated :)
The extra-vars must be an assignment of a variable and value. For example
shell> ansible-playbook -i inventory -e "my_vars=['vars1','vars2']"
A more convenient structure of the data would be a dictionary for this purpose. For example
apps_location:
vars1:
src: 'vars1.tgz'
dest: '/tmp/vars1_file.tgz'
vars2:
src: 'vars2.tgz'
dest: '/tmp/vars2_file.tgz'
vars3:
src: 'vars3.tgz'
dest: '/tmp/vars3_file.tgz'
Then the loop might look like
- aws_s3:
bucket: "{{ resource_bucket_name }}"
object: "{{ apps_location[item].src }}"
dest: "{{ apps_location[item].dest }}"
mode: get
loop: "{{ my_vars|default([]) }}"
Q: "Define a variable containing a list of defined apps."
A: Try this
- set_fact:
my_list: "{{ my_list(default([]) +
[apps_location[item].dest] }}"
loop: "{{ my_vars|default([]) }}"
(not tested)

How to set variables for all subsequent hosts in one place without redundant code?

TL/DR: I have "src_host" and "dest_host" variables that I want to use to set the "- hosts:" object in a play. However, I have to set them again for each play under "vars:" of each "- hosts:" section e.g. src_host="{{ hostvars['localhost']['src_host'] }}" how do I set these two variables at the beginning and not have to reset them?
My hosts file looks like this
[wordpress]
localhost ansible_user=user ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
root_localhost ansible_user=root ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
---snip---
server2.net ansible_host="server2.net" ansible_user=user ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
root_server2.net ansible_host="server2.net" ansible_user=root ansible_port=22 ansible_ssh_private_key_file=/home/user/.ssh/id_rsa
The beginning of my playbook looks like this:
- hosts: localhost, server2.net, root_server2.net #always include "localhost" in this list because it is needed to store the variables for the src_host and dest_host
vars:
src_host: localhost #modify these and the host will be changed for all subsequent plays/tasks
dest_host: server2.net #modify these and the host will be changed for all subsequent plays/tasks
src_dump_path: /home/user/cvrt9_dump.sql #set vars for copying file
roles:
- set_facts_for_db_copy
- hosts: "{{ src_host }}"
vars:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
---snip---
roles:
- dump_db
- copy_file
etc . . .
for "- set_facts_for_db_copy" I have "main.yml" as this where I set the "src_host" and "dest_host" variables:
---
# tasks file for set_facts_for_db_copy
- name: create variables that equal src_dump_path and set src_host/dest_host
set_fact:
---snip---
src_host: "{{ src_host }}"
dest_host: "{{ dest_host }}"
So I need to set the "src_host" and "dest_host" for all subsequent "- hosts:" that use them by getting the values from one of the host variables that "set_fact_for_db_copy" set. I randomly picked "localhost" as you may have noticed:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
If I don't have that line there I get:
user#localhost:/home/maintainer/ansible-play$ ansible-playbook -i hosts_tat-kay playbook.yml
PLAY [localhost, server2.net, root_server2.net] **************
TASK [setup] *******************************************************************
ok: [server2.net]
ok: [root_server2.net]
ok: [localhost]
TASK [set_facts_for_db_copy : create variables that equal src_dump_path] *******
ok: [localhost]
ok: [server2.net]
ok: [root_server2.net]
ERROR! the field 'hosts' has an invalid value, which appears to include a variable that is undefined. The error was: 'src_host' is undefined
The error appears to have been in '/home/maintainer/ansible-play/playbook.yml': line 14, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- hosts: "{{ src_host }}"
^ here
. . .
Now I can set the these variables in my host file:
[wordpress:vars]
src_host=localhost
dest_host=server2.net
But then I still have to reference them from the subsquent "-hosts:" objects in my playbook with "{{ hostvars['localhost']['src_host'] }}" etc . . . So my question is how do I get rid of this redundant code in all my subsequent "-hosts:" objects (shown below) while still letting me change the "src_host" and "dest_host" variables once at the beginning and have those changes affect the rest of the plays? Thanks.
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
For this use your inventory file, make a parent group of the host you need the variable as follow.
[desireenv:children]
wordpress
otherhost
etc
and then assigne th vars value to the new parent group created
[desireenv:vars]
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
One solution I found with the help of https://stackoverflow.com/users/4716639/bryan-calvo-benoit is to put this in my hosts file (inventory file)
[wordpress]
localhost
server2.net
[testenv:children]
wordpress
[testenv:vars]
src_host=localhost
dest_host=server2.net
And then in the ansible playbook and the roles that it calls I had to replace
"{{ src_host }}"
with
"{{ hostvars['localhost']['src_host'] }}"
and likewise for "{{ dest_host }}"
However, I could delete this redundant code in my ansible playbook:
src_host: "{{ hostvars['localhost']['src_host'] }}"
dest_host: "{{ hostvars['localhost']['dest_host'] }}"
It would be nice if I didn't have to change the src_host and dest_host to hostvars['localhost']... because it seems arbitrary to use localhost and also what if I want to run several ansible scripts one right after the other with different src_host and dest_host? Using the inventory file locks it down so this is not ideal. If no one else answers I'll accept this answer because it is the only one that works and it technically does what my question asked.

Resources