I want to use ansible to generate zone configuration files separately for bind9 dns server with 3 zones by reading data from a csv file
for example, in csv file we have:
hostname ,network1 ,network2 ,network3
server1 ,192.168.101.1 ,172.16.101.1 ,10.185.101.1
server2 ,192.168.101.2 ,172.16.101.2 ,10.185.101.2
<etc...>
I want at the end ansible to generate 3 files named like network1.zone.conf
, network2.zone.conf
, network3.zone.conf
.
I have no idea how to do that.
I tried:
- name: read from csv file
read_csv:
unique: false
path: "{{ CSV_File_Path }}"
delegate_to: localhost
register: OUTPUT_CSV
- name: print output
template:
src: zones.rfc.j2 # template path
dest: "/path/to/create/files/{{item}}.txt"
delegate to: dns_server
loop: "{{ OUTPUT_CSV.list }}"
loop_control:
extended: true
label: "{{ ansible_loop.index0 }}"
This made a bunch of files with whole list in the name of the files.
Demo file structure:
$ tree
.
├── inventory.yml
├── playbook.yml
├── templates
│ └── zones.rfc.j2
└── zones.csv
1 directory, 4 files
I made a fake inventory.yml
for the occasion which will play everything on localhost:
---
dnsservers:
hosts:
dnsserver1:
ansible_connection: local
ansible_python_interpreter: /usr/bin/python3
I fixed your zones.csv
so that it does not contain parasite space we later have to trim:
hostname,network1,network2,network3
server1,192.168.101.1,172.16.101.1,10.185.101.1
server2,192.168.101.2,172.16.101.2,10.185.101.2
Since it is not part of your question, I created what should more or less look like your template in templates/zones.rfc.j2
$TTL 604800 ; 1 week
{{ network }}.local IN SOA ns0.local. ns1.local. (
11278 ; serial
10800 ; refresh (3 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
38400 ; minimum (10 hours 40 minutes)
)
NS ns0.local.
NS ns1.local.
$ORIGIN {{ network }}.local
$TTL 3600 ; 1 hour
{% for server in server_list %}
{{ server.hostname }} A {{ server[network] }}
{% endfor %}
We can now put all this together with a playbook. The difficulty here is that reading the csv gives a list element for each line in your csv while we want to create a file for each networks given as columns.
If we read the csv into output.csv
then we can get output_csv.list[0]
which is a dictionary:
{
"hostname": "server1",
"network1": "192.168.101.1",
"network2": "172.16.101.1",
"network3": "10.185.101.1"
}
If we extract the key names and remove the hostname
key, we have our list of networks. Hence output_csv.list[0].keys() | difference(['hosname'])
gives:
[
"network1",
"network2",
"network3"
]
The below playbook uses that technique with an alias in the task (i.e. output_csv.list
=> server_list
) to match the variables used to make the template more readable.
- name: Make bind zone files from csv
hosts: dnsservers
gather_facts: false
vars:
csv_file_path: zones.csv
zone_output_dir: /tmp/test_bind_zones/{{ inventory_hostname }}/
tasks:
- name: Read zone info from csv file
community.general.read_csv:
unique: false
path: "{{ csv_file_path }}"
delegate_to: localhost
run_once: true
register: output_csv
- name: Make sure our output dir exists
ansible.builtin.file:
path: "{{ zone_output_dir }}"
state: directory
- name: Push result files in output dir
vars:
server_list: "{{ output_csv.list }}"
ansible.builtin.template:
src: zones.rfc.j2
dest: "{{ zone_output_dir }}/{{ network }}.zone.conf"
loop: "{{ server_list[0].keys() | difference(['hostname']) }}"
loop_control:
loop_var: network
Running the playbook gives:
$ ansible-playbook -i inventory.yml playbook.yml
PLAY [Make bind zone files from csv] ****************************************************************************************************************************************************************************************
TASK [Read zone info from csv file] *****************************************************************************************************************************************************************************************
ok: [dnsserver1 -> localhost]
TASK [Make sure our output dir exists] **************************************************************************************************************************************************************************************
changed: [dnsserver1]
TASK [Push result files in output dir] **************************************************************************************************************************************************************************************
changed: [dnsserver1] => (item=network1)
changed: [dnsserver1] => (item=network2)
changed: [dnsserver1] => (item=network3)
PLAY RECAP ******************************************************************************************************************************************************************************************************************
dnsserver1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
We can check all files have been created in the target dir:
$ ls -l /tmp/test_bind_zones/dnsserver1/
total 15
-rw-r--r-- 1 user users 369 nov 19 15:58 network1.zone.conf
-rw-r--r-- 1 user users 367 nov 19 15:58 network2.zone.conf
-rw-r--r-- 1 user users 367 nov 19 15:58 network3.zone.conf
I'll only show result from the first one here as a proof, you can check the others testing on your own environment:
$ cat /tmp/test_bind_zones/dnsserver1/network1.zone.conf
$TTL 604800 ; 1 week
network1.local IN SOA ns0.local. ns1.local. (
11278 ; serial
10800 ; refresh (3 hours)
3600 ; retry (1 hour)
604800 ; expire (1 week)
38400 ; minimum (10 hours 40 minutes)
)
NS ns0.local.
NS ns1.local.
$ORIGIN network1.local
$TTL 3600 ; 1 hour
server1 A 192.168.101.1
server2 A 192.168.101.2