141 lines
3.9 KiB
Go
141 lines
3.9 KiB
Go
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
|
|
}
|
|
|
|
// CheckGroupName reports why name cannot be a ClientGroup's object name, so a
|
|
// validation endpoint can say it before a save does.
|
|
func CheckGroupName(name string) error {
|
|
if name == "" {
|
|
return fmt.Errorf("a group needs a name")
|
|
}
|
|
return checkName("group", name)
|
|
}
|