diff --git a/Makefile b/Makefile index ae69cd8b..92b3ec53 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H: else DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") endif -VERSION ?= v0.40.0 +VERSION ?= v0.40.1 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.40.1.md b/change_logs/release_v0.40.1.md new file mode 100644 index 00000000..7660c687 --- /dev/null +++ b/change_logs/release_v0.40.1.md @@ -0,0 +1,64 @@ + + +# Release v0.40.1 + +## 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/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +😳 Aye! Buzz kill on the 0.40.0 aftermath... 🙀 👻 + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [K9s v0.40.0 Sneak peek](https://youtu.be/iy6RDozAM4A) +* [K9s v0.31.0 Configs+Sneak peek](https://youtu.be/X3444KfjguE) +* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4) +* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU) + +--- + +## Resolved Issues + +* [#3113](https://github.com/derailed/k9s/issues/3113) 0.40.0 can't retain temporary view sort +* [#3111](https://github.com/derailed/k9s/issues/3111) k9s can't describe or print YAML for HPAs in all namespaces view +* [#2966](https://github.com/derailed/k9s/issues/2966) Go to the Contexts page and filter, contexts that are matched will be filtered ou +* [#2962](https://github.com/derailed/k9s/issues/2962) Small colour/filtering related bug +* [#2961](https://github.com/derailed/k9s/issues/2961) Drain node with the -disable-eviction +* [#2958](https://github.com/derailed/k9s/issues/2958) Restart count in container view associated with the wrong container +* [#2945](https://github.com/derailed/k9s/issues/2945) Could we add ServiceAccount Column in v1/POD view + +--- + +## 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!! + +* [#3094](https://github.com/derailed/k9s/pull/3094) Log in as root to the node. +* [#3033](https://github.com/derailed/k9s/pull/3033) Skip cache invalidation on failed connection +* [#2965](https://github.com/derailed/k9s/pull/2965) Make menu foreground style configurable through skins +* [#2952](https://github.com/derailed/k9s/pull/2952) A modest attempt to improve the logo aesthetics +* [#2833](https://github.com/derailed/k9s/pull/2833) allow scaling custom resource +* [#2799](https://github.com/derailed/k9s/pull/2799) feat(app): add history navigation with [ and ], most recent command with - +* [#2719](https://github.com/derailed/k9s/pull/2719) fix: stop table header cells from being selectable +* [#2865](https://github.com/derailed/k9s/pull/2865) Feature/DisableAutoscroll + +--- + + © 2024 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/internal/dao/dynamic.go b/internal/dao/dynamic.go index a1bd321f..4af90160 100644 --- a/internal/dao/dynamic.go +++ b/internal/dao/dynamic.go @@ -4,7 +4,6 @@ package dao import ( "context" - "errors" "fmt" "strings" @@ -24,13 +23,30 @@ type Dynamic struct { Generic } +// Get returns a given resource as a table object. func (d *Dynamic) Get(ctx context.Context, path string) (runtime.Object, error) { - return nil, errors.New("Not implemented") + oo, err := d.toTable(ctx, path) + if err != nil || len(oo) == 0 { + return nil, err + } + + return oo[0], nil } +// List returns a collection of resources as one or more table objects. func (d *Dynamic) List(ctx context.Context, ns string) ([]runtime.Object, error) { + return d.toTable(ctx, ns) +} + +func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, error) { strLabel, _ := ctx.Value(internal.KeyLabels).(string) + opts := []string{d.gvr.R()} + ns, n := client.Namespaced(fqn) + if n != "" { + opts = append(opts, n) + } + allNS := client.IsAllNamespaces(ns) flags := cmdutil.NewMatchVersionFlags(d.getFactory().Client().Config().Flags()) f := cmdutil.NewFactory(flags) @@ -40,7 +56,7 @@ func (d *Dynamic) List(ctx context.Context, ns string) ([]runtime.Object, error) LabelSelectorParam(strLabel). FieldSelectorParam(""). RequestChunksOf(0). - ResourceTypeOrNameArgs(true, d.gvr.R()). + ResourceTypeOrNameArgs(true, opts...). ContinueOnError(). Latest(). Flatten(). diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 0e707a8c..8c6c75ed 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -10,6 +10,7 @@ import ( "strings" "sync" + "github.com/derailed/k9s/internal/client" "github.com/rs/zerolog/log" apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -17,8 +18,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/derailed/k9s/internal/client" + "k8s.io/apimachinery/pkg/util/sets" ) const ( @@ -32,19 +32,19 @@ const ( // MetaAccess tracks resources metadata. var MetaAccess = NewMeta() -var stdGroups = map[string]struct{}{ - "apps/v1": {}, - "autoscaling/v1": {}, - "autoscaling/v2": {}, - "autoscaling/v2beta1": {}, - "autoscaling/v2beta2": {}, - "batch/v1": {}, - "batch/v1beta1": {}, - "extensions/v1beta1": {}, - "policy/v1beta1": {}, - "policy/v1": {}, - "v1": {}, -} +var stdGroups = sets.New[string]( + "apps/v1", + "autoscaling/v1", + "autoscaling/v2", + "autoscaling/v2beta1", + "autoscaling/v2beta2", + "batch/v1", + "batch/v1beta1", + "extensions/v1beta1", + "policy/v1beta1", + "policy/v1", + "v1", +) func (m ResourceMetas) clear() { for k := range m { @@ -381,20 +381,15 @@ func loadPreferred(f Factory, m ResourceMetas) error { } func isStandardGroup(gv string) bool { - if _, ok := stdGroups[gv]; ok { - return true - } - - return strings.Contains(gv, "k8s.io") + return stdGroups.Has(gv) || strings.Contains(gv, "k8s.io") } -var deprecatedGVRs = map[client.GVR]struct{}{ - client.NewGVR("extensions/v1beta1/ingresses"): {}, -} +var deprecatedGVRs = sets.New[client.GVR]( + client.NewGVR("extensions/v1beta1/ingresses"), +) func isDeprecated(gvr client.GVR) bool { - _, ok := deprecatedGVRs[gvr] - return ok + return deprecatedGVRs.Has(gvr) } // loadCRDs Wait for the cache to synced and then add some additional properties to CRD. diff --git a/internal/model/registry.go b/internal/model/registry.go index ab4bb774..97814706 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -84,9 +84,9 @@ var Registry = map[string]ResourceMeta{ }, // Core... - // "v1/endpoints": { - // Renderer: &render.Endpoints{}, - // }, + "v1/endpoints": { + Renderer: &render.Endpoints{}, + }, "v1/pods": { DAO: &dao.Pod{}, Renderer: render.NewPod(), diff --git a/internal/render/cust_cols.go b/internal/render/cust_cols.go index acf541ed..7fde0e72 100644 --- a/internal/render/cust_cols.go +++ b/internal/render/cust_cols.go @@ -17,8 +17,10 @@ import ( "k8s.io/client-go/util/jsonpath" ) +// ColsSpecs represents a collection of column specification ie NAME:spec|flags. type ColsSpecs []string +// NewColsSpecs returns a new instance. func NewColsSpecs(cols ...string) ColsSpecs { return ColsSpecs(cols) } @@ -40,6 +42,7 @@ func (cc ColsSpecs) parseSpecs() (ColumnSpecs, error) { return specs, nil } +// RenderedCols tracks a collection of column header and cust column parse expression. type RenderedCols []RenderedCol func (rr RenderedCols) hydrateRow(row *model1.Row) { @@ -50,6 +53,7 @@ func (rr RenderedCols) hydrateRow(row *model1.Row) { row.Fields = ff } +// HasHeader checks if a given header is present in the collection. func (rr RenderedCols) HasHeader(n string) bool { for _, r := range rr { if r.has(n) { @@ -60,26 +64,31 @@ func (rr RenderedCols) HasHeader(n string) bool { return false } +// RenderedCol represents a column header and a column spec. type RenderedCol struct { Header model1.HeaderColumn Value string } +// Has checks if the header column match the given name. func (r RenderedCol) has(n string) bool { return r.Header.Name == n } +// ColumnSpec tracks a header column and an options cust column spec. type ColumnSpec struct { Header model1.HeaderColumn Spec string } +// ColumnSpecs tracks a collection of column specs. type ColumnSpecs []ColumnSpec func (c ColumnSpecs) isEmpty() bool { return len(c) == 0 } +// Header builds a new header that is a super set of custom and/or default header. func (cc ColumnSpecs) Header(rh model1.Header) model1.Header { hh := make(model1.Header, 0, len(cc)) for _, h := range cc { diff --git a/internal/render/table.go b/internal/render/table.go index 2b8f52ed..0bc07e92 100644 --- a/internal/render/table.go +++ b/internal/render/table.go @@ -92,18 +92,26 @@ func (t *Table) Render(o any, ns string, r *model1.Row) error { func (t *Table) defaultRow(row *metav1.TableRow, ns string, r *model1.Row) error { th := t.defaultHeader() ons, name := ns, UnknownValue - if row.Object.Object != nil { - m, _ := meta.Accessor(row.Object.Object) - if m != nil { + switch { + case row.Object.Object != nil: + if m, _ := meta.Accessor(row.Object.Object); m != nil { ons, name = m.GetNamespace(), m.GetName() } - } else if idx, ok := th.IndexOf("NAME", true); ok && idx >= 0 { - name = row.Cells[idx].(string) - if idx, ok := th.IndexOf("NAMESPACE", true); ok && idx >= 0 { - ons = row.Cells[idx].(string) + case row.Object.Raw != nil: + var pm metav1.PartialObjectMetadata + if err := json.Unmarshal(row.Object.Raw, &pm); err != nil { + return err + } + ons, name = pm.Namespace, pm.Name + default: + if idx, ok := th.IndexOf("NAME", true); ok && idx >= 0 { + name = row.Cells[idx].(string) + if idx, ok := th.IndexOf("NAMESPACE", true); ok && idx >= 0 { + ons = row.Cells[idx].(string) + } } - } else { } + if client.IsClusterWide(ons) { ons = client.ClusterScope } diff --git a/internal/ui/table.go b/internal/ui/table.go index 56d6bb20..c8ceafe9 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -157,9 +157,12 @@ func (t *Table) GVR() client.GVR { return t.gvr } func (t *Table) ViewSettingsChanged(vs *config.ViewSetting) { if t.setViewSetting(vs) { if vs == nil { - t.setSortCol(model1.SortColumn{}) + if !t.getMSort() { + t.setSortCol(model1.SortColumn{}) + } + } else { + t.setMSort(false) } - t.setMSort(false) t.Refresh() } } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 93e5755e..00423a23 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core22 -version: 'v0.40.0' +version: 'v0.40.1' 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.