Files
zeekayandHanzo Dev 7163867904 initial commit, on zip v1.20.0
The tree was staged but never committed — this is the repo's first commit, so it
carries both the existing content and the zip v1.18.8 -> v1.20.0 migration.

Migration: Prepare() is replaced by Build() error, and both test harnesses assert
the verdict rather than discarding it. main.go:59's Use call needed no change —
the three middleware constructors already return zip.Handler, which satisfies the
closed Component interface.

The committed projections (k8s.plugin.json, openapi.json, ops/zipdoc_gen.go) are
regenerated: v1.20.0 splits an op's prose into summary (first line) + description
and zipdoc now emits per-field docs, so all three artifacts move with the bump.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-02 14:20:05 -07:00

67 lines
2.4 KiB
Go

package ops
import (
"context"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/discovery"
"github.com/hanzoai/k8s/plane"
)
// podMetrics returns live CPU and memory use for pods, from metrics.k8s.io.
//
// `available` is the field that carries the honesty. metrics-server is absent from most
// of our clusters, and a cluster without it answers this call with an error — which,
// rendered as zeroes, tells an operator that a busy namespace is idle. So a missing
// metrics API is reported as available=false with no rows, and a caller must render
// "unmeasured" rather than "0m".
//
// Example: {"cluster":"hanzo-k8s","namespace":"acme-prod"}
// Response: {"pods":[{"namespace":"acme-prod","name":"api-7d9f-2xk","cpuMilli":250,"memoryBytes":536870912}],"available":true}
func (o Ops) podMetrics(ctx context.Context, in *plane.Selector) (*plane.PodMetrics, error) {
b, err := o.reach(ctx, in.Cluster)
if err != nil {
return nil, err
}
scope, err := b.Scope(in.Namespace)
if err != nil {
return nil, fail(err)
}
out := &plane.PodMetrics{Pods: []plane.PodMetric{}}
for _, ns := range scope {
list, err := b.Metrics.MetricsV1beta1().PodMetricses(ns).List(ctx, metav1.ListOptions{LabelSelector: in.LabelSelector})
if err != nil {
// One namespace failing on a cluster that HAS metrics is a real error and
// must surface; a cluster with no metrics API at all fails every namespace
// the same way, and the caller needs that distinction. The apiserver
// answers a missing aggregated API with NotFound or ServiceUnavailable,
// so those two are the "no metrics here" answer and nothing else is.
if noMetrics(err) {
return out, nil
}
return nil, fail(err)
}
for i := range list.Items {
m := list.Items[i]
row := plane.PodMetric{Namespace: m.Namespace, Name: m.Name}
for _, c := range m.Containers {
row.CPUMilli += c.Usage.Cpu().MilliValue()
row.MemoryBytes += c.Usage.Memory().Value()
}
out.Pods = append(out.Pods, row)
}
}
out.Available = true
return out, nil
}
// noMetrics reports whether an error means the cluster serves no metrics API, as
// distinct from a metrics API that refused this read.
func noMetrics(err error) bool {
return apierrors.IsNotFound(err) || apierrors.IsServiceUnavailable(err) ||
meta.IsNoMatchError(err) || discovery.IsGroupDiscoveryFailedError(err)
}