package kubeedit import ( "context" "fmt" "sort" "git.k8s-git-k6n.net/mats/fancy-dns/internal/config" "git.k8s-git-k6n.net/mats/fancy-dns/internal/kubesource" ) // GroupObject is a stored ClientGroup. type GroupObject struct { Name string Spec kubesource.ClientGroupSpec } // Groups lists the ClientGroup objects in evaluation order. func (e *Editor) Groups(ctx context.Context) ([]GroupObject, error) { items, err := e.list(ctx, kubesource.GroupGVR) if err != nil { return nil, err } out := make([]GroupObject, 0, len(items)) for i := range items { var s kubesource.ClientGroupSpec if err := decodeSpec(&items[i], &s); err != nil { continue // the runtime reports an undecodable object; the console skips it } out = append(out, GroupObject{Name: items[i].GetName(), Spec: s}) } sort.SliceStable(out, func(i, j int) bool { if out[i].Spec.Priority != out[j].Spec.Priority { return out[i].Spec.Priority < out[j].Spec.Priority } return out[i].Name < out[j].Name }) return out, nil } // SpecFromGroup converts the console's group to an object spec. func SpecFromGroup(g config.Group, priority int32) kubesource.ClientGroupSpec { return kubesource.ClientGroupSpec{ Priority: priority, Comment: g.Comment, Match: g.Match, Policy: g.Policy, AllowDomains: g.AllowDomains, DenyDomains: g.DenyDomains, SkipBlocklist: g.SkipBlocklist, SkipCache: g.SkipCache, RateLimit: g.RateLimit, MaxConcurrent: g.MaxConcurrent, LogQueries: g.LogQueries, Upstreams: g.Upstreams, } } // SyncResult says what SyncGroups changed. type SyncResult struct { Created, Updated, Deleted []string } // SyncGroups makes the stored ClientGroups match the console's list. // // desired is the full list the console sent, in the order the operator put it. // Groups for which foreign returns true belong to something else (the runtime's // YAML) and are neither written nor deleted. Every group is validated before // anything is written, so a bad group changes nothing. Priorities follow the // list position, so reordering in the console reorders evaluation. func (e *Editor) SyncGroups(ctx context.Context, desired []config.Group, foreign func(string) bool, actor string) (SyncResult, error) { var res SyncResult existing, err := e.Groups(ctx) if err != nil { return res, err } have := make(map[string]GroupObject, len(existing)) for _, g := range existing { have[g.Name] = g } type write struct { name string spec kubesource.ClientGroupSpec new bool } var writes []write keep := map[string]bool{} for i, g := range desired { if foreign != nil && foreign(g.Name) { if _, ours := have[g.Name]; !ours { continue } } if err := checkName("group", g.Name); err != nil { return res, err } spec := SpecFromGroup(g, int32(i+1)*10) if _, err := kubesource.CheckGroup(g.Name, spec); err != nil { return res, fmt.Errorf("group %q: %w", g.Name, err) } keep[g.Name] = true cur, ok := have[g.Name] switch { case !ok: writes = append(writes, write{g.Name, spec, true}) case !sameSpec(cur.Spec, spec): writes = append(writes, write{g.Name, spec, false}) } } for _, w := range writes { if _, err := e.upsert(ctx, kubesource.GroupGVR, "ClientGroup", w.name, actor, w.spec); err != nil { return res, fmt.Errorf("write group %q: %w", w.name, err) } if w.new { res.Created = append(res.Created, w.name) } else { res.Updated = append(res.Updated, w.name) } } for _, g := range existing { if keep[g.Name] { continue } if err := e.remove(ctx, kubesource.GroupGVR, g.Name); err != nil { return res, err } res.Deleted = append(res.Deleted, g.Name) } return res, nil }