Page MenuHomeDevCentral

D3690.id9546.diff
No OneTemporary

D3690.id9546.diff

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,168 @@
+#!/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

Mime Type
text/plain
Expires
Sat, Sep 20, 13:00 (16 h, 46 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2997537
Default Alt Text
D3690.id9546.diff (7 KB)

Event Timeline