Linked zones: shared records kept in step across views
Zones of the same name in different views can be linked. Shared records
are kept in step: a change in one view reaches the others in the same
changeset, as explicit operations, so the diff, permission checks,
history and undo cover every view. Records local to a view override the
shared records of that name and type there; removing the override brings
the shared ones back, and sharing a view's version makes it every view's.
Each zone keeps its own options, serial and file.
Linking existing zones changes no answer: what is the same everywhere
becomes shared, the rest stays local. Unlinking keeps every record, all
local. ACME challenges are written once per link, and --view all changes
a link once. jarl zone link/unlink, zone create --link-with, record
--local/--shared, LINK column, web UI controls, POST /zones/{zone}/link
and /unlink, validation checks linked_*, store migration 11, docs and
e2e.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6313fa51f8
commit
057433c198
31 files changed
+1729
-55
No files matched your search
+32
-2
@@ -265,6 +265,9 @@ func setZoneFields(st *model.State, z *model.Zone, in *ZoneInput, creating bool)
|
||||
if in.Comment != nil {
|
||||
z.Comment = *in.Comment
|
||||
}
|
||||
if in.LinkGroup != nil {
|
||||
z.LinkGroup = *in.LinkGroup
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -447,6 +450,9 @@ func applyRecordCreate(st *model.State, op model.Op, c *applyCtx) error {
|
||||
if in.Disabled != nil {
|
||||
r.Disabled = *in.Disabled
|
||||
}
|
||||
if in.SharedID != nil {
|
||||
r.SharedID = *in.SharedID
|
||||
}
|
||||
for _, w := range p.Warnings {
|
||||
c.warn("%s %s: %s", dnsname.Display(owner), p.Type, w)
|
||||
}
|
||||
@@ -526,6 +532,9 @@ func applyRecordUpdate(st *model.State, op model.Op, c *applyCtx) error {
|
||||
if in.Disabled != nil {
|
||||
r.Disabled = *in.Disabled
|
||||
}
|
||||
if in.SharedID != nil {
|
||||
r.SharedID = *in.SharedID
|
||||
}
|
||||
r.Touch(c.user(), c.now)
|
||||
return nil
|
||||
}
|
||||
@@ -662,16 +671,34 @@ func applyRRSetReplace(st *model.State, op model.Op, c *applyCtx) error {
|
||||
k := strings.ToLower(r.Data)
|
||||
if seen[k] && !keep[k] {
|
||||
keep[k] = true
|
||||
if (ttlKnown && !sameTTL(r.TTL, ttl)) || r.Disabled {
|
||||
shared := r.SharedID
|
||||
if in.Shared != nil {
|
||||
shared = 0
|
||||
if *in.Shared {
|
||||
shared = r.SharedID
|
||||
if shared == 0 {
|
||||
shared = r.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ttlKnown && !sameTTL(r.TTL, ttl)) || r.Disabled || shared != r.SharedID {
|
||||
if ttlKnown {
|
||||
r.TTL = copyTTL(ttl)
|
||||
}
|
||||
r.Disabled = false
|
||||
r.Disabled, r.SharedID = false, shared
|
||||
r.Touch(c.user(), c.now)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if r.Disabled {
|
||||
// Kept, but shared or local like the rest of the set.
|
||||
if in.Shared != nil && (*in.Shared) != (r.SharedID != 0) {
|
||||
r.SharedID = 0
|
||||
if *in.Shared {
|
||||
r.SharedID = r.ID
|
||||
}
|
||||
r.Touch(c.user(), c.now)
|
||||
}
|
||||
continue
|
||||
}
|
||||
delete(st.Records, id)
|
||||
@@ -692,6 +719,9 @@ func applyRRSetReplace(st *model.State, op model.Op, c *applyCtx) error {
|
||||
if in.Comment != nil {
|
||||
r.Comment = *in.Comment
|
||||
}
|
||||
if in.Shared != nil && *in.Shared {
|
||||
r.SharedID = r.ID
|
||||
}
|
||||
r.Touch(c.user(), c.now)
|
||||
st.Records[r.ID] = r
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user