feat(tmpl): {{domain_name}} placeholder — materialize on diff/apply, parameterize on snapshot

Adds internal/tmpl with Materialize (template placeholder -> zone name) and
Parameterize (zone name -> placeholder, the inverse used by the
template-from-zone snapshot). service.resolve now materializes the template
against DomainRef.ZoneName before diffing, so one template can be reused
across domains. LoadDomainFull (source query + hand-edited sqlc output, since
sqlc is not installed) now also selects zone_name to populate it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwxdSt4reTm7Dj1oxRvpP3
This commit is contained in:
2026-07-05 13:41:18 +07:00
parent 135917216c
commit df895d8850
9 changed files with 158 additions and 11 deletions
+5 -4
View File
@@ -13,6 +13,7 @@ import (
"github.com/vasyakrg/dns-autoresolver/internal/model"
"github.com/vasyakrg/dns-autoresolver/internal/service"
"github.com/vasyakrg/dns-autoresolver/internal/store/dto"
"github.com/vasyakrg/dns-autoresolver/internal/tmpl"
)
func writeJSON(w http.ResponseWriter, status int, v any) {
@@ -141,17 +142,17 @@ func (a *API) handleTemplateFromZone(w http.ResponseWriter, r *http.Request) {
managed = append(managed, rc)
}
}
doc := dto.FromModel(managed)
tmpl, err := a.Store.CreateTemplate(r.Context(), pid, dom.ZoneName+" snapshot", doc)
doc := tmpl.Parameterize(managed, dom.ZoneName)
tpl, err := a.Store.CreateTemplate(r.Context(), pid, dom.ZoneName+" snapshot", doc)
if err != nil {
log.Printf("api: template-from-zone: create template failed: %v", err)
writeErr(w, http.StatusInternalServerError, "internal error")
return
}
if _, err := a.Store.SetDomainTemplate(r.Context(), did, pid, &tmpl.ID); err != nil {
if _, err := a.Store.SetDomainTemplate(r.Context(), did, pid, &tpl.ID); err != nil {
log.Printf("api: template-from-zone: attach template failed: %v", err)
writeErr(w, http.StatusInternalServerError, "internal error")
return
}
writeJSON(w, http.StatusCreated, toTemplateResponse(tmpl))
writeJSON(w, http.StatusCreated, toTemplateResponse(tpl))
}
+16 -3
View File
@@ -643,15 +643,17 @@ func TestZoneRecords_ReturnsProviderRecords(t *testing.T) {
// TestTemplateFromZone_SnapshotsManagedRecordsOnlyAndAttaches covers the
// snapshot-to-template flow: NS/SOA are read-only and must be excluded from
// the generated template, and the new template must be auto-attached to the
// domain (SetDomainTemplate) so check/apply become immediately available.
// the generated template, the new template must be auto-attached to the
// domain (SetDomainTemplate) so check/apply become immediately available,
// and the zone name must be parameterized to {{domain_name}} in names/values
// so the resulting template is reusable across domains (tmpl.Parameterize).
func TestTemplateFromZone_SnapshotsManagedRecordsOnlyAndAttaches(t *testing.T) {
a, ts := newTenantTestAPI()
domID := uuid.New()
ts.domains = []store.Domain{{ID: domID, ZoneName: "example.com", ZoneID: "z1"}}
a.Svc = &mockCheckApplier{zoneRecords: []model.Record{
{Type: model.A, Name: "a.example.com.", TTL: 300, Values: []string{"1.1.1.1"}},
{Type: model.TXT, Name: "a.example.com.", TTL: 300, Values: []string{"v=spf1 -all"}},
{Type: model.TXT, Name: "a.example.com.", TTL: 300, Values: []string{"v=spf1 a:mail.example.com -all"}},
{Type: model.NS, Name: "example.com.", TTL: 3600, Values: []string{"ns1.example.com."}},
{Type: model.SOA, Name: "example.com.", TTL: 3600, Values: []string{"ns1.example.com. admin.example.com. 1 2 3 4 5"}},
}}
@@ -674,6 +676,17 @@ func TestTemplateFromZone_SnapshotsManagedRecordsOnlyAndAttaches(t *testing.T) {
if r.Type == "NS" || r.Type == "SOA" {
t.Fatalf("read-only record type %s leaked into snapshot template", r.Type)
}
if strings.Contains(r.Name, "example.com") {
t.Fatalf("expected zone name parameterized to {{domain_name}} in record name, got %+v", r)
}
for _, v := range r.Values {
if strings.Contains(v, "example.com") {
t.Fatalf("expected zone name parameterized to {{domain_name}} in record value, got %+v", r)
}
}
}
if ts.createTemplate.Doc.Records[1].Values[0] != "v=spf1 a:mail.{{domain_name}} -all" {
t.Fatalf("expected SPF value parameterized, got %q", ts.createTemplate.Doc.Records[1].Values[0])
}
// SetDomainTemplate must have been called with the newly created template's id.
if ts.domains[0].TemplateID == nil || *ts.domains[0].TemplateID != ts.createTemplate.ID {