Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F11742913
D3690.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
7 KB
Referenced Files
None
Subscribers
None
D3690.diff
View Options
diff --git a/_tests/Makefile b/_tests/Makefile
--- a/_tests/Makefile
+++ b/_tests/Makefile
@@ -1,4 +1,4 @@
-test: test-python test-bats
+test: test-python test-roles test-bats
test-python:
python -m unittest discover modules
@@ -9,6 +9,15 @@
bats scripts/bats/test_edit_acme_dns_accounts.sh
bats roles/bats/test_webserver_content.sh
+# -------------------------------------------------------------
+# Tests for roles
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+test-roles: test-roles-dns
+
+test-roles-dns:
+ roles/python/dns/run_test_dns_zones.sh
+
# -------------------------------------------------------------
# Configuration test specific to the primary server
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/_tests/roles/python/dns/run_test_dns_zones.sh b/_tests/roles/python/dns/run_test_dns_zones.sh
new file mode 100755
--- /dev/null
+++ b/_tests/roles/python/dns/run_test_dns_zones.sh
@@ -0,0 +1,27 @@
+#!/bin/sh
+
+# -------------------------------------------------------------
+# Run roles/python/dns/test_dns_zones.py if kzonecheck exists
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Description: Avoid to maintain both BSD and GNU Makefile
+# for conditional logic .ifdef vs .if defined
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+KZONECHECK=kzonecheck
+
+# -------------------------------------------------------------
+# Program check
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+if ! command -v $KZONECHECK > /dev/null; then
+ echo "[WARNING] [SKIP] Skip testing roles/dns: kzonecheck missing"
+ exit 0
+fi
+
+# -------------------------------------------------------------
+# Run test
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+python roles/python/dns/test_dns_zones.py
diff --git a/_tests/roles/python/dns/test_dns_zones.py b/_tests/roles/python/dns/test_dns_zones.py
new file mode 100755
--- /dev/null
+++ b/_tests/roles/python/dns/test_dns_zones.py
@@ -0,0 +1,169 @@
+#!/usr/bin/env python3
+
+# -------------------------------------------------------------
+# Tests for roles/dns files
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# Project: Nasqueron
+# Description: Checks for DNS zones
+# License: BSD-2-Clause
+# -------------------------------------------------------------
+
+
+import os
+import subprocess
+import sys
+import tempfile
+from typing import Dict, List
+
+from jinja2 import Environment, FileSystemLoader
+import unittest
+from unittest_data_provider import data_provider
+import yaml
+
+
+# -------------------------------------------------------------
+# Pillars helpers
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+PILLARS_DIRECTORY = "../pillar/dns/"
+
+
+def load_pillar_files(pillar_directory: str) -> List:
+ pillar_files = []
+
+ for dir_path, dir_names, file_names in os.walk(pillar_directory):
+ files = [
+ os.path.join(dir_path, file_name)
+ for file_name in file_names
+ if file_name.endswith(".sls")
+ ]
+
+ pillar_files.extend(files)
+
+ return pillar_files
+
+
+def load_pillar(file_path: str) -> Dict:
+ with open(file_path) as fd:
+ return yaml.safe_load(fd)
+
+
+def load_pillars() -> Dict:
+ pillar_files = load_pillar_files(PILLARS_DIRECTORY)
+
+ return {file_path: load_pillar(file_path) for file_path in pillar_files}
+
+
+# -------------------------------------------------------------
+# Do zone file exists?
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+ZONE_FILES_DIR = "../roles/dns/knot/files/zones/"
+
+
+def get_zone_file_path(zone_name: str) -> str:
+ return ZONE_FILES_DIR + zone_name + ".zone"
+
+
+def is_existing_zone_file(zone_name: str) -> bool:
+ zone_file_path = get_zone_file_path(zone_name)
+
+ return os.path.exists(zone_file_path)
+
+
+# -------------------------------------------------------------
+# Jinja template
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def build_context(pillar: str) -> Dict:
+ return {
+ # To sync with context from knotdns_file_... from roles/dns/knot/config.sls
+ "identity": pillar["dns_identity"],
+ "vars": pillar.get("dns_zone_variables", {}),
+ }
+
+
+def resolve_zone_template(pillar: Dict, zone_name: str) -> str:
+ zone_path = get_zone_file_path(zone_name).replace("../", "")
+
+ engine = Environment(loader=FileSystemLoader(".."))
+ template = engine.get_template(zone_path)
+ context = build_context(pillar)
+
+ return template.render(context)
+
+
+# -------------------------------------------------------------
+# Call Knot utilities
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def check_zone(file_content: str) -> bool:
+ """Validate zone content with kzonecheck"""
+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_fd:
+ tmp_fd.write(file_content)
+
+ try:
+ result = subprocess.run(
+ ["kzonecheck", tmp_fd.name], capture_output=True, text=True
+ )
+
+ if result.returncode == 0:
+ return True
+
+ print(result.stdout, file=sys.stderr)
+ print(result.stderr, file=sys.stderr)
+ return False
+ finally:
+ os.remove(tmp_fd.name)
+
+
+# -------------------------------------------------------------
+# Tests
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+pillars = load_pillars()
+
+
+class Testinstance(unittest.TestCase):
+ @staticmethod
+ def provide_pillars():
+ for file_path, pillar in pillars.items():
+ yield (file_path, pillar)
+
+ @data_provider(provide_pillars)
+ def test_existing_zone_files(self, pillar_file_path, pillar):
+ for zone in pillar.get("dns_zones", []):
+ self.assertTrue(
+ is_existing_zone_file(zone), f"Missing zone file for {zone}"
+ )
+
+ @data_provider(provide_pillars)
+ def test_mandatory_fields(self, pillar_file_path, pillar):
+ self.assertIn(
+ "dns_identity", pillar, "Mandatory key, it should match the DNS server name"
+ )
+
+ @data_provider(provide_pillars)
+ def test_zone_content(self, pillar_file_path, pillar):
+ for zone in pillar.get("dns_zones", []):
+ zone_content = resolve_zone_template(pillar, zone)
+ zone_path = get_zone_file_path(zone)
+
+ result = check_zone(zone_content)
+ self.assertTrue(
+ result, f"Zone for {zone} doesn't pass Knot checks. Edit {zone_path}"
+ )
+
+
+# -------------------------------------------------------------
+# Application entry point
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/roles/dns/knot/config.sls b/roles/dns/knot/config.sls
--- a/roles/dns/knot/config.sls
+++ b/roles/dns/knot/config.sls
@@ -33,6 +33,8 @@
- source: salt://roles/dns/knot/files/zones/{{ zone }}.zone
- name: /var/db/knot/{{ zone }}.zone
- template: jinja
+
+ # Context to sync with test_dns_zones.py::build_context
- context:
identity: {{ pillar["dns_identity"] }}
vars: {{ zone_vars }}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Sep 19, 19:59 (11 h, 53 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2997132
Default Alt Text
D3690.diff (7 KB)
Attached To
Mode
D3690: Test DNS zone files
Attached
Detach File
Event Timeline
Log In to Comment