Repository: cobbler/cobbler

Issue:
Cobbler sync: DNS does not work
### Describe the bug

<!--- A clear and concise description of what the bug is. -->

<!--- HINT: You can paste gist.github.com links for long logs or larger files -->

### Steps to reproduce

1. Set `manage_dns` to true in `/etc/cobbler/settings.yaml`
2. Restart cobblerd
3. Add distro, profile, system
4. Do a `cobbler sync` (must be done at least once before using `cobbler sync --dns`)

### Expected behavior

`cobbler sync` and `cobbler sync --dns` should work.

### Cobbler version

#2684

### Operating system

openSUSE Tumbleweed

### Cobbler log

<!--- Paste (partial) output from `/var/log/cobbler/cobbler.log` -->
````bash
(...)
[2021-07-15_094204_sync] 2021-07-15T09:42:05 - DEBUG | done with get_items; ['distro']
[2021-07-15_094204_sync] 2021-07-15T09:42:05 - INFO | rendering DNS files
[2021-07-15_094204_sync] 2021-07-15T09:42:05 - INFO | Exception occurred: <class 'TypeError'>
[2021-07-15_094204_sync] 2021-07-15T09:42:05 - INFO | Exception value: 'NetworkInterface' object is not subscriptable
[2021-07-15_094204_sync] 2021-07-15T09:42:05 - INFO | Exception Info:
  File "/usr/local/lib/python3.8/site-packages/cobbler/remote.py", line 96, in run
    rc = self._run(self)

  File "/usr/local/lib/python3.8/site-packages/cobbler/remote.py", line 212, in runner
    self.remote.api.sync(self.options.get("verbose", False), what=what)

  File "/usr/local/lib/python3.8/site-packages/cobbler/api.py", line 1379, in sync
    sync_obj.run()

  File "/usr/local/lib/python3.8/site-packages/cobbler/actions/sync.py", line 164, in run
    self.dns.write_configs()

  File "/usr/local/lib/python3.8/site-packages/cobbler/modules/managers/bind.py", line 604, in write_configs
    self.__write_named_conf()

  File "/usr/local/lib/python3.8/site-packages/cobbler/modules/managers/bind.py", line 270, in __write_named_conf
    metadata = {'forward_zones': list(self.__forward_zones().keys()),

  File "/usr/local/lib/python3.8/site-packages/cobbler/modules/managers/bind.py", line 119, in __forward_zones
    host = interface["dns_name"]

[2021-07-15_094204_sync] 2021-07-15T09:42:05 - ERROR | ### TASK FAILED ###
````

### Additional information

```bash
$ cobbler sync      
task started: 2021-07-15_094204_sync
task started (id=Sync, time=Thu Jul 15 09:42:04 2021)
running python triggers from /var/lib/cobbler/triggers/task/sync/pre/*
running shell triggers from /var/lib/cobbler/triggers/task/sync/pre/*
shell triggers finished successfully
syncing all
running pre-sync triggers
running python triggers from /var/lib/cobbler/triggers/sync/pre/*
running shell triggers from /var/lib/cobbler/triggers/sync/pre/*
shell triggers finished successfully
cleaning trees
removing: /srv/www/cobbler/images/dtest
removing: /srv/tftpboot/pxelinux.cfg/stest
removing: /srv/tftpboot/pxelinux.cfg/default
removing: /srv/tftpboot/grub/images
removing: /srv/tftpboot/grub/system
removing: /srv/tftpboot/grub/system_link
removing: /srv/tftpboot/grub/grub.cfg
removing: /srv/tftpboot/grub/local_efi.cfg
removing: /srv/tftpboot/grub/local_legacy.cfg
removing: /srv/tftpboot/grub/local_powerpc-ieee1275.cfg
removing: /srv/tftpboot/images/dtest
removing: /srv/tftpboot/ipxe/default.ipxe
copying bootloaders
running: ['rsync', '-rpt', '--copy-links', '--exclude=.cobbler_postun_cleanup', '/var/lib/cobbler/loaders/', '/srv/tftpboot']
received on stdout: 
received on stderr: 
running: ['rsync', '-rpt', '--copy-links', '--exclude=README.grubconfig', '/var/lib/cobbler/grub_config/', '/srv/tftpboot']
received on stdout: 
received on stderr: 
copying distros to tftpboot
copying files for distro: dtest
mkdir: /srv/tftpboot/images/dtest
trying hardlink /root/vmlinuz -> /srv/tftpboot/images/dtest/vmlinuz
trying cachelink /root/vmlinuz -> /srv/tftpboot/images/.link_cache/da39a3ee5e6b4b0d3255bfef95601890afd80709 -> /srv/tftpboot/images/dtest/vmlinuz
trying hardlink /root/initrd -> /srv/tftpboot/images/dtest/initrd
trying cachelink /root/initrd -> /srv/tftpboot/images/.link_cache/da39a3ee5e6b4b0d3255bfef95601890afd80709 -> /srv/tftpboot/images/dtest/initrd
copying images
generating PXE configuration files
(...)
generating: /srv/tftpboot/pxelinux.cfg/stest
(...)
generating: /srv/tftpboot/grub/system/stest
removing: /srv/tftpboot/grub/system_link/stest
mkdir: /srv/tftpboot/grub/system_link
generating PXE menu structure
(...)
mkdir: /srv/tftpboot/pxelinux.cfg
mkdir: /srv/tftpboot/ipxe
(...)
mkdir: /srv/www/cobbler/images/dtest
trying hardlink /root/vmlinuz -> /srv/www/cobbler/images/dtest/vmlinuz
trying symlink /root/vmlinuz -> /srv/www/cobbler/images/dtest/vmlinuz
trying hardlink /root/initrd -> /srv/www/cobbler/images/dtest/initrd
trying symlink /root/initrd -> /srv/www/cobbler/images/dtest/initrd
Writing template files for dtest
(...)
rendering DNS files
Exception occurred: <class 'TypeError'>
Exception value: 'NetworkInterface' object is not subscriptable
Exception Info:
!!! TASK FAILED !!!
```



Discussion:

Resolving patch:
diff --git a/cobbler/modules/managers/bind.py b/cobbler/modules/managers/bind.py
--- a/cobbler/modules/managers/bind.py
+++ b/cobbler/modules/managers/bind.py
@@ -115,11 +115,11 @@ def __forward_zones(self):
             zones[zone] = {}
 
         for system in self.systems:
-            for (name, interface) in list(system.interfaces.items()):
-                host = interface["dns_name"]
-                ip = interface["ip_address"]
-                ipv6 = interface["ipv6_address"]
-                ipv6_sec_addrs = interface["ipv6_secondaries"]
+            for (name, interface) in system.interfaces.items():
+                host = interface.dns_name
+                ip = interface.ip_address
+                ipv6 = interface.ipv6_address
+                ipv6_sec_addrs = interface.ipv6_secondaries
                 if not system.is_management_supported(cidr_ok=False):
                     continue
                 if not host:
@@ -134,7 +134,7 @@ def __forward_zones(self):
                 # - b.c.d.e
                 # then a.b.c.d.e should go in b.c.d.e
                 best_match = ''
-                for zone in list(zones.keys()):
+                for zone in zones.keys():
                     if re.search(r'\.%s$' % zone, host) and len(zone) > len(best_match):
                         best_match = zone
 
@@ -206,11 +206,11 @@ def __reverse_zones(self):
             zones[zone] = {}
 
         for system in self.systems:
-            for (name, interface) in list(system.interfaces.items()):
-                host = interface["dns_name"]
-                ip = interface["ip_address"]
-                ipv6 = interface["ipv6_address"]
-                ipv6_sec_addrs = interface["ipv6_secondaries"]
+            for (name, interface) in system.interfaces.items():
+                host = interface.dns_name
+                ip = interface.ip_address
+                ipv6 = interface.ipv6_address
+                ipv6_sec_addrs = interface.ipv6_secondaries
                 if not system.is_management_supported(cidr_ok=False):
                     continue
                 if not host or ((not ip) and (not ipv6)):
@@ -224,7 +224,7 @@ def __reverse_zones(self):
                     # - 1.2.3
                     # then 1.2.3.4 should go in 1.2.3
                     best_match = ''
-                    for zone in list(zones.keys()):
+                    for zone in zones.keys():
                         if re.search(r'^%s\.' % zone, ip) and len(zone) > len(best_match):
                             best_match = zone
 
@@ -267,7 +267,7 @@ def __write_named_conf(self):
         # forward_zones = self.settings.manage_forward_zones
         # reverse_zones = self.settings.manage_reverse_zones
 
-        metadata = {'forward_zones': list(self.__forward_zones().keys()),
+        metadata = {'forward_zones': self.__forward_zones().keys(),
                     'reverse_zones': [],
                     'zone_include': ''}
 
@@ -280,7 +280,7 @@ def __write_named_conf(self):
 """ % {'zone': zone}
             metadata['zone_include'] = metadata['zone_include'] + txt
 
-        for zone in list(self.__reverse_zones().keys()):
+        for zone in self.__reverse_zones().keys():
             # IPv6 zones are : delimited
             if ":" in zone:
                 # if IPv6, assume xxxx:xxxx:xxxx:xxxx
@@ -324,7 +324,7 @@ def __write_secondary_conf(self):
         # forward_zones = self.settings.manage_forward_zones
         # reverse_zones = self.settings.manage_reverse_zones
 
-        metadata = {'forward_zones': list(self.__forward_zones().keys()),
+        metadata = {'forward_zones': self.__forward_zones().keys(),
                     'reverse_zones': [],
                     'zone_include': ''}
 
@@ -340,7 +340,7 @@ def __write_secondary_conf(self):
 """ % {'zone': zone, 'master': self.settings.bind_master}
             metadata['zone_include'] = metadata['zone_include'] + txt
 
-        for zone in list(self.__reverse_zones().keys()):
+        for zone in self.__reverse_zones().keys():
             # IPv6 zones are : delimited
             if ":" in zone:
                 # if IPv6, assume xxxx:xxxx:xxxx:xxxx for the zone
@@ -422,12 +422,12 @@ def __pretty_print_host_records(self, hosts, rectype: str = 'A', rclass: str = '
         # particular system
 
         for system in self.systems:
-            for (name, interface) in list(system.interfaces.items()):
-                if interface["dns_name"] == "":
+            for (name, interface) in system.interfaces.items():
+                if interface.dns_name == "":
                     self.logger.info("Warning: dns_name unspecified in the system: %s, while writing host records",
                                      system.name)
 
-        names = [k for k, v in list(hosts.items())]
+        names = [k for k, v in hosts.items()]
         if not names:
             return ''  # zones with no hosts
 
@@ -472,12 +472,12 @@ def __pretty_print_cname_records(self, hosts, rectype: str = 'CNAME'):
         # Which results in empty records without any warning to the users
 
         for system in self.systems:
-            for (name, interface) in list(system.interfaces.items()):
-                cnames = interface.get("cnames", [])
+            for (name, interface) in system.interfaces.items():
+                cnames = interface.cnames
 
                 try:
                     if interface.get("dns_name", "") != "":
-                        dnsname = interface["dns_name"].split('.')[0]
+                        dnsname = interface.dns_name.split('.')[0]
                         for cname in cnames:
                             s += "%s  %s  %s;\n" % (cname.split('.')[0], rectype, dnsname)
                     else:
@@ -529,7 +529,7 @@ def __write_zone_files(self):
 
         zonefileprefix = self.settings.bind_chroot_path + self.zonefile_base
 
-        for (zone, hosts) in list(forward.items()):
+        for (zone, hosts) in forward.items():
             metadata = {
                 'cobbler_server': cobbler_server,
                 'serial': serial,
@@ -571,7 +571,7 @@ def __write_zone_files(self):
             self.logger.info("generating (forward) %s", zonefilename)
             self.templar.render(template_data, metadata, zonefilename)
 
-        for (zone, hosts) in list(reverse.items()):
+        for (zone, hosts) in reverse.items():
             metadata = {
                 'cobbler_server': cobbler_server,
                 'serial': serial,
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -541,11 +541,14 @@ def run(self):
             'webroot': os.path.normpath(webroot),
             'tftproot': os.path.normpath(tftproot),
             'httpd_service': httpd_service,
+            'bind_zonefiles': bind_zonefiles,
         },
         configure_files=[
             "config/cobbler/settings.yaml",
             "config/apache/cobbler.conf",
             "config/service/cobblerd.service",
+            "templates/etc/named.template",
+            "templates/etc/secondary.template",
         ],
         man_pages=[
             'docs/cobblerd.rst',


Test patch: