Ctrl K
ComunidadGuías…

internal/checks/deprecated.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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,
	}
}