From 13cb55bb66272ac4c872a1f6bfa3e820d7d0ca5b Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sun, 11 May 2025 23:16:37 -0600 Subject: [PATCH] Rel v0.50.6 (#3338) * update deps * add epslice support * update pulses * fix #3334 * rel notes --- .gitignore | 3 +- .golangci.yml | 2 + Makefile | 2 +- change_logs/release_v0.50.6.md | 39 +++++++++++ go.mod | 21 +++--- go.sum | 42 ++++++------ internal/client/gvrs.go | 3 + internal/model/pulse.go | 17 ++--- internal/model/pulse_health.go | 33 +++++++-- internal/model/registry.go | 6 ++ internal/model1/table_data.go | 2 +- internal/port/pfs.go | 2 +- internal/render/ep_test.go | 4 +- internal/render/eps.go | 108 ++++++++++++++++++++++++++++++ internal/render/eps_test.go | 22 ++++++ internal/render/ev.go | 19 ++---- internal/render/testdata/ep.json | 31 +++++++-- internal/render/testdata/eps.json | 51 ++++++++++++++ internal/render/types.go | 2 +- snap/snapcraft.yaml | 2 +- 20 files changed, 335 insertions(+), 76 deletions(-) create mode 100644 change_logs/release_v0.50.6.md create mode 100644 internal/render/eps.go create mode 100644 internal/render/eps_test.go create mode 100644 internal/render/testdata/eps.json diff --git a/.gitignore b/.gitignore index 25be190a..abff12a6 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ demos kind *.snap /stresser -__debug_bin* \ No newline at end of file +__debug_bin* +fg.yaml \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 72fdf6e6..da766b8b 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -11,6 +11,8 @@ run: tests: true linters: + disable: + - staticcheck enable: - sloglint - bodyclose diff --git a/Makefile b/Makefile index bfb1ce21..d193f5bd 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ NAME := k9s -VERSION ?= v0.50.5 +VERSION ?= v0.50.6 PACKAGE := github.com/derailed/$(NAME) OUTPUT_BIN ?= execs/${NAME} GO_FLAGS ?= diff --git a/change_logs/release_v0.50.6.md b/change_logs/release_v0.50.6.md new file mode 100644 index 00000000..a3fd8274 --- /dev/null +++ b/change_logs/release_v0.50.6.md @@ -0,0 +1,39 @@ + + +# Release v0.50.6 + +## Notes + +Thank you to all that contributed with flushing out issues and enhancements for K9s! +I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev +and see if we're happier with some of the fixes! +If you've filed an issue please help me verify and close. + +Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated! +Also big thanks to all that have allocated their own time to help others on both slack and on this repo!! + +As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey, +please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer) + +On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA) + +## Maintenance Release! + +--- + +## Resolved Issues + +* [#3334](https://github.com/derailed/k9s/issues/3334) Watcher failed for events.k8s.io/v1/events -- expecting a meta table but got *unstructured.Unstructure + +--- + +## Contributed PRs + +Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!! + +* [#3332](https://github.com/derailed/k9s/pull/3332) fix: pre-check for get permissions only on port-forward +* [#3311](https://github.com/derailed/k9s/pull/3311) Fix concurrent read writes +* [#3310](https://github.com/derailed/k9s/pull/3310) fix: use full path of date to avoid conflict + +--- + © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) \ No newline at end of file diff --git a/go.mod b/go.mod index 5dd573c4..f5991e6a 100644 --- a/go.mod +++ b/go.mod @@ -30,15 +30,14 @@ require ( golang.org/x/text v0.24.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.17.3 - k8s.io/api v0.32.3 - k8s.io/apiextensions-apiserver v0.32.3 + k8s.io/api v0.33.0 + k8s.io/apiextensions-apiserver v0.33.0 k8s.io/apimachinery v0.33.0 - k8s.io/cli-runtime v0.32.3 - k8s.io/client-go v0.32.3 + k8s.io/cli-runtime v0.33.0 + k8s.io/client-go v0.33.0 k8s.io/klog/v2 v2.130.1 - k8s.io/kubectl v0.32.3 - k8s.io/kubernetes v1.33.0 - k8s.io/metrics v0.32.3 + k8s.io/kubectl v0.33.0 + k8s.io/metrics v0.33.0 sigs.k8s.io/yaml v1.4.0 ) @@ -171,7 +170,6 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/gohugoio/hashstructure v0.5.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.6.9 // indirect @@ -330,7 +328,7 @@ require ( golang.org/x/crypto v0.37.0 // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/net v0.39.0 // indirect - golang.org/x/oauth2 v0.27.0 // indirect + golang.org/x/oauth2 v0.29.0 // indirect golang.org/x/sync v0.13.0 // indirect golang.org/x/sys v0.32.0 // indirect golang.org/x/term v0.31.0 // indirect @@ -348,8 +346,9 @@ require ( gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gorm.io/gorm v1.25.12 // indirect - k8s.io/apiserver v0.32.3 // indirect - k8s.io/component-base v0.32.3 // indirect + k8s.io/apiserver v0.33.0 // indirect + k8s.io/component-base v0.33.0 // indirect + k8s.io/component-helpers v0.33.0 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect modernc.org/libc v1.62.1 // indirect diff --git a/go.sum b/go.sum index 62365371..58c37e67 100644 --- a/go.sum +++ b/go.sum @@ -1148,8 +1148,6 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/licensecheck v0.3.1 h1:QoxgoDkaeC4nFrtGN1jV7IPmDCHFNIVh54e5hSt6sPs= github.com/google/licensecheck v0.3.1/go.mod h1:ORkR35t/JjW+emNKtfJDII0zlciG9JgbT7SmsohlHmY= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= @@ -2006,8 +2004,8 @@ golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= -golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2596,30 +2594,30 @@ honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= -k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= +k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= +k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= +k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= +k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.32.3 h1:kOw2KBuHOA+wetX1MkmrxgBr648ksz653j26ESuWNY8= -k8s.io/apiserver v0.32.3/go.mod h1:q1x9B8E/WzShF49wh3ADOh6muSfpmFL0I2t+TG0Zdgc= -k8s.io/cli-runtime v0.32.3 h1:khLF2ivU2T6Q77H97atx3REY9tXiA3OLOjWJxUrdvss= -k8s.io/cli-runtime v0.32.3/go.mod h1:vZT6dZq7mZAca53rwUfdFSZjdtLyfF61mkf/8q+Xjak= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= -k8s.io/component-base v0.32.3 h1:98WJvvMs3QZ2LYHBzvltFSeJjEx7t5+8s71P7M74u8k= -k8s.io/component-base v0.32.3/go.mod h1:LWi9cR+yPAv7cu2X9rZanTiFKB2kHA+JjmhkKjCZRpI= +k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc= +k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8= +k8s.io/cli-runtime v0.33.0 h1:Lbl/pq/1o8BaIuyn+aVLdEPHVN665tBAXUePs8wjX7c= +k8s.io/cli-runtime v0.33.0/go.mod h1:QcA+r43HeUM9jXFJx7A+yiTPfCooau/iCcP1wQh4NFw= +k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= +k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= +k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= +k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= +k8s.io/component-helpers v0.33.0 h1:0AdW0A0mIgljLgtG0hJDdJl52PPqTrtMgOgtm/9i/Ys= +k8s.io/component-helpers v0.33.0/go.mod h1:9SRiXfLldPw9lEEuSsapMtvT8j/h1JyFFapbtybwKvU= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/kubectl v0.32.3 h1:VMi584rbboso+yjfv0d8uBHwwxbC438LKq+dXd5tOAI= -k8s.io/kubectl v0.32.3/go.mod h1:6Euv2aso5GKzo/UVMacV6C7miuyevpfI91SvBvV9Zdg= -k8s.io/kubernetes v1.33.0 h1:BP5Y5yIzUZVeBuE/ESZvnw6TNxjXbLsCckIkljE+R0U= -k8s.io/kubernetes v1.33.0/go.mod h1:2nWuPk0seE4+6sd0x60wQ6rYEXcV7SoeMbU0YbFm/5k= -k8s.io/metrics v0.32.3 h1:2vsBvw0v8rIIlczZ/lZ8Kcqk9tR6Fks9h+dtFNbc2a4= -k8s.io/metrics v0.32.3/go.mod h1:9R1Wk5cb+qJpCQon9h52mgkVCcFeYxcY+YkumfwHVCU= +k8s.io/kubectl v0.33.0 h1:HiRb1yqibBSCqic4pRZP+viiOBAnIdwYDpzUFejs07g= +k8s.io/kubectl v0.33.0/go.mod h1:gAlGBuS1Jq1fYZ9AjGWbI/5Vk3M/VW2DK4g10Fpyn/0= +k8s.io/metrics v0.33.0 h1:sKe5sC9qb1RakMhs8LWYNuN2ne6OTCWexj8Jos3rO2Y= +k8s.io/metrics v0.33.0/go.mod h1:XewckTFXmE2AJiP7PT3EXaY7hi7bler3t2ZLyOdQYzU= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= diff --git a/internal/client/gvrs.go b/internal/client/gvrs.go index ada91c79..c4a3f912 100644 --- a/internal/client/gvrs.go +++ b/internal/client/gvrs.go @@ -20,6 +20,9 @@ var ( NodeGVR = NewGVR("v1/nodes") SvcGVR = NewGVR("v1/services") + // Discovery... + EpsGVR = NewGVR("discovery.k8s.io/v1/endpointslices") + // Autoscaling... HpaGVR = NewGVR("autoscaling/v1/horizontalpodautoscalers") diff --git a/internal/model/pulse.go b/internal/model/pulse.go index a36c6af6..1916937a 100644 --- a/internal/model/pulse.go +++ b/internal/model/pulse.go @@ -3,7 +3,6 @@ package model import ( "context" "fmt" - "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" @@ -11,14 +10,12 @@ import ( "github.com/derailed/k9s/internal/health" ) -const defaultRefreshRate = 1 * time.Minute - // PulseListener represents a health model listener. type PulseListener interface { // PulseChanged notifies the model data changed. PulseChanged(*health.Check) - // TreeFailed notifies the health check failed. + // PulseFailed notifies the health check failed. PulseFailed(error) // MetricsChanged update metrics time series. @@ -27,18 +24,16 @@ type PulseListener interface { // Pulse tracks multiple resources health. type Pulse struct { - gvr *client.GVR - namespace string - listeners []PulseListener - refreshRate time.Duration - health *PulseHealth + gvr *client.GVR + namespace string + listeners []PulseListener + health *PulseHealth } // NewPulse returns a new pulse. func NewPulse(gvr *client.GVR) *Pulse { return &Pulse{ - gvr: gvr, - refreshRate: defaultRefreshRate, + gvr: gvr, } } diff --git a/internal/model/pulse_health.go b/internal/model/pulse_health.go index b43251d3..b2341c81 100644 --- a/internal/model/pulse_health.go +++ b/internal/model/pulse_health.go @@ -10,6 +10,8 @@ import ( "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/slogs" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) const pulseRate = 15 * time.Second @@ -96,6 +98,7 @@ func (h *PulseHealth) Watch(ctx context.Context, ns string) HealthChan { } func (h *PulseHealth) checkPulse(ctx context.Context, ns string, c HealthChan) error { + slog.Debug("Checking pulses...") for _, gvr := range PulseGVRs { check, err := h.check(ctx, ns, gvr) if err != nil { @@ -110,8 +113,8 @@ func (h *PulseHealth) check(ctx context.Context, ns string, gvr *client.GVR) (He meta, ok := Registry[gvr] if !ok { meta = ResourceMeta{ - DAO: &dao.Table{}, - Renderer: &render.Generic{}, + DAO: new(dao.Table), + Renderer: new(render.Table), } } if meta.DAO == nil { @@ -124,11 +127,31 @@ func (h *PulseHealth) check(ctx context.Context, ns string, gvr *client.GVR) (He return HealthPoint{}, err } c := HealthPoint{GVR: gvr, Total: len(oo)} - for _, o := range oo { - if err := meta.Renderer.Healthy(ctx, o); err != nil { - c.Faults++ + if isTable(oo) { + ta := oo[0].(*metav1.Table) + c.Total = len(ta.Rows) + for _, row := range ta.Rows { + if err := meta.Renderer.Healthy(ctx, row); err != nil { + c.Faults++ + } + } + } else { + for _, o := range oo { + if err := meta.Renderer.Healthy(ctx, o); err != nil { + c.Faults++ + } } } + slog.Debug("Checked", slogs.GVR, gvr, slogs.Config, c) return c, nil } + +func isTable(oo []runtime.Object) bool { + if len(oo) == 0 || len(oo) > 1 { + return false + } + _, ok := oo[0].(*metav1.Table) + + return ok +} diff --git a/internal/model/registry.go b/internal/model/registry.go index 193de099..0a393bea 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -84,6 +84,11 @@ var Registry = map[*client.GVR]ResourceMeta{ Renderer: new(render.Alias), }, + // Discovery... + client.EpsGVR: { + Renderer: new(render.EndpointSlice), + }, + // Core... client.EpGVR: { Renderer: new(render.Endpoints), @@ -124,6 +129,7 @@ var Registry = map[*client.GVR]ResourceMeta{ Renderer: new(render.PersistentVolumeClaim), }, client.EvGVR: { + DAO: new(dao.Table), Renderer: new(render.Event), }, diff --git a/internal/model1/table_data.go b/internal/model1/table_data.go index 8b6c516f..0ddaa8e1 100644 --- a/internal/model1/table_data.go +++ b/internal/model1/table_data.go @@ -272,7 +272,7 @@ func (t *TableData) Render(_ context.Context, r Renderer, oo []runtime.Object) e t.Update(rows) t.SetHeader(t.namespace, r.Header(t.namespace)) if t.HeaderCount() == 0 { - return fmt.Errorf("fail to list resource %s", t.gvr) + return fmt.Errorf("no data found for resource %s", t.gvr) } return nil diff --git a/internal/port/pfs.go b/internal/port/pfs.go index 9dd9e760..533f3e46 100644 --- a/internal/port/pfs.go +++ b/internal/port/pfs.go @@ -40,7 +40,7 @@ func (aa PFAnns) ToTunnels(address string, _ ContainerPortSpecs, available PortC return pts, err } if !available(pt) { - return pts, fmt.Errorf("Port %s is not available on host", pt.LocalPort) + return pts, fmt.Errorf("port %s is not available on host", pt.LocalPort) } pts = append(pts, pt) } diff --git a/internal/render/ep_test.go b/internal/render/ep_test.go index f9d618b3..e65667cf 100644 --- a/internal/render/ep_test.go +++ b/internal/render/ep_test.go @@ -17,6 +17,6 @@ func TestEndpointsRender(t *testing.T) { r := model1.NewRow(4) require.NoError(t, c.Render(load(t, "ep"), "", &r)) - assert.Equal(t, "default/dictionary1", r.ID) - assert.Equal(t, model1.Fields{"default", "dictionary1", ""}, r.Fields[:3]) + assert.Equal(t, "ns-1/blee", r.ID) + assert.Equal(t, model1.Fields{"ns-1", "blee", "10.0.0.67:8080"}, r.Fields[:3]) } diff --git a/internal/render/eps.go b/internal/render/eps.go new file mode 100644 index 00000000..4bdcc72f --- /dev/null +++ b/internal/render/eps.go @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render + +import ( + "fmt" + "strconv" + "strings" + + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/model1" + discoveryv1 "k8s.io/api/discovery/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +var defaultEPsHeader = model1.Header{ + model1.HeaderColumn{Name: "NAMESPACE"}, + model1.HeaderColumn{Name: "NAME"}, + model1.HeaderColumn{Name: "ADDRESSTYPE"}, + model1.HeaderColumn{Name: "PORTS"}, + model1.HeaderColumn{Name: "ENDPOINTS"}, + model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}}, +} + +// EndpointSlice renders a K8s EndpointSlice to screen. +type EndpointSlice struct { + Base +} + +// Header returns a header row. +func (e EndpointSlice) Header(_ string) model1.Header { + return e.doHeader(defaultEPsHeader) +} + +// Render renders a K8s resource to screen. +func (e EndpointSlice) Render(o any, ns string, row *model1.Row) error { + if err := e.defaultRow(o, ns, row); err != nil { + return err + } + if e.specs.isEmpty() { + return nil + } + cols, err := e.specs.realize(o.(*unstructured.Unstructured), defaultEPsHeader, row) + if err != nil { + return err + } + cols.hydrateRow(row) + + return nil +} + +func (e EndpointSlice) defaultRow(o any, ns string, r *model1.Row) error { + raw, ok := o.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("expected Unstructured, but got %T", o) + } + var eps discoveryv1.EndpointSlice + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &eps) + if err != nil { + return err + } + + r.ID = client.MetaFQN(&eps.ObjectMeta) + r.Fields = make(model1.Fields, 0, len(e.Header(ns))) + r.Fields = model1.Fields{ + eps.Namespace, + eps.Name, + string(eps.AddressType), + toPorts(eps.Ports), + toEPss(eps.Endpoints), + ToAge(eps.GetCreationTimestamp()), + } + + return nil +} + +// ---------------------------------------------------------------------------- +// Helpers... + +func toEPss(ee []discoveryv1.Endpoint) string { + if len(ee) == 0 { + return UnsetValue + } + + aa := make([]string, 0, len(ee)) + for _, e := range ee { + aa = append(aa, e.Addresses...) + } + + return strings.Join(aa, ",") +} + +func toPorts(ee []discoveryv1.EndpointPort) string { + if len(ee) == 0 { + return UnsetValue + } + + aa := make([]string, 0, len(ee)) + for _, e := range ee { + if e.Port != nil { + aa = append(aa, strconv.Itoa(int(*e.Port))) + } + } + + return strings.Join(aa, ",") +} diff --git a/internal/render/eps_test.go b/internal/render/eps_test.go new file mode 100644 index 00000000..bb4828be --- /dev/null +++ b/internal/render/eps_test.go @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + +package render_test + +import ( + "testing" + + "github.com/derailed/k9s/internal/model1" + "github.com/derailed/k9s/internal/render" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEndpointSliceRender(t *testing.T) { + c := render.EndpointSlice{} + r := model1.NewRow(4) + + require.NoError(t, c.Render(load(t, "eps"), "", &r)) + assert.Equal(t, "blee/fred", r.ID) + assert.Equal(t, model1.Fields{"blee", "fred", "IPv4", "4244", "172.20.0.2,172.20.0.3"}, r.Fields[:5]) +} diff --git a/internal/render/ev.go b/internal/render/ev.go index ec7791b0..e5e2f701 100644 --- a/internal/render/ev.go +++ b/internal/render/ev.go @@ -8,9 +8,7 @@ import ( "log/slog" "github.com/derailed/k9s/internal/slogs" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - api "k8s.io/kubernetes/pkg/apis/core" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // Event renders a event resource to screen. @@ -20,19 +18,14 @@ type Event struct { // Healthy checks component health. func (*Event) Healthy(_ context.Context, o any) error { - u, ok := o.(*unstructured.Unstructured) + r, ok := o.(metav1.TableRow) if !ok { - slog.Error("Expected Unstructured", slogs.Type, fmt.Sprintf("%T", o)) + slog.Error("Expected TableRow", slogs.Type, fmt.Sprintf("%T", o)) return nil } - var ev api.Event - err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &ev) - if err != nil { - slog.Error("Failed to convert unstructured to Node", slogs.Error, err) - return nil - } - if ev.Type != "Normal" { - return fmt.Errorf("event is not normal: %s", ev.Type) + idx := 2 + if idx < len(r.Cells) && r.Cells[idx] != "Normal" { + return fmt.Errorf("event is not normal: %s", r.Cells[idx]) } return nil diff --git a/internal/render/testdata/ep.json b/internal/render/testdata/ep.json index 659472af..f0e8e24f 100644 --- a/internal/render/testdata/ep.json +++ b/internal/render/testdata/ep.json @@ -3,10 +3,29 @@ "kind": "Endpoints", "metadata": { "creationTimestamp": "2019-07-10T23:10:43Z", - "name": "dictionary1", - "namespace": "default", - "resourceVersion": "36684456", - "selfLink": "/api/v1/namespaces/default/endpoints/dictionary1", - "uid": "f119c74f-a367-11e9-990f-42010a800218" - } + "name": "blee", + "namespace": "ns-1" + }, + "subsets": [ + { + "addresses": [ + { + "ip": "10.0.0.67", + "nodeName": "n-1", + "targetRef": { + "kind": "Pod", + "name": "blah", + "namespace": "blee" + } + } + ], + "ports": [ + { + "name": "http", + "port": 8080, + "protocol": "TCP" + } + ] + } +] } \ No newline at end of file diff --git a/internal/render/testdata/eps.json b/internal/render/testdata/eps.json new file mode 100644 index 00000000..8f4366e5 --- /dev/null +++ b/internal/render/testdata/eps.json @@ -0,0 +1,51 @@ +{ + "apiVersion": "discovery.k8s.io/v1", + "kind": "EndpointSlice", + "metadata": { + "creationTimestamp": "2025-04-17T22:14:13Z", + "name": "fred", + "namespace": "blee" + }, + "addressType": "IPv4", + "endpoints": [ + { + "addresses": [ + "172.20.0.2" + ], + "conditions": { + "ready": true, + "serving": true, + "terminating": false + }, + "nodeName": "n-1", + "targetRef": { + "kind": "Pod", + "name": "zorg", + "namespace": "kube-system" + } + }, + { + "addresses": [ + "172.20.0.3" + ], + "conditions": { + "ready": true, + "serving": true, + "terminating": false + }, + "nodeName": "n-1", + "targetRef": { + "kind": "Pod", + "name": "zorg", + "namespace": "kube-system" + } + } + ], + "ports": [ + { + "name": "peer-service", + "port": 4244, + "protocol": "TCP" + } + ] +} \ No newline at end of file diff --git a/internal/render/types.go b/internal/render/types.go index 3b1c37ae..9d23756c 100644 --- a/internal/render/types.go +++ b/internal/render/types.go @@ -45,7 +45,7 @@ const ( UnknownValue = "" // UnsetValue represent an unset value. - UnsetValue = "" + UnsetValue = "" // ZeroValue represents a zero value. ZeroValue = "0" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 70ef57f0..35435935 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core22 -version: 'v0.50.5' +version: 'v0.50.6' summary: K9s is a CLI to view and manage your Kubernetes clusters. description: | K9s is a CLI to view and manage your Kubernetes clusters. By leveraging a terminal UI, you can easily traverse Kubernetes resources and view the state of your clusters in a single powerful session.