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 ""