feat(diff): mark records deliberately kept outside the template
This commit is contained in:
+43
-3
@@ -19,6 +19,10 @@ type RecordDiff struct {
|
|||||||
Desired *model.Record // nil for Delete
|
Desired *model.Record // nil for Delete
|
||||||
Actual *model.Record // nil for Add
|
Actual *model.Record // nil for Add
|
||||||
ReadOnly bool // NS/SOA — shown but never applied
|
ReadOnly bool // NS/SOA — shown but never applied
|
||||||
|
// Custom marks a record the operator deliberately keeps outside the
|
||||||
|
// template: shown in its own section, never counted as drift, never
|
||||||
|
// applied. Set by MarkCustom, not by Diff.
|
||||||
|
Custom bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Key is the stable identifier of the RRset this diff targets, normalised the
|
// Key is the stable identifier of the RRset this diff targets, normalised the
|
||||||
@@ -37,7 +41,7 @@ type Changeset struct {
|
|||||||
func (c Changeset) Actionable() []RecordDiff {
|
func (c Changeset) Actionable() []RecordDiff {
|
||||||
var out []RecordDiff
|
var out []RecordDiff
|
||||||
for _, d := range c.Diffs {
|
for _, d := range c.Diffs {
|
||||||
if d.ReadOnly || d.Kind == InSync {
|
if d.ReadOnly || d.Custom || d.Kind == InSync {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
out = append(out, d)
|
out = append(out, d)
|
||||||
@@ -52,7 +56,7 @@ func (c Changeset) Actionable() []RecordDiff {
|
|||||||
func (c Changeset) Updates() []RecordDiff {
|
func (c Changeset) Updates() []RecordDiff {
|
||||||
var out []RecordDiff
|
var out []RecordDiff
|
||||||
for _, d := range c.Diffs {
|
for _, d := range c.Diffs {
|
||||||
if d.ReadOnly {
|
if d.ReadOnly || d.Custom {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if d.Kind == Add || d.Kind == Update {
|
if d.Kind == Add || d.Kind == Update {
|
||||||
@@ -73,7 +77,7 @@ func (c Changeset) Updates() []RecordDiff {
|
|||||||
func (c Changeset) Prunes() []RecordDiff {
|
func (c Changeset) Prunes() []RecordDiff {
|
||||||
var out []RecordDiff
|
var out []RecordDiff
|
||||||
for _, d := range c.Diffs {
|
for _, d := range c.Diffs {
|
||||||
if d.ReadOnly {
|
if d.ReadOnly || d.Custom {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if d.Kind == Delete {
|
if d.Kind == Delete {
|
||||||
@@ -131,3 +135,39 @@ func index(recs []model.Record) map[string]model.Record {
|
|||||||
}
|
}
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkCustom flags the diffs whose Key() is in keys as Custom. Only
|
||||||
|
// Kind == Delete diffs are marked, and never read-only ones: the template
|
||||||
|
// wins. As soon as the template starts describing a key, its diff becomes
|
||||||
|
// Add/Update/InSync and the stored mark stops having any effect (it is not
|
||||||
|
// deleted — the operator may go back to a template without that record).
|
||||||
|
func (c *Changeset) MarkCustom(keys []string) {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
set := make(map[string]bool, len(keys))
|
||||||
|
for _, k := range keys {
|
||||||
|
set[k] = true
|
||||||
|
}
|
||||||
|
for i := range c.Diffs {
|
||||||
|
d := &c.Diffs[i]
|
||||||
|
if d.ReadOnly || d.Kind != Delete {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if set[d.Key()] {
|
||||||
|
d.Custom = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Customs returns diffs marked by MarkCustom. Disjoint from Updates() and
|
||||||
|
// Prunes(), and outside Actionable() — a custom record is never drift.
|
||||||
|
func (c Changeset) Customs() []RecordDiff {
|
||||||
|
var out []RecordDiff
|
||||||
|
for _, d := range c.Diffs {
|
||||||
|
if d.Custom {
|
||||||
|
out = append(out, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
@@ -242,3 +242,62 @@ func TestIndexDedupLastWriteWins(t *testing.T) {
|
|||||||
t.Fatalf("expected exactly 1 diff for the duplicated key, got %d", count)
|
t.Fatalf("expected exactly 1 diff for the duplicated key, got %d", count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMarkCustomOnlyMarksDeletes(t *testing.T) {
|
||||||
|
template := []model.Record{
|
||||||
|
{Type: "A", Name: "www.example.com.", TTL: 300, Values: []string{"1.1.1.1"}},
|
||||||
|
}
|
||||||
|
actual := []model.Record{
|
||||||
|
{Type: "A", Name: "www.example.com.", TTL: 300, Values: []string{"2.2.2.2"}},
|
||||||
|
{Type: "CNAME", Name: "admin.example.com.", TTL: 300, Values: []string{"example.com."}},
|
||||||
|
}
|
||||||
|
cs := Diff(template, actual)
|
||||||
|
// Ключ записи, которую шаблон описывает (Update), и ключ лишней записи (Delete).
|
||||||
|
cs.MarkCustom([]string{"A www.example.com.", "CNAME admin.example.com."})
|
||||||
|
|
||||||
|
customs := cs.Customs()
|
||||||
|
if len(customs) != 1 {
|
||||||
|
t.Fatalf("expected exactly 1 custom diff, got %d: %+v", len(customs), customs)
|
||||||
|
}
|
||||||
|
if customs[0].Key() != "CNAME admin.example.com." {
|
||||||
|
t.Fatalf("expected the Delete diff to be custom, got %q", customs[0].Key())
|
||||||
|
}
|
||||||
|
// Шаблон побеждает: описанный шаблоном ключ остаётся обычным Update.
|
||||||
|
updates := cs.Updates()
|
||||||
|
if len(updates) != 1 || updates[0].Key() != "A www.example.com." {
|
||||||
|
t.Fatalf("expected the templated record to stay in Updates, got %+v", updates)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomDiffsLeaveActionableAndPrunes(t *testing.T) {
|
||||||
|
actual := []model.Record{
|
||||||
|
{Type: "CNAME", Name: "admin.example.com.", TTL: 300, Values: []string{"example.com."}},
|
||||||
|
}
|
||||||
|
cs := Diff(nil, actual)
|
||||||
|
if len(cs.Prunes()) != 1 {
|
||||||
|
t.Fatalf("precondition: expected 1 prune before marking, got %+v", cs.Prunes())
|
||||||
|
}
|
||||||
|
cs.MarkCustom([]string{"CNAME admin.example.com."})
|
||||||
|
|
||||||
|
if got := cs.Prunes(); len(got) != 0 {
|
||||||
|
t.Fatalf("expected custom diff to leave Prunes, got %+v", got)
|
||||||
|
}
|
||||||
|
if got := cs.Actionable(); len(got) != 0 {
|
||||||
|
t.Fatalf("expected custom diff to leave Actionable, got %+v", got)
|
||||||
|
}
|
||||||
|
if got := cs.Customs(); len(got) != 1 {
|
||||||
|
t.Fatalf("expected 1 custom diff, got %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarkCustomNeverMarksReadOnly(t *testing.T) {
|
||||||
|
actual := []model.Record{
|
||||||
|
{Type: "NS", Name: "example.com.", TTL: 300, Values: []string{"ns1.example.com."}},
|
||||||
|
}
|
||||||
|
cs := Diff(nil, actual)
|
||||||
|
cs.MarkCustom([]string{"NS example.com."})
|
||||||
|
|
||||||
|
if got := cs.Customs(); len(got) != 0 {
|
||||||
|
t.Fatalf("read-only diffs must never become custom, got %+v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user