Ctrl K
ComunidadGuías…

pkg/readreason/codes.go

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

Archivo original ↗Preguntar a KiBot
// Package readreason names why Radar could not give a complete answer to a
// resource read or a search, and what resolves each reason. The values are
// wire contracts: resource-read errors carry one as error_code (REST bodies,
// MCP structuredContent and text), and coverage gaps as gaps[].code. Clients
// branch on them, so a spelling changes only deliberately, and a newer UI
// maps a renamed code it can still receive from an older Radar.
//
// A code names its subject and condition. Where whose limit it is changes the
// remedy, the prefix names that identity: user_ for the access of the user
// asking (a person, or an agent acting as one), radar_ for Radar's own
// Kubernetes identity and what it collects. kind_
// codes cover kind resolution, support and loading; namespace_ and outside_ codes are how
// the request targeted namespaces; cluster_ and connection_ are Radar's
// cluster connection; the rest describe the request or the answer itself.
// Each code's Action says what resolves it.
package readreason

import "slices"

// Code is one reason a read or search could not give a complete answer.
type Code string

const (
	// UserForbidden: the user's own RBAC denies the exact operation requested.
	UserForbidden Code = "user_forbidden"
	// UserNoNamespaceAccess: the user's access reaches no namespace in the
	// request's scope (Radar's membership check: the user can list pods or
	// deployments there). A namespace the user named and can't see is
	// UserNamespaceNotVisible.
	UserNoNamespaceAccess Code = "user_no_namespace_access"
	// UserPermissionCheckFailed: Radar could not complete the user's permission
	// check. It is not a denial, and a retry can succeed.
	UserPermissionCheckFailed Code = "user_permission_check_failed"

	// RadarForbidden: Kubernetes refused Radar's own identity (its service
	// account or kubeconfig user). Granting the user access does not help.
	RadarForbidden Code = "radar_forbidden"
	// RadarCredentialsRejected: Kubernetes rejected Radar's own
	// credentials (HTTP 401).
	RadarCredentialsRejected Code = "radar_credentials_rejected"
	// RadarNamespaceNotCollected: the target reaches namespaces Radar
	// does not collect.
	RadarNamespaceNotCollected Code = "radar_namespace_not_collected"

	// KindSyncPending: the kind is still loading.
	KindSyncPending Code = "kind_sync_pending"
	// KindSyncTimedOut: the kind's initial load did not finish by its deadline.
	// Radar keeps trying and it may still finish, but repeating the request
	// does not restart it; if it persists, Radar's connection or
	// list/watch permissions need attention.
	KindSyncTimedOut Code = "kind_sync_timed_out"
	// RadarKindNotCollected: Radar's setup leaves the kind out, so it can't say
	// which exist. Whoever runs Radar can include it, if Radar's identity may
	// list and watch it.
	RadarKindNotCollected Code = "radar_kind_not_collected"
	// KindUnknown: neither the built-in catalog nor discovery resolves the
	// kind, or the version a reference names it at.
	KindUnknown Code = "kind_unknown"
	// KindNotWatchable: the API cannot be list/watched, so Radar cannot collect
	// it; warming a watch will not help.
	KindNotWatchable Code = "kind_not_watchable"
	// KindNotSearchable: search doesn't cover the kind because Radar reads it
	// directly instead of watching it (Endpoints, EndpointSlices, Leases), so a
	// search can't say which exist; list_resources or get_resource reads it.
	KindNotSearchable Code = "kind_not_searchable"

	// OutsideNamespacePin: a namespace the request names is outside Radar's
	// --namespace-scope pin, which whoever runs Radar sets.
	OutsideNamespacePin Code = "outside_namespace_pin"
	// UserNamespaceNotVisible: the namespace is not among those the user can
	// see, or does not exist; the code does not say which.
	UserNamespaceNotVisible Code = "user_namespace_not_visible"
	// NamespaceNotFound: the namespace does not exist. Only a user who can
	// see every namespace is told this.
	NamespaceNotFound Code = "namespace_not_found"
	// NamespaceRequired: a get or a search of a namespaced kind named no
	// namespace.
	NamespaceRequired Code = "namespace_required"

	// ObjectNotFound: the object is absent from a scope Radar read in full.
	ObjectNotFound Code = "object_not_found"
	// ListTooLarge: the list is over the request's size budget.
	ListTooLarge Code = "list_too_large"
	// FilterEvalFailed: a search filter could not be evaluated on objects
	// Radar did read, so whether they match is unknown.
	FilterEvalFailed Code = "filter_eval_failed"
	// FindingsScanIncomplete: the findings scan ran out of time, so findings
	// in unscanned reports may still match. Narrow with kind:, ns: or a more
	// specific term.
	FindingsScanIncomplete Code = "findings_scan_incomplete"

	// ClusterConnecting: Radar is still connecting to the cluster.
	ClusterConnecting Code = "cluster_connecting"
	// ClusterDisconnected: Radar has no working cluster connection; the
	// explanation carries the cause.
	ClusterDisconnected Code = "cluster_disconnected"
	// ConnectionChanged: Radar's cluster connection or cache was replaced
	// during the read (a context switch or a reconnect), so the read cannot be
	// trusted and may now reach a different cluster.
	ConnectionChanged Code = "connection_changed"

	// ReadError: an unclassified read or discovery failure; the explanation
	// carries the cause.
	ReadError Code = "read_error"
)

// Action is what resolves a code. It is a property of the code, not of one
// response: clients choose their own retry budgets and delays.
type Action string

const (
	// ActionRetry: the same request may succeed later; nothing has to change.
	ActionRetry Action = "retry"
	// ActionWait: retry later; if it persists, Radar needs
	// attention.
	ActionWait Action = "wait"
	// ActionCheckTarget: confirm which cluster Radar is connected to before
	// repeating the request.
	ActionCheckTarget Action = "check_target"
	// ActionChangeRequest: the request itself has to change.
	ActionChangeRequest Action = "change_request"
	// ActionUserAccess: the user's own Kubernetes access has to change.
	ActionUserAccess Action = "user_access"
	// ActionRadarConfig: Radar's identity, credentials or collection
	// configuration has to change.
	ActionRadarConfig Action = "radar_config"
	// ActionNone: the code is an answer, not a failure.
	ActionNone Action = "none"
)

var actions = map[Code]Action{
	UserForbidden:              ActionUserAccess,
	UserNoNamespaceAccess:      ActionUserAccess,
	UserPermissionCheckFailed:  ActionRetry,
	RadarForbidden:             ActionRadarConfig,
	RadarCredentialsRejected:   ActionRadarConfig,
	RadarNamespaceNotCollected: ActionRadarConfig,
	KindSyncPending:            ActionRetry,
	KindSyncTimedOut:           ActionWait,
	RadarKindNotCollected:      ActionRadarConfig,
	KindUnknown:                ActionChangeRequest,
	KindNotWatchable:           ActionChangeRequest,
	KindNotSearchable:          ActionChangeRequest,
	OutsideNamespacePin:        ActionRadarConfig,
	UserNamespaceNotVisible:    ActionChangeRequest,
	NamespaceNotFound:          ActionChangeRequest,
	NamespaceRequired:          ActionChangeRequest,
	ObjectNotFound:             ActionNone,
	ListTooLarge:               ActionChangeRequest,
	FilterEvalFailed:           ActionChangeRequest,
	FindingsScanIncomplete:     ActionChangeRequest,
	ClusterConnecting:          ActionRetry,
	ClusterDisconnected:        ActionRadarConfig,
	ConnectionChanged:          ActionCheckTarget,
	ReadError:                  ActionRetry,
}

// ActionFor reports what resolves c, and false for an undeclared code.
func ActionFor(c Code) (Action, bool) {
	a, ok := actions[c]
	return a, ok
}

// Known reports whether c is a declared code.
func Known(c Code) bool {
	_, ok := actions[c]
	return ok
}

// Codes returns every declared code, sorted.
func Codes() []Code {
	out := make([]Code, 0, len(actions))
	for c := range actions {
		out = append(out, c)
	}
	slices.Sort(out)
	return out
}

// AffectsWholeCluster reports a code that blocks every read from this Radar,
// not just one kind or namespace — the only codes that should make a fleet
// client treat the cluster itself as failing. RadarCredentialsRejected is
// not one: a single read can be rejected while others succeed, as when a
// token rotates mid-read. Once Radar confirms its credentials are rejected,
// its connection is down and reads report ClusterDisconnected.
func AffectsWholeCluster(c Code) bool {
	return c == ClusterConnecting || c == ClusterDisconnected
}

// precedence orders codes for an error that has to name one, by what the
// user should do first: fix a request no retry can answer; restore the
// connection, without which nothing is read; wait for what resolves by
// itself, since a retry then returns at least part of the answer; act on
// their own access and namespace targeting; then Radar's identity and setup,
// a load that is overdue, and what Radar doesn't collect. Search filter
// failures, incomplete findings scans and the unclassified read_error come
// last among failures, so an error names a specific cause when it has one.
// object_not_found is an
// answer, not a failure.
var precedence = []Code{
	NamespaceRequired, KindUnknown, KindNotWatchable, KindNotSearchable, ListTooLarge,
	ClusterDisconnected, ClusterConnecting, ConnectionChanged,
	KindSyncPending, UserPermissionCheckFailed,
	UserForbidden, UserNoNamespaceAccess, UserNamespaceNotVisible, NamespaceNotFound,
	OutsideNamespacePin,
	RadarCredentialsRejected, RadarForbidden, RadarKindNotCollected,
	KindSyncTimedOut, RadarNamespaceNotCollected, FilterEvalFailed, FindingsScanIncomplete, ReadError,
	ObjectNotFound,
}

// Primary returns the code an error reports when several codes apply, such
// as a read whose every namespace failed for a different reason. The error's
// coverage keeps the rest, and its sentence names them. A declared code
// outranks an undeclared one; among equals the first wins. It returns "" for
// no codes.
func Primary(codes []Code) Code {
	best, rank := Code(""), len(precedence)+1
	for _, c := range codes {
		r := slices.Index(precedence, c)
		if r < 0 {
			r = len(precedence)
		}
		if r < rank {
			best, rank = c, r
		}
	}
	return best
}