Page MenuHomeDevCentral

D4092.diff
No OneTemporary

D4092.diff

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -52,3 +52,11 @@
hooks:
- id: shellcheck
args: ["--severity=warning"]
+
+ - repo: local
+ hooks:
+ - id: dns-soa-serial-validator
+ name: Validate DNS Zone SOA Serials
+ entry: scripts/precommit-dns-validator.sh
+ language: system
+ files: ^roles/dns/knot/files/zones/.*\.zone$
diff --git a/scripts/precommit-dns-validator.sh b/scripts/precommit-dns-validator.sh
new file mode 100755
--- /dev/null
+++ b/scripts/precommit-dns-validator.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+# Exit immediately if a fail happens
+set -e
+
+# Loop over all modified files passed by pre-commit
+for file in "$@"; do
+ case "$file" in
+ *.zone)
+ echo "Checking DNS zone: $file"
+
+ # Create a temporary file
+ old_file=$(mktemp)
+
+ # Get old version from Git
+ if git show "HEAD:$file" > "$old_file" 2>/dev/null; then
+ :
+ else
+ # New dns zone file
+ # Remove the tmp file
+ rm -f "$old_file"
+ # Skip to next file
+ continue
+ fi
+
+ # Call the Python validator
+ python3 scripts/validate_dns_serial.py "$old_file" "$file"
+
+ # Remove the tmp file
+ rm -f "$old_file"
+ ;;
+ esac
+done
diff --git a/scripts/validate_dns_serial.py b/scripts/validate_dns_serial.py
new file mode 100755
--- /dev/null
+++ b/scripts/validate_dns_serial.py
@@ -0,0 +1,125 @@
+#!/usr/bin/env python3
+
+import re
+import sys
+from pathlib import Path
+
+
+# -------------------------------------------------------------
+# Configuration
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+# Match SOA serial number in a DNS zone file content (e.g. "2026041801 ;serial")
+SERIAL_REGEX = re.compile(r"(\d+)\s*;\s*serial", re.IGNORECASE)
+
+
+# -------------------------------------------------------------
+# Helper functions
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def read_file(path: str) -> str:
+ """
+ Read and return the content of a file.
+
+ Args:
+ path (str): Path to the file.
+
+ Returns:
+ str: Content of the file as a string.
+ """
+ return Path(path).read_text(encoding="utf-8")
+
+
+def extract_serial(content: str) -> int | None:
+ """
+ Extract the SOA serial number from a DNS zone file content.
+
+ Args:
+ content (str): Content of the DNS zone file.
+
+ Returns:
+ int | None: The serial number if found, otherwise None.
+ """
+ match = SERIAL_REGEX.search(content)
+ if not match:
+ return None
+ return int(match.group(1))
+
+
+def normalize_zone(content: str) -> str:
+ """
+ Remove the serial and clean the text (e.g. remove extra spaces)
+ to properly compare two DNS zone files.
+
+ Args:
+ content (str): Content of the DNS zone file.
+
+ Returns:
+ str: Normalized content of the DNS zone file ready for comparison.
+ """
+ content = SERIAL_REGEX.sub("SERIAL ; serial", content)
+
+ # 1. splitlines → split content into lines
+ # 2. for line → iterate over each line
+ # 3. split() → check if line is not empty (if line.split())
+ # 4. split() → split line into words (remove all extra spaces)
+ # 5. join " " → rebuild each line with a single space
+ # 6. join "\n" → rebuild full text with newlines
+ # 7. strip → remove leading and trailing space
+ return "\n".join(
+ " ".join(line.split()) for line in content.splitlines() if line.split()
+ ).strip()
+
+
+# -------------------------------------------------------------
+# Main function
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+def main() -> int:
+ if len(sys.argv) != 3:
+ print(f"Usage: {sys.argv[0]} <old_zone_file> <new_zone_file>")
+ return 2
+
+ old_file = sys.argv[1]
+ new_file = sys.argv[2]
+
+ old_content = read_file(old_file)
+ new_content = read_file(new_file)
+
+ old_serial = extract_serial(old_content)
+ new_serial = extract_serial(new_content)
+
+ if new_serial is None:
+ print(f"ERROR: Could not find SOA serial in new file: {new_file}")
+ return 1
+
+ old_zone_normalized = normalize_zone(old_content)
+ new_zone_normalized = normalize_zone(new_content)
+
+ if old_zone_normalized == new_zone_normalized:
+ print(f"OK: No DNS record change detected in {new_file}")
+ return 0
+
+ if new_serial <= old_serial:
+ print(
+ f"ERROR: DNS zone changed but SOA serial was not bumped "
+ f"({old_serial} -> {new_serial}) in {new_file}"
+ )
+ return 1
+
+ print(
+ f"OK: DNS zone changed and SOA serial was bumped ({old_serial} -> {new_serial})"
+ )
+ return 0
+
+
+# -------------------------------------------------------------
+# pre-commit hook - Entry point
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+
+if __name__ == "__main__":
+ sys.exit(main())

File Metadata

Mime Type
text/plain
Expires
Fri, Apr 24, 02:20 (22 h, 22 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3653769
Default Alt Text
D4092.diff (5 KB)

Event Timeline