internal/checks/deprecated.go
Rama main · réplica de lectura del repositorio original.
package checks
import (
"reflect"
"slices"
"strconv"
"strings"
"sync"
"k8s.io/client-go/kubernetes/scheme"
"github.com/sophotechlabs/spinoza/internal/api"
)
type Facts struct {
ServerVersion string
ServedVersions []string
Warnings []string
}
type removal struct {
groupVersion string
kinds string
minor int
}
type removalKey struct {
groupVersion string
minor int
}
type removedAPI interface {
APILifecycleRemoved() (int, int)
}
var removals = sync.OnceValue(collectRemovals)
func collectRemovals() []removal {
grouped := map[removalKey][]string{}
for gvk, typ := range scheme.Scheme.AllKnownTypes() {
if strings.HasSuffix(gvk.Kind, "List") {
continue
}
marked, ok := reflect.TypeAssert[removedAPI](reflect.New(typ))
if !ok {
continue
}
major, minor := marked.APILifecycleRemoved()
if major != 1 {
continue
}
key := removalKey{groupVersion: gvk.GroupVersion().String(), minor: minor}
grouped[key] = append(grouped[key], gvk.Kind)
}
return sortedRemovals(grouped)
}
func sortedRemovals(grouped map[removalKey][]string) []removal {
out := make([]removal, 0, len(grouped))
for key, kinds := range grouped {
slices.Sort(kinds)
out = append(out, removal{
groupVersion: key.groupVersion,
kinds: strings.Join(kinds, ", "),
minor: key.minor,
})
}
slices.SortFunc(out, func(one, two removal) int {
if one.groupVersion != two.groupVersion {
return strings.Compare(one.groupVersion, two.groupVersion)
}
return one.minor - two.minor
})
return out
}
func deprecationChecks() []check {
return []check{
{
id: "serves-a-removed-api",
title: "Cluster still serves an API a release removes",
category: categoryReliability,
severity: severityHigh,
wrong: "Anything still writing to this version stops working the moment the cluster is upgraded past the release that removes it.",
remedy: "Move whatever uses it to the replacement version before that upgrade.",
find: overCorpus(servesARemovedAPI),
},
{
id: "apiserver-says-deprecated",
title: "The apiserver warned about something the audit asked for",
category: categoryReliability,
severity: severityLow,
wrong: "Your own cluster answered a request with a deprecation warning. This is what it has said since Spinoza connected, so a window open for an hour has seen more than one just started.",
remedy: "Move to what the warning names.",
find: overCorpus(apiserverSaysDeprecated),
},
}
}
func minorOf(version string) int {
trimmed := strings.TrimPrefix(version, "v")
parts := strings.Split(trimmed, ".")
if len(parts) < 2 {
return 0
}
minor, err := strconv.Atoi(strings.TrimSuffix(parts[1], "+"))
if err != nil {
return 0
}
return minor
}
func servesARemovedAPI(sc scan) []found {
running := minorOf(sc.facts.ServerVersion)
if running == 0 {
return nil
}
out := []found{}
for _, one := range removals() {
if one.minor <= running {
continue
}
if !slices.Contains(sc.facts.ServedVersions, one.groupVersion) {
continue
}
out = append(out, clusterFinding("serves "+one.groupVersion,
one.groupVersion+" carries "+one.kinds+" and is removed in 1."+
strconv.Itoa(one.minor)+"; this cluster is 1."+strconv.Itoa(running)))
}
return out
}
func apiserverSaysDeprecated(sc scan) []found {
out := make([]found, 0, len(sc.facts.Warnings))
for _, text := range sc.facts.Warnings {
out = append(out, clusterFinding(warningSubject(text), text))
}
return out
}
func warningSubject(text string) string {
head, _, found := strings.Cut(text, " is deprecated")
if !found {
return "the apiserver"
}
return head
}
func clusterFinding(name, detail string) found {
return found{
subject: Subject{
Ref: api.ObjectRef{Version: "v1", Resource: "clusters", Name: name},
Kind: "Cluster",
},
detail: detail,
}
}