Commit 5e5605bc28 for bind
commit 5e5605bc2808ba93f8a823799a88d72f108c699f
Author: Evan Hunt <each@isc.org>
Date: Wed Oct 21 12:05:06 2026 -0700
Allow extra zone options in template-generated zones
The 'options' argument of isctest.zone.Zone lists statements to be
added to the 'zone' statement generated for the zone from
'_common/zones.conf.j2'.
For example, to configure a primary zone that can receive dynamic
updates and allows transfers to a secondary, use the following in the
python test:
def bootstrap():
zone = Zone(
"example",
NS2,
signed=True,
options=[
"allow-update { 10.53.0.1; }",
"allow-transfer { 10.53.0.3; }",
],
)
zone.configure()
... and the '_common/zones.conf.j2' template will generate:
zone "example" {
type primary;
file "zones/example.db.signed";
allow-update { 10.53.0.1; };
allow-transfer { 10.53.0.3; };
};
Assisted-by: Claude:claude-opus-5-5
diff --git a/bin/tests/system/COOKBOOK.md b/bin/tests/system/COOKBOOK.md
index e1c4dcdd5d..0b3a33807a 100644
--- a/bin/tests/system/COOKBOOK.md
+++ b/bin/tests/system/COOKBOOK.md
@@ -235,6 +235,16 @@ matches the rendering server) and the validating resolver's with
(`trusted.conf.j2` emits `trust-anchors` from the `trust_anchors` data, and
`root.hint.conf` points the resolver at ns1 for the root zone).
+The generated `zone` block holds only the zone's `type` and its `file`
+(`server-addresses` for a `static-stub` zone). Further zone statements
+passed in `options` are copied into the block verbatim, each terminated
+with `;`:
+
+```python
+zone = Zone("example", NS2, options=["allow-update { 10.53.0.1; }"])
+zone.configure()
+```
+
The `dnssec_py` directory is the canonical example of this pattern,
including several modules with different zone setups sharing one directory.
Declare the generated files as artifacts:
diff --git a/bin/tests/system/_common/zones.conf.j2 b/bin/tests/system/_common/zones.conf.j2
index 5b5df244cf..75b0f21060 100644
--- a/bin/tests/system/_common/zones.conf.j2
+++ b/bin/tests/system/_common/zones.conf.j2
@@ -8,6 +8,9 @@ zone "@zone.name@" {
{% else %}
file "@zone.filepath@";
{% endif %}
+{% for option in zone.options %}
+ @option@;
+{% endfor %}
};
{% endif %}
{% endfor %}
diff --git a/bin/tests/system/isctest/template.py b/bin/tests/system/isctest/template.py
index 75661b88bc..87652ed6d4 100644
--- a/bin/tests/system/isctest/template.py
+++ b/bin/tests/system/isctest/template.py
@@ -221,6 +221,7 @@ class Zone:
ns: Nameserver | None = None
type: str = "primary"
filepath: Path | None = field(default=None)
+ options: list[str] = field(default_factory=list)
def __post_init__(self) -> None:
if self.filepath is None:
@@ -244,6 +245,12 @@ def zones(zone_list: "list[_SetupZone]") -> dict[str, Zone]:
each template zone is set to the actual zone file (signed or unsigned).
"""
return {
- z.name: Zone(name=z.name, ns=z.ns, type=z.type, filepath=z.filepath)
+ z.name: Zone(
+ name=z.name,
+ ns=z.ns,
+ type=z.type,
+ filepath=z.filepath,
+ options=z.options,
+ )
for z in zone_list
}
diff --git a/bin/tests/system/isctest/tests/tests_template.py b/bin/tests/system/isctest/tests/tests_template.py
index b085400b66..0349aa14df 100644
--- a/bin/tests/system/isctest/tests/tests_template.py
+++ b/bin/tests/system/isctest/tests/tests_template.py
@@ -11,12 +11,16 @@
"""
isctest.template self-test
-Check the {% include_indented %} tag of the TemplateEngine.
+Check the {% include_indented %} tag of the TemplateEngine and the zone
+blocks generated by _common/zones.conf.j2.
"""
import jinja2
import pytest
+from isctest.template import NS2, zones
+from isctest.zone import Zone
+
def render(templates, system_test_dir, name, source, data=None):
(system_test_dir / f"{name}.j2").write_text(source)
@@ -89,3 +93,27 @@ def test_include_indented_common_prefix(templates, system_test_dir):
"\t};\n"
"};\n"
)
+
+
+def test_zones_conf_options(templates, system_test_dir):
+ plain = Zone("plain", NS2)
+ extra = Zone("extra", NS2, options=["allow-update { any; }", "notify no"])
+ output = render(
+ templates,
+ system_test_dir,
+ "zones.conf",
+ '{% include "_common/zones.conf.j2" %}\n',
+ {"ns": NS2, "zones": zones([plain, extra])},
+ )
+ assert output == (
+ 'zone "plain" {\n'
+ "\ttype primary;\n"
+ '\tfile "zones/plain.db";\n'
+ "};\n"
+ 'zone "extra" {\n'
+ "\ttype primary;\n"
+ '\tfile "zones/extra.db";\n'
+ "\tallow-update { any; };\n"
+ "\tnotify no;\n"
+ "};\n"
+ )
diff --git a/bin/tests/system/isctest/zone.py b/bin/tests/system/isctest/zone.py
index c045b463a4..02714897d3 100644
--- a/bin/tests/system/isctest/zone.py
+++ b/bin/tests/system/isctest/zone.py
@@ -407,6 +407,7 @@ class Zone:
filepath_unsigned: Path | str | None = None,
filepath_signed: Path | str | None = None,
zone_type: str = "primary",
+ options: list[str] | None = None,
) -> None:
self.dname: dns.name.Name = (
dns.name.from_text(name) if isinstance(name, str) else name
@@ -418,6 +419,7 @@ class Zone:
self.signed = signed
self.subdir = subdir
self.type = zone_type
+ self.options: list[str] = list(options or [])
self._configured = False
prefix = f"{subdir}/" if subdir else ""