Adding a value to a template from a variable - ansible

I have a variable test
test:
- 10.0.0.1
- 10.0.0.2
- 10.0.0.3
- 10.0.0.4
And there is a config file where you need to insert one value from this variable. There should be 4 config files in the output, each file contains one line from this test variable. I figured out how to make 4 files be created with ip names from the list, but I can't understand how to make ip substituted into the template.
From template:
auto br_omgt
iface br_omgt inet static
bridge_ports bond0.2210
address {{ db }}
netmask 255.255.255.192
gateway 10.15.35.62

You have to use the module template.
Your file must be jinja2.
role/MY_Role/templates/my_file.txt.j2
my_file.txt.j2
auto br_omgt iface br_omgt inet static bridge_ports bond0.2210 address {{ db }} netmask 255.255.255.192 gateway 10.15.35.62
my_file.txt
auto br_omgt iface br_omgt inet static bridge_ports bond0.2210 address 10.0.0.1 netmask 255.255.255.192 gateway 10.15.35.62
Playbook :
vars:
db: 10.0.0.1
tasks:
- name: Use Template
template:
src: my_file.txt.j2
dest: home/My_User/my_file.txt
With a loop, you got 4 files. ( Extended )
vars:
db: 10.0.0.1
10.0.0.2
tasks:
- name: Use Template looped
template:
src: my_file.txt.j2
dest: home/My_User/my_file_{{index}}.txt
loop_control:
extended: yes
loop: "{{ db }}"

Related

populate yaml variable from jinja variable

I want to populate the nifi.web.https.host below after I run the below playbook. I am new to ansible jinja/yaml so I am not sure why is not working.
{% set external_ip = 'curl 169.254.169.254/2009-04-04/meta-data/public-ipv4' %}
- hosts: localhost
become: yes
roles:
- my.nifi
vars:
nifi_properties:
# HTTPS properties
nifi.web.https.host: 'external_ip'
nifi.web.https.port: 8443
nifi.web.https.network.interface.default: eth0
After I run the playbook I get this error:
{% set external_ip = 'curl 169.254.169.254/2009-04-04/meta-data/public-ipv4' %}
^ here
- hosts: localhost
become: yes
roles:
- my.nifi
pre_tasks:
- name: Get EC2 public IP
raw: curl http://169.254.169.254/2009-04-04/meta-data/public-ipv4
register: ec2_public_ip
vars:
nifi_properties:
# HTTPS properties
nifi.web.https.host: "{{ ec2_public_ip.stdout }}"
nifi.web.https.port: 8443
nifi.web.https.network.interface.default: eth0

Ansible parse_cli build object from config lines in arbitrary order

I'm trying to parse a DHCP scope from a range of Cisco IOS routers using Ansible.
< TL;DR; > I'm currently using parse_cli with a regex including multiple named matches for the same thing in different positions (e.g. domain_name_1, domain_name_2) then combining them in the output object by assigning them as domain_name: "{{ item.domain_name_1 }}{{ item.domain_name_2 }}" - as only one of them at a time will be populated, the output object still contains the correct data. I'd like to find a way to do it which doesn't require ugly inflexible hacks like that. < /TL;DR; >
I like the parse_cli Jinja filter as it provides a nice object as the output containing only the info I need, but I can't work out a way to have it cope with an arbitrary order of commands in the config.
Three examples of the same config in different order are as follows. Order of config lines can vary depending on which firmware version the router is running, the order the commands were entered in, or the model of router. The commands themselves (in this case) will always be the same so I just need a way to pull out each line and parse it appropriately.
Cisco 867
ip dhcp pool Data
import all
network 192.168.1.0 255.255.255.0
update dns
default-router 192.168.1.1
dns-server 192.168.1.1 8.8.8.8
option 42 ip 192.168.1.1
domain-name test.local
lease 8
Cisco 881
ip dhcp pool Data
import all
network 192.168.1.0 255.255.255.0
option 42 ip 192.168.1.1
default-router 192.168.1.1
dns-server 192.168.1.1 8.8.8.8
domain-name test.local
lease 8
Cisco 1111
ip dhcp pool Data
import all
network 192.168.1.0 255.255.255.0
default-router 192.168.1.1
dns-server 192.168.1.1 8.8.8.8
domain-name test.local
option 42 ip 192.168.1.1
lease 8
In all of those cases or any other new order which appears in future, I want to create an object in ansible as a fact with format:
{
"network": "192.168.1.0",
"dns-servers": ["192.168.1.1", "8.8.8.8"],
"domain-name": "test.local",
"options": {
"42": {
"type": "ip",
"value": "192.168.1.1"
}
}
}
Currently I'm doing something like this:
tasks/main.yml
- name: Get Cisco IOS DHCP pools
ios_command:
commands:
- 'show running-config | s ip dhcp pool'
retries: 1
register: ciscoios_dhcp_pool_output
- name: Convert Cisco IOS DHCP pools to list
delegate_to: 127.0.0.1
set_fact:
info_dhcp_pools: "{{ ciscoios_dhcp_pool_output.stdout[0] | parse_cli('roles/get-router-dhcp-pools/parsers/ios-pools.yml') }}"
parsers/ios-pools.yml
---
vars:
dhcp_pool:
name: "{{ item.name }}"
network: "{{ item.network_ip }}"
subnet: "{{ item.network_subnet }}"
dns_servers: "{{ item.dns_servers_1 }}{{ item.dns_servers_2 }}"
domain_name: "{{ item.domain_name_0 }}{{ item.domain_name_1 }}{{ item.domain_name_2 }}{{ item.domain_name_3 }}"
options: "{{ item.options_1 }}{{ item.options_2 }}"
lease_days: "{{ item.lease_days }}"
lease_hours: "{{ item.lease_hours }}"
lease_minutes: "{{ item.lease_minutes }}"
keys:
dhcp_pools:
value: "{{ dhcp_pool }}"
items: "^ip dhcp pool (?P<name>[^\\n]+)\\s+(?:import (?P<import_all>all)\\s*)?(?:network (?P<network_ip>[\\d.]+) (?P<network_subnet>[\\d.]+)?\\s*)?(?:update dns\\s*)?(?:host (?P<host_ip>[\\d.]+) (?P<host_subnet>[\\d.]+)\\s*)?(?:domain-name (?P<domain_name_0>[\\w._-]+)\\s+)?(?:default-router (?P<default_router>[\\d.]+)\\s*)?(?:dns-server (?P<dns_servers_1>(?:[\\d.]+ ?)+ ?)+\\s*)?(?:domain-name (?P<domain_name_1>[\\w._-]+)\\s+)?(?P<options_1>(?:option [^\\n]+\\n*\\s*)*)?(?:domain-name (?P<domain_name_2>[\\w._-]+)\\s+)?(?P<options_2>(?:option [^\\n]+\\n*\\s*)*)?(?:dns-server (?P<dns_servers_2>(?:[\\d.]+ ?)+ ?)+\\s*)?(?:domain-name (?P<domain_name_3>[\\w._-]+)\\s*)?(lease (?P<lease_days>\\d+)(?: (?P<lease_hours>\\d+))?(?: (?P<lease_minutes>\\d+))?\\s*)?(?:update arp)?"
That however feels very messy, and requires updates every time I discover a new order that config lines can appear in. It feels like the sort of thing that there should be a better way to achieve but I'm not sure on what it is.

Gather a List of Configured IP Addresses

I want to pull out the lines of a Juniper config that assign an IPv6 address to an interface and save that output to a file.
The output I am after is generated with the command 'show configuration | display set| match "inet6 address" '
I'm building an Ansible playbook and have pinballed off of errors to end up with the below task. It is basically giving me the complete interface configs, and I just want to narrow it down to the lines that would fit the "match" line in the manual command. The commented out filters aren't working and I can't find documentation that explains filters in a way that I understand.
- name: "Get selected configuration hierarchies"
juniper_junos_config:
host: "{{ ansible_host }}"
retrieve: "committed"
config_mode : "private"
filter: "<configuration><interfaces/></configuration>"
#filter: "<configuration><interfaces/><family/><inet6/><address/></configuration>"
#filter: "inet6/address"
format: "set"
options:
inherit: "inherit"
dest: "{{ inventory_hostname }}-inet6-config"
register: response
- name: "Print result"
debug:
var: response
Output:
ok: [LAB-QFX5110-1] => {
"response": {
"changed": false,
"config": "\nset interfaces xe-0/0/0 apply-groups-except jumbo-frames\nset interfaces xe-0/0/0 description \"Test Laptop - DMZ;000\"\nset interfaces xe-0/0/0 gigether-options 802.3ad ae12\n<SNIP>\nset interfaces lo0 unit 10 family inet address 100.126.0.x/32\nset interfaces lo0 unit 10 family inet6 address ABCD:EF00:0000:01c4::1/128\n<SNIP>/n",
"config_lines": [
"",
"set interfaces xe-0/0/0 apply-groups-except jumbo-frames",
"set interfaces xe-0/0/0 description \"Test Laptop - DMZ;000\"",
"set interfaces xe-0/0/0 gigether-options 802.3ad ae12",
"<SNIP>",
"set interfaces lo0 unit 10 family inet address 100.126.0.x/32",
"set interfaces lo0 unit 10 family inet6 address ABCD:EF00:0000:01c4::1/128",
"<SNIP>",
],
"failed": false,
"msg": "Configuration has been: opened, retrieved, closed."
}
}
I just want the lines that read:
set interfaces unit X family inet6 address XXXX:YYYY:ZZZZ:1234::1/127
But I can't seem to plug in the correct filter.
I will also mention that if there is a better way to gather this, I am open to exploring it. It just seems like this is the task Ansible was created to perform.
here is how to do it. since your response dictionary contains the output split by lines, we will use the config_lines key, process line by line:
code:
---
- hosts: localhost
gather_facts: false
vars:
response:
changed: false
config: |2-
set interfaces xe-0/0/0 apply-groups-except jumbo-frames
set interfaces xe-0/0/0 description "Test Laptop - DMZ;000"
set interfaces xe-0/0/0 gigether-options 802.3ad ae12
<SNIP>
set interfaces lo0 unit 10 family inet address 100.126.0.x/32
set interfaces lo0 unit 10 family inet6 address ABCD:EF00:0000:01c4::1/128
<SNIP>/n
config_lines:
- ''
- set interfaces xe-0/0/0 apply-groups-except jumbo-frames
- set interfaces xe-0/0/0 description "Test Laptop - DMZ;000"
- set interfaces xe-0/0/0 gigether-options 802.3ad ae12
- "<SNIP>"
- set interfaces lo0 unit 10 family inet address 100.126.0.x/32
- set interfaces lo0 unit 10 family inet6 address ABCD:EF00:0000:01c4::1/128
- "<SNIP>"
failed: false
msg: 'Configuration has been: opened, retrieved, closed.'
tasks:
- name: find entries containing inet6 address, add to results
set_fact:
interfaces: "{{ interfaces | default([]) + [item] }}"
when: item is search('inet6 address')
with_items:
- "{{ response.config_lines }}"
- debug:
var: interfaces
- name: save results to file
template:
src: results.j2
dest: /tmp/results.out
you will need a jinja2 template for the last task to work (under same dir as your playbook), with contents:
results.j2:
# processing results:
{% for interface in interfaces -%}
{{ interface }}
{%- endfor %}
1st task parses each line and if the when condition is met, its adding the given line to a results , the interfaces list. 2nd task prints that variable. 3rd task saves the results to a file under /tmp/.
hope it helps

get child elements of a dictonairy

I have a network with different hosts and different networks and I want to use ansible to ping each host in each network.
In my host_vars directory all informations about every host and its network are stored in its own file. A hostfile looks like this:
bond:
- conn_name: net1
ip4: '10.10.44.21'
gw4: '10.10.44.1'
- conn_name: net2
ip4: '10.10.47.21'
gw4: '10.10.47.1'
- conn_name: net3
ip4: '10.10.46.21'
gw4: '10.10.46.1'
bond_slave:
- conn_name: eno1
ifname: eno1
master: net1
- conn_name: eno2
ifname: eno2
master: net2
- conn_name: ens2f1
ifname: ens2f1
master: net2
- conn_name: ens2f0
ifname: ens2f0
master: net2
- conn_name: ens4f1
ifname: ens4f1
master: net3
- conn_name: ens4f0
ifname: ens4f0
master: net3
My purpose is to get every ip4-value and ping it from every host. what i can do is include all files from host_vars directory and ping the ip-adress of the first bond item of each file.
---
- hosts: all
- name: Include information about all hosts
include_vars: ../../host_vars/{{ item }}.yaml
with_items: hosts
register: "r"
- name: execute ping
command: ping {{ bond[0].ip4 }} -c 3
with_items: "{{ r.results }}"
But the bond can have from 2 to 4 networks. How do i loop through each bond to access all ip-address?
Solution
Even though the snippet of Konstantin Suvorov wasn't working for me, it helped me to find the right solution:
- name: execute ping
command: ping {{ item.ip4 }} -c 3
with_items: "{{ r.results | map(attribute='ansible_facts.bond') | list }}"
If you have bond var defined for each host via host_vars, you can use hostvars magic variable (no need to do this ugly include task):
- name: ping everything
command: ping {{ item }} -c 3
with_items: "{{ play_hosts | map('extract',hostvars,'bond') | list }}"
Actually this Jinja2 expression will return a list of lists, but with_items flattends them, so you end up with long list all elements from every bond of every host in current play.

Ansible loop over variables

i am using ansible to update configuration file of newly added NIC
for that i have defined some variables in separate yml file
/tmp/ip.yml
#first interface
interface1: eth1
bootproto1: static
ipaddress1: 192.168.211.249
netmask1: 255.255.255.0
gateway: 192.168.211.2
DNS1: 192.168.211.2
#second interface
interface2: eth2
bootproto2: static
ipaddress2: 10.0.0.100
netmask2: 255.0.0.0
Playbook
- include_vars: /tmp/ip.yml
- name: configuring interface
lineinfile:
state=present
create=yes
dest=/etc/sysconfig/network-scripts/ifcfg-{{interface1}}
regexp="{{ item.regexp }}"
line="{{ item.line }}"
with_items:
- { regexp: '^BOOTPROTO=.*', line: 'BOOTPROTO={{interface1}}' }
- { regexp: '^IPADDR=.*', line: 'IPADDR={{ipaddress1}' }
- { regexp: '^NETMASK=.*', line: 'NETMASK={{netmask1}}' }
- { regexp: '^GATEWAY=.*', line: 'GATEWAY={{gateway}}' }
- { regexp: '^PEERDNS=.*', line: 'PEERDNS=no' }
- { regexp: '^DNS1=.*', line: 'DNS1={{DNS1}}' }
- { regexp: '^ONBOOT=.*', line: 'ONBOOT={{onboot}}' }
when: bootproto1 == 'static'
- name: configuring for DHCP
lineinfile:
state=present
create=yes
dest=/etc/sysconfig/network-scripts/ifcfg-{{interface1}}
regexp="{{ item.regexp }}"
line="{{ item.line }}"
with_items:
- { regexp: '^BOOTPROTO=.*',line: 'BOOTPROTO={{bootproto1}}' }
- {regexp: '^PEERDNS=.*',line: 'PEERDNS=yes' }
- { regexp: '^ONBOOT=.*', line: 'ONBOOT={{onboot}}' }
when: bootproto1 == 'dhcp'
similarly repeated for second interface.
Even Though this method works for 2 NIC,this is too difficult to manage ,that is for each new NIC added i need to modify playbook and update corresponding variable in /tmp/ip.yml.
Is there a way to add variables to /tmp/ip.yml and may be using some separator parse it to playbook with out modifying playbook each time for plugging in new NIC.
There is a lot to say here.
First, try to avoid lineinfile like plague. It is really a last-resort solution. lineinfile makes it hard to write consistent and idempotents playbooks.
Now, since you're trying to populate RH style interface files, it is quite easy to do.
Organize your variables
The first thing to do is to have a proper structure for your variables. You'll want to loop over your interfaces so you have to make stuff 'loopable'. Having interface1, interface2 ... interfaceN is not scalable as you mentioned.
Here is a suggestion :
interfaces_ipv4:
- name: eth0
bootproto: static
ipaddress: 192.168.211.249
netmask: 255.255.255.0
gateway: 192.168.211.2
dns: 192.168.211.2
- name: eth2
bootproto: static
ipaddress: 10.0.0.100
netmask: 255.0.0.0
Write your template
Now that you have your data, you need a template to create your OS config file.
BOOTPROTO={{item.bootproto}}
IPADDR={{item.ipaddress}}
NETMASK={{item.netmask}}
{% if item.gateway is defined %}
GATEWAY={{item.gateway}}
{% endif %}
PEERDNS=no
DNS1={{item.dns}}
ONBOOT={{item.onboot|default('no')}}
I included two variations : you can skip outputting a line when it's not set ({% if ... %} construct) or provide default values (for instance {{item.onboot|default('no')}}).
Your mileage may vay, depending if you want to use a default or to skip with the if construct.
Create a task
Finally, here is a task that will create interface configuration files for each interface :
- name: Push template
template:
src=/path/to/the/above/template.j2
dest=/etc/sysconfig/network-scripts/ifcfg-{{item.name}}.cfg
with_items:
- "{{ interfaces_ipv4 }}"
This should do it all.
Of course, best way to use this task is to add it to some "network" role, and call it from a playbook.
Good luck.

Resources