Search code examples
ansiblebind9

how to make zone config file for bind9 in ansible with a template and read variables from a csv file?


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.


Solution

  • 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