How to convert multiple lines string to dict in Ansible? - ansible

I set a fact in Ansible using environment variables with query('env', 'VARIABLE')
My VARIABLE is multiline string (in YAML format):
device: eth0
bootproto: static
address: 192.168.x.x
netmask: 255.255.255.0
gateway: 192.168.x.x
When I print the VARIABLE with Ansible, I get it as a single string with \n between lines
"msg": ["device: eth0\nbootproto: static\naddress:
192.168.x.x\nnetmask: 255.255.255.0\ngateway: 192.168.x.x"]
Is there a convenient way to convert it into dict? I need to use it later in my task, to load the parameters when configuring a machine's NIC.
I have tried to use the Jinja2 filter - debug: msg="{{ network_settings | from_yaml }}" with no success.

There is an important note in the docs:
The difference between lookup and query is largely that query will always return a list.
So:
either replace query('env', 'VARIABLE') with lookup('env', 'VARIABLE'):
- debug:
msg: "{{ lookup('env', 'VARIABLE') | from_yaml }}"
or process the list accordingly (the contents will be in the first-and-only element):
- debug:
msg: "{{ query('env', 'VARIABLE') | first | from_yaml }}"

Related

How do I format each string in a list of strings in ansible

I'm trying to write a json file with a list of ip addresses formatted as "http://{ip_address}:{port}" as a part of an ansible playbook. I'm able to get `"{ip_address}:port" with the following code:
vars:
config:
addresses: "{{ groups['other']
| map('extract', hostvars)
| map(attribute='ansible_host')
| product([':' ~ other_port]) | map('join')
}}"
I tried using the format filter but it expects the input to be the format pattern not a variable to insert into a format.
If I was allowed to execute arbitrary python would express what I want as:
vars:
config:
addresses: "{{ [ f'http://{host.hostvars.ansible_host}:{port}' for host in groups['other'] }}"
but it's all got to be in jinja2 filters. I feel like being able to even tack on the port number was a fluke. Is there a way to express this in jinja? Right now I'm looking at hard-coding the format into the hosts file.
For example, given the inventory
shell> cat hosts
[other]
test_11 ansible_host=10.1.0.61
test_12 ansible_host=10.1.0.62
test_13 ansible_host=10.1.0.63
The playbook below
- hosts: localhost
vars:
other_port: 80
addresses: "{{ groups.other|
map('extract', hostvars, 'ansible_host')|
map('regex_replace', '^(.*)$', addresses_replace)|
list }}"
addresses_replace: 'http://\1:{{ other_port }}'
tasks:
- debug:
var: addresses
gives (abridged)
addresses:
- http://10.1.0.61:80
- http://10.1.0.62:80
- http://10.1.0.63:80

access a variable in a variable

I am 90% sure this doesn't work because i'm doing it the wrong way, but i can't figure out what is the "right way",I hope you can get my point :
I am trying to access an ipv4 of a certain interface,I have in my hosts file interface_lan = enp4s0 because i need it in a role,so i thought i might just use it to have the IP address of that interface :
"{{hostvars[inventory_hostname]['ansible_{{interface_lan}']['ipv4.address']}}"
with that command,he is looking for "ansible_{{interface_lan}}" but i want him to look for
"ansible_"{{interface_lan}}"" and to consider "{{interface_lan}}" as a variable,not as a string.
I tried my best to explain,sorry if you did not understand you are free to enjoy the rest of your day without helping me, i have been ignoring this line for a few days now.
thank you !
Concatenate the name of the attribute an use it in the index. For example
- hosts: localhost
gather_facts: false
vars:
interface_lan: enp4s0
ansible_enp4s0:
ipv4:
address: 10.1.0.10
tasks:
- set_fact:
ansible_enp4s0: "{{ ansible_enp4s0 }}"
- debug:
msg: "{{ hostvars[inventory_hostname][my_ifc]['ipv4']['address'] }}"
vars:
my_ifc: "{{ 'ansible_' ~ interface_lan }}"
gives (abridged)
msg: 10.1.0.10
Note: set_fact is needed in the example to put the dictionary ansible_enp4s0 into the hostvars.
The indirect addressing of variables without hostvars is possible with the lookup plugin vars. For example
- hosts: localhost
vars:
test_eth0: 10.1.0.10
test_eth1: 10.1.0.11
tasks:
- debug:
msg: "{{ item }}: {{ lookup('vars', 'test_' ~ item ) }}"
loop:
- eth0
- eth1
gives (abridged)
msg: 'eth0: 10.1.0.10'
msg: 'eth1: 10.1.0.11'

Filter hostvar by special propery

I have host.yml like this
---
all:
hosts:
server-a:
server_dc: "Hetzner"
ansible_host: 192.168.1.1
server-b:
server_dc: "OVH"
ansible_host: 192.168.1.2
And play book debug is:
- name: sample
debug:
var: hostvars
And all hostvars debug success.
How to get same hostvars variable but filtered. Any of that server_dc is equal OVH
I dont want to iterate for template, i just one new filtered variable that contain all other properies.
I need another variable that i debug see this output:
['server-b']
This I believe meets your requirement (removing 'no_log: true', will result in the complete dictionary being printed in your playbook output):
- set_fact:
filtered_hosts: "{{ filtered_hosts | default({}) | combine({item.key: item.value}) }}"
when: "item.value.server_dc == 'OVH'"
with_dict: "{{ hostvars }}"
no_log: true
- debug:
var: filtered_hosts

Ansible to get the netmask in CIDR

In order to get the value "24" corresponding to an IP address/networkmask, I have this working piece of code:
- set_fact:
ip: "{{ ansible_default_ipv4.address }}/{{ansible_default_ipv4.netmask }}"
- set_fact:
mask_cidr: "{{ ip | ipaddr('prefix') }}"
Where ansible_default_ipv4.address = 172.16.1.67 and ansible_default_ipv4.netmask is 255.255.255.0 as per gather_facts or setup module.
I've tried different things to make this code "smarter" so I only need to set 1 fact instead of 2, but my filtering abilities are not strong.
Any ideas in how to convert these two facts in a smarter fact that do both things?
Easiest and the most clean way is through a helper variable:
- set_fact:
mask_cidr: "{{ ip | ipaddr('prefix') }}"
vars:
ip: "{{ ansible_default_ipv4.address }}/{{ansible_default_ipv4.netmask }}"
If you insist on writing a single template:
- sef_fact:
ip: "{{ (ansible_default_ipv4.address + '/' + ansible_default_ipv4.netmask) | ipaddr('prefix') }}"
I'm using this:
mynetwork = {{ (ansible_default_ipv4.network + '/' + ansible_default_ipv4.netmask) | ipaddr('network/prefix') }}

Ansible parses strings as lists if the format is compatible, how to escape?

Using ansible, I need to put a list of hosts in line in a file like so:
["127.0.0.1", "127.0.0.2", "127.0.0.3"]
But whenever I achieve this format, ansible interprets it as a list and the content of the file is this pythonic version:
['127.0.0.1', '127.0.0.2', '127.0.0.3']
Here's my attempts to get it out thus far:
---
- hosts: all
gather_facts: False
tasks:
- set_fact:
myhosts:
- 127.0.0.1
- 127.0.0.2
- 127.0.0.3
# This comes out as a list, I need a string
- set_fact:
var: "[ \"{{ myhosts | join('\", \"')}}\" ]"
- debug: var=var
# This comes out as a string, but I need no underscore on it
- set_fact:
var: "_[ \"{{ myhosts | join('\", \"')}}\" ]"
- debug: var=var
# This also comes out as a list
- set_fact:
var: >
[ "{{ myhosts | join('", "')}}" ]
- debug: var=var
# Also parsed as a list
- set_fact:
var: "{{ myhosts | to_json }}"
- debug: var=var
# ansible-playbook -i "localhost," this_file.yml
There are some filters that prevent Ansible template engine from doing string evaluation.
This list of filters is stored in STRING_TYPE_FILTERS setting.
In Ansible 2.1 it contains: string, to_json, to_nice_json, to_yaml, ppretty, json.
So, you can do this:
- lineinfile: line="{{ myhosts | to_json }}" dest=output.txt
This will add ["127.0.0.1", "127.0.0.2", "127.0.0.3"] line to the file.
And don't believe debug's output when dealing with exact string formatting.
Always use copy: content="{{ string_output_to_test | string }}" dest=test.txt and check file contents to be sure.
debug: var=myvar will always template with evaluation, so your string will always be printed as a list.
debug: msg="{{ myvar | string }}" will print myvar as JSON encoded string.

Resources