Rel v0.40.4 (#3130)
* fix#3119 - non alpha cust col * Address issues: o fix#3118 - wrong res shown on alias o fix#3120 - shuffled cols after sort * fix#3122 - view event not sorted * rel v0.40.4mine
parent
f8f112933d
commit
e44c223e91
|
|
@ -1,7 +1,7 @@
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# The base image for building the k9s binary
|
# The base image for building the k9s binary
|
||||||
|
|
||||||
FROM golang:1.23.6-alpine3.20 AS build
|
FROM golang:1.24.0-alpine3.21 AS build
|
||||||
|
|
||||||
WORKDIR /k9s
|
WORKDIR /k9s
|
||||||
COPY go.mod go.sum main.go Makefile ./
|
COPY go.mod go.sum main.go Makefile ./
|
||||||
|
|
@ -13,8 +13,8 @@ RUN apk --no-cache add --update make libx11-dev git gcc libc-dev curl \
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Build the final Docker image
|
# Build the final Docker image
|
||||||
|
|
||||||
FROM alpine:3.21.2
|
FROM alpine:3.21.3
|
||||||
ARG KUBECTL_VERSION="v1.31.2"
|
ARG KUBECTL_VERSION="v1.32.2"
|
||||||
|
|
||||||
COPY --from=build /k9s/execs/k9s /bin/k9s
|
COPY --from=build /k9s/execs/k9s /bin/k9s
|
||||||
RUN apk --no-cache add --update ca-certificates \
|
RUN apk --no-cache add --update ca-certificates \
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
|
||||||
else
|
else
|
||||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
endif
|
endif
|
||||||
VERSION ?= v0.40.3
|
VERSION ?= v0.40.4
|
||||||
IMG_NAME := derailed/k9s
|
IMG_NAME := derailed/k9s
|
||||||
IMAGE := ${IMG_NAME}:${VERSION}
|
IMAGE := ${IMG_NAME}:${VERSION}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,8 @@ Your donations will go a long way in keeping our servers lights on and beers in
|
||||||
|
|
||||||
## Demo Videos/Recordings
|
## Demo Videos/Recordings
|
||||||
|
|
||||||
|
* [K9s v0.40.0 -Column Blow- 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)
|
* [K9s v0.30.0 Sneak peek](https://youtu.be/mVBc1XneRJ4)
|
||||||
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
|
* [Vulnerability Scans](https://youtu.be/ULkl0MsaidU)
|
||||||
* [K9s v0.29.0](https://youtu.be/oiU3wmoAkBo)
|
* [K9s v0.29.0](https://youtu.be/oiU3wmoAkBo)
|
||||||
|
|
@ -640,6 +642,11 @@ The new column syntax is as follows:
|
||||||
Where `:json_parse_expression` represents an expression to pull a specific snippet out of the resource manifest.
|
Where `:json_parse_expression` represents an expression to pull a specific snippet out of the resource manifest.
|
||||||
Similar to `kubectl -o custom-columns` command. This expression is optional.
|
Similar to `kubectl -o custom-columns` command. This expression is optional.
|
||||||
|
|
||||||
|
> IMPORTANT! Columns must be valid YAML strings. Thus if your column definition contains non-alpha chars
|
||||||
|
> they must figure with either single/double quotes or escaped via `\`
|
||||||
|
|
||||||
|
> NOTE! Be sure to watch k9s logs as any issues with the custom views specification are only surfaced in the logs.
|
||||||
|
|
||||||
Additionally, you can specify column attributes to further tailor the column rendering.
|
Additionally, you can specify column attributes to further tailor the column rendering.
|
||||||
To use this you will need to add a `|` indicator followed by your rendering bits.
|
To use this you will need to add a `|` indicator followed by your rendering bits.
|
||||||
You can have one or more of the following attributes:
|
You can have one or more of the following attributes:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
|
||||||
|
|
||||||
|
# Release v0.40.4
|
||||||
|
|
||||||
|
## 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! Continued Buzz kill on the 0.40.0 aftermath 🙀 👻
|
||||||
|
|
||||||
|
Likely additional `disturbance in the farce` might be observed.
|
||||||
|
Thank you all for giving this drop a rinse and reporting back!! 😍
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Videos Are In The Can!
|
||||||
|
|
||||||
|
Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content...
|
||||||
|
|
||||||
|
* [K9s v0.40.0 -Column Blow- 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
|
||||||
|
|
||||||
|
* [#3122](https://github.com/derailed/k9s/issues/3122) Viewing events is no longer sorted by LAST SEEN
|
||||||
|
* [#3120](https://github.com/derailed/k9s/issues/3120) Custom View Column Mismatch in K9s: Shuffled Values in Pods View
|
||||||
|
* [#3119](https://github.com/derailed/k9s/issues/3119) Custom Views Fail to Load with % in Column Names
|
||||||
|
* [#3118](https://github.com/derailed/k9s/issues/3118) selecting an alias, the wrong resources are being shown
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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!!
|
||||||
|
|
||||||
|
* [#3123](https://github.com/derailed/k9s/pull/3123) update regex to allow '%' and '/' in column names
|
||||||
|
|
||||||
|
|
||||||
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2024 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)
|
||||||
|
|
@ -68,6 +68,10 @@ func (g GVR) FQN(n string) string {
|
||||||
|
|
||||||
// AsResourceName returns a resource . separated descriptor in the shape of kind.version.group.
|
// AsResourceName returns a resource . separated descriptor in the shape of kind.version.group.
|
||||||
func (g GVR) AsResourceName() string {
|
func (g GVR) AsResourceName() string {
|
||||||
|
if g.g == "" {
|
||||||
|
return g.r
|
||||||
|
}
|
||||||
|
|
||||||
return g.r + "." + g.v + "." + g.g
|
return g.r + "." + g.v + "." + g.g
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,9 +108,9 @@ func TestGVRAsResourceName(t *testing.T) {
|
||||||
e string
|
e string
|
||||||
}{
|
}{
|
||||||
"full": {"apps/v1/deployments", "deployments.v1.apps"},
|
"full": {"apps/v1/deployments", "deployments.v1.apps"},
|
||||||
"core": {"v1/pods", "pods.v1."},
|
"core": {"v1/pods", "pods"},
|
||||||
"k9s": {"users", "users.."},
|
"k9s": {"users", "users"},
|
||||||
"empty": {"", ".."},
|
"empty": {"", ""},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
|
|
|
||||||
|
|
@ -108,10 +108,9 @@ func (a *Aliases) Define(gvr string, aliases ...string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, alias := range aliases {
|
for _, alias := range aliases {
|
||||||
if _, ok := a.Alias[alias]; ok {
|
if _, ok := a.Alias[alias]; !ok {
|
||||||
continue
|
a.Alias[alias] = gvr
|
||||||
}
|
}
|
||||||
a.Alias[alias] = gvr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,9 +116,14 @@ func (a *Alias) load(path string) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
a.Define(gvrStr, strings.ToLower(meta.Kind), meta.Name)
|
a.Define(gvrStr, gvr.AsResourceName())
|
||||||
if meta.SingularName != "" {
|
|
||||||
a.Define(gvrStr, meta.SingularName)
|
// Allow single shot commands for k8s resources only!
|
||||||
|
if isStandardGroup(gvr.String()) {
|
||||||
|
a.Define(gvrStr, strings.ToLower(meta.Kind), meta.Name)
|
||||||
|
if meta.SingularName != "" {
|
||||||
|
a.Define(gvrStr, meta.SingularName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if meta.ShortNames != nil {
|
if meta.ShortNames != nil {
|
||||||
a.Define(gvrStr, meta.ShortNames...)
|
a.Define(gvrStr, meta.ShortNames...)
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ func (d *Dynamic) List(ctx context.Context, ns string) ([]runtime.Object, error)
|
||||||
func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, error) {
|
func (d *Dynamic) toTable(ctx context.Context, fqn string) ([]runtime.Object, error) {
|
||||||
strLabel, _ := ctx.Value(internal.KeyLabels).(string)
|
strLabel, _ := ctx.Value(internal.KeyLabels).(string)
|
||||||
|
|
||||||
opts := []string{d.gvr.R()}
|
opts := []string{d.gvr.AsResourceName()}
|
||||||
ns, n := client.Namespaced(fqn)
|
ns, n := client.Namespaced(fqn)
|
||||||
if n != "" {
|
if n != "" {
|
||||||
opts = append(opts, n)
|
opts = append(opts, n)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ package model1
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReRangeFn func(int, RowEvent) bool
|
type ReRangeFn func(int, RowEvent) bool
|
||||||
|
|
@ -272,6 +274,14 @@ func (r *RowEvents) Sort(ns string, sortCol int, isDuration, numCol, isCapacity,
|
||||||
r.reindex()
|
r.reindex()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For debguging...
|
||||||
|
func (re RowEvents) Dump(msg string) {
|
||||||
|
log.Debug().Msg(msg)
|
||||||
|
for _, r := range re.events {
|
||||||
|
log.Debug().Msgf("!!YO!! %#v", r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
// RowEventSorter sorts row events by a given colon.
|
// RowEventSorter sorts row events by a given colon.
|
||||||
|
|
|
||||||
|
|
@ -20,16 +20,19 @@ import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
// SortFn represent a function that can sort columnar data.
|
||||||
// SortFn represent a function that can sort columnar data.
|
type SortFn func(rows Rows, sortCol SortColumn)
|
||||||
SortFn func(rows Rows, sortCol SortColumn)
|
|
||||||
|
|
||||||
// SortColumn represents a sortable column.
|
// SortColumn represents a sortable column.
|
||||||
SortColumn struct {
|
type SortColumn struct {
|
||||||
Name string
|
Name string
|
||||||
ASC bool
|
ASC bool
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
// IsSet checks if the sort column is set.
|
||||||
|
func (s SortColumn) IsSet() bool {
|
||||||
|
return s.Name != ""
|
||||||
|
}
|
||||||
|
|
||||||
const spacer = " "
|
const spacer = " "
|
||||||
|
|
||||||
|
|
@ -174,8 +177,11 @@ func (t *TableData) rxFilter(q string, inverse bool) (*RowEvents, error) {
|
||||||
rr := NewRowEvents(t.RowCount() / 2)
|
rr := NewRowEvents(t.RowCount() / 2)
|
||||||
ageIndex, _ := t.header.IndexOf("AGE", true)
|
ageIndex, _ := t.header.IndexOf("AGE", true)
|
||||||
t.rowEvents.Range(func(_ int, re RowEvent) bool {
|
t.rowEvents.Range(func(_ int, re RowEvent) bool {
|
||||||
ff := re.Row.Fields[startIndex:]
|
ff := make([]string, 0, len(re.Row.Fields))
|
||||||
if ageIndex >= 0 && ageIndex+1 <= len(ff) {
|
for _, r := range re.Row.Fields[startIndex:] {
|
||||||
|
ff = append(ff, r)
|
||||||
|
}
|
||||||
|
if ageIndex >= 0 && startIndex != ageIndex && ageIndex+1 <= len(ff) {
|
||||||
ff = append(ff[0:ageIndex], ff[ageIndex+1:]...)
|
ff = append(ff[0:ageIndex], ff[ageIndex+1:]...)
|
||||||
}
|
}
|
||||||
match := rx.MatchString(strings.Join(ff, spacer))
|
match := rx.MatchString(strings.Join(ff, spacer))
|
||||||
|
|
@ -321,22 +327,25 @@ func (t *TableData) Labelize(labels []string) *TableData {
|
||||||
return &data
|
return &data
|
||||||
}
|
}
|
||||||
|
|
||||||
// Customize returns a new model with customized column layout.
|
// ComputeSortCol computes the best matched sort column.
|
||||||
func (t *TableData) Customize(vs *config.ViewSetting, sc SortColumn, manual bool) (*TableData, SortColumn) {
|
func (t *TableData) ComputeSortCol(vs *config.ViewSetting, sc SortColumn, manual bool) SortColumn {
|
||||||
if vs.IsBlank() {
|
if vs.IsBlank() {
|
||||||
if sc.Name != "" {
|
if sc.Name != "" {
|
||||||
return t, sc
|
return sc
|
||||||
}
|
}
|
||||||
if psc, err := t.sortCol(vs); err == nil {
|
if psc, err := t.sortCol(vs); err == nil {
|
||||||
return t, psc
|
return psc
|
||||||
}
|
}
|
||||||
return t, sc
|
return sc
|
||||||
|
}
|
||||||
|
if manual && sc.IsSet() {
|
||||||
|
return sc
|
||||||
}
|
}
|
||||||
if s, asc, err := vs.SortCol(); err == nil {
|
if s, asc, err := vs.SortCol(); err == nil {
|
||||||
return t, SortColumn{Name: s, ASC: asc}
|
return SortColumn{Name: s, ASC: asc}
|
||||||
}
|
}
|
||||||
|
|
||||||
return t, sc
|
return sc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TableData) sortCol(vs *config.ViewSetting) (SortColumn, error) {
|
func (t *TableData) sortCol(vs *config.ViewSetting) (SortColumn, error) {
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,13 @@ func init() {
|
||||||
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
zerolog.SetGlobalLevel(zerolog.FatalLevel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTableDataCustomize(t *testing.T) {
|
func TestTableDataComputeSortCol(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
t1, e *TableData
|
t1 *TableData
|
||||||
vs config.ViewSetting
|
vs config.ViewSetting
|
||||||
sc SortColumn
|
sc SortColumn
|
||||||
wide, manual bool
|
wide, manual bool
|
||||||
|
e SortColumn
|
||||||
}{
|
}{
|
||||||
"same": {
|
"same": {
|
||||||
t1: NewTableDataWithRows(
|
t1: NewTableDataWithRows(
|
||||||
|
|
@ -37,20 +38,8 @@ func TestTableDataCustomize(t *testing.T) {
|
||||||
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
|
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
vs: config.ViewSetting{Columns: []string{"A", "B", "C"}},
|
vs: config.ViewSetting{Columns: []string{"A", "B", "C"}, SortColumn: "A:asc"},
|
||||||
e: NewTableDataWithRows(
|
e: SortColumn{Name: "A", ASC: true},
|
||||||
client.NewGVR("test"),
|
|
||||||
Header{
|
|
||||||
HeaderColumn{Name: "A"},
|
|
||||||
HeaderColumn{Name: "B"},
|
|
||||||
HeaderColumn{Name: "C"},
|
|
||||||
},
|
|
||||||
NewRowEventsWithEvts(
|
|
||||||
RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}},
|
|
||||||
RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}},
|
|
||||||
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
"wide-col": {
|
"wide-col": {
|
||||||
t1: NewTableDataWithRows(
|
t1: NewTableDataWithRows(
|
||||||
|
|
@ -66,21 +55,10 @@ func TestTableDataCustomize(t *testing.T) {
|
||||||
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
|
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
vs: config.ViewSetting{Columns: []string{"A", "B", "C"}},
|
vs: config.ViewSetting{Columns: []string{"A", "B", "C"}, SortColumn: "B:desc"},
|
||||||
e: NewTableDataWithRows(
|
e: SortColumn{Name: "B"},
|
||||||
client.NewGVR("test"),
|
|
||||||
Header{
|
|
||||||
HeaderColumn{Name: "A"},
|
|
||||||
HeaderColumn{Name: "B", Attrs: Attrs{Wide: true}},
|
|
||||||
HeaderColumn{Name: "C"},
|
|
||||||
},
|
|
||||||
NewRowEventsWithEvts(
|
|
||||||
RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}},
|
|
||||||
RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}},
|
|
||||||
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"wide": {
|
"wide": {
|
||||||
t1: NewTableDataWithRows(
|
t1: NewTableDataWithRows(
|
||||||
client.NewGVR("test"),
|
client.NewGVR("test"),
|
||||||
|
|
@ -96,28 +74,16 @@ func TestTableDataCustomize(t *testing.T) {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
wide: true,
|
wide: true,
|
||||||
vs: config.ViewSetting{Columns: []string{"A", "C"}},
|
vs: config.ViewSetting{Columns: []string{"A", "C"}, SortColumn: ""},
|
||||||
e: NewTableDataWithRows(
|
e: SortColumn{Name: ""},
|
||||||
client.NewGVR("test"),
|
|
||||||
Header{
|
|
||||||
HeaderColumn{Name: "A"},
|
|
||||||
HeaderColumn{Name: "B", Attrs: Attrs{Wide: true}},
|
|
||||||
HeaderColumn{Name: "C"},
|
|
||||||
},
|
|
||||||
NewRowEventsWithEvts(
|
|
||||||
RowEvent{Row: Row{ID: "A", Fields: Fields{"1", "2", "3"}}},
|
|
||||||
RowEvent{Row: Row{ID: "B", Fields: Fields{"0", "2", "3"}}},
|
|
||||||
RowEvent{Row: Row{ID: "C", Fields: Fields{"10", "2", "3"}}},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
td, _ := u.t1.Customize(&u.vs, u.sc, u.manual)
|
sc := u.t1.ComputeSortCol(&u.vs, u.sc, u.manual)
|
||||||
assert.Equal(t, u.e, td)
|
assert.Equal(t, u.e, sc)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,9 @@ type Alias struct {
|
||||||
func (Alias) Header(ns string) model1.Header {
|
func (Alias) Header(ns string) model1.Header {
|
||||||
return model1.Header{
|
return model1.Header{
|
||||||
model1.HeaderColumn{Name: "RESOURCE"},
|
model1.HeaderColumn{Name: "RESOURCE"},
|
||||||
|
model1.HeaderColumn{Name: "GROUP"},
|
||||||
|
model1.HeaderColumn{Name: "VERSION"},
|
||||||
model1.HeaderColumn{Name: "COMMAND"},
|
model1.HeaderColumn{Name: "COMMAND"},
|
||||||
model1.HeaderColumn{Name: "API-GROUP"},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,13 +36,13 @@ func (Alias) Render(o interface{}, ns string, r *model1.Row) error {
|
||||||
return fmt.Errorf("expected AliasRes, but got %T", o)
|
return fmt.Errorf("expected AliasRes, but got %T", o)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.ID = a.GVR
|
|
||||||
gvr := client.NewGVR(a.GVR)
|
gvr := client.NewGVR(a.GVR)
|
||||||
res, grp := gvr.RG()
|
r.ID = gvr.String()
|
||||||
r.Fields = append(r.Fields,
|
r.Fields = append(r.Fields,
|
||||||
res,
|
gvr.R(),
|
||||||
|
gvr.G(),
|
||||||
|
gvr.V(),
|
||||||
strings.Join(a.Aliases, ","),
|
strings.Join(a.Aliases, ","),
|
||||||
grp,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -54,17 +54,18 @@ func TestAliasColorer(t *testing.T) {
|
||||||
func TestAliasHeader(t *testing.T) {
|
func TestAliasHeader(t *testing.T) {
|
||||||
h := model1.Header{
|
h := model1.Header{
|
||||||
model1.HeaderColumn{Name: "RESOURCE"},
|
model1.HeaderColumn{Name: "RESOURCE"},
|
||||||
|
model1.HeaderColumn{Name: "GROUP"},
|
||||||
|
model1.HeaderColumn{Name: "VERSION"},
|
||||||
model1.HeaderColumn{Name: "COMMAND"},
|
model1.HeaderColumn{Name: "COMMAND"},
|
||||||
model1.HeaderColumn{Name: "API-GROUP"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var a render.Alias
|
var a render.Alias
|
||||||
assert.Equal(t, h, a.Header("fred"))
|
assert.Equal(t, h, a.Header("ns-1"))
|
||||||
assert.Equal(t, h, a.Header(client.NamespaceAll))
|
assert.Equal(t, h, a.Header(client.NamespaceAll))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAliasRender(t *testing.T) {
|
func TestAliasRender(t *testing.T) {
|
||||||
a := render.Alias{}
|
var a render.Alias
|
||||||
|
|
||||||
o := render.AliasRes{
|
o := render.AliasRes{
|
||||||
GVR: "fred/v1/blee",
|
GVR: "fred/v1/blee",
|
||||||
|
|
@ -73,7 +74,10 @@ func TestAliasRender(t *testing.T) {
|
||||||
|
|
||||||
var r model1.Row
|
var r model1.Row
|
||||||
assert.Nil(t, a.Render(o, "fred/v1/blee", &r))
|
assert.Nil(t, a.Render(o, "fred/v1/blee", &r))
|
||||||
assert.Equal(t, model1.Row{ID: "fred/v1/blee", Fields: model1.Fields{"blee", "a,b,c", "fred"}}, r)
|
assert.Equal(t, model1.Row{
|
||||||
|
ID: "fred/v1/blee",
|
||||||
|
Fields: model1.Fields{"blee", "fred", "v1", "a,b,c"},
|
||||||
|
}, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkAlias(b *testing.B) {
|
func BenchmarkAlias(b *testing.B) {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"k8s.io/kubectl/pkg/cmd/get"
|
"k8s.io/kubectl/pkg/cmd/get"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fullRX = regexp.MustCompile(`\A([\w\s%/\-]+)\:?([^\|]*)\|?([T|N|W|L|R|H]{0,3})\b`)
|
var fullRX = regexp.MustCompile(`\A([\w\s%\/-]+)\:?([^\|]*)\|?([T|N|W|L|R|H]{0,3})\b`)
|
||||||
|
|
||||||
type colAttr byte
|
type colAttr byte
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ func (t *Table) GVR() client.GVR { return t.gvr }
|
||||||
func (t *Table) ViewSettingsChanged(vs *config.ViewSetting) {
|
func (t *Table) ViewSettingsChanged(vs *config.ViewSetting) {
|
||||||
if t.setViewSetting(vs) {
|
if t.setViewSetting(vs) {
|
||||||
if vs == nil {
|
if vs == nil {
|
||||||
if !t.getMSort() {
|
if !t.getMSort() && !t.sortCol.IsSet() {
|
||||||
t.setSortCol(model1.SortColumn{})
|
t.setSortCol(model1.SortColumn{})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -280,10 +280,9 @@ func (t *Table) doUpdate(data *model1.TableData) *model1.TableData {
|
||||||
t.actions.Delete(KeyShiftP)
|
t.actions.Delete(KeyShiftP)
|
||||||
}
|
}
|
||||||
|
|
||||||
cdata, sortCol := data.Customize(t.getViewSetting(), t.getSortCol(), t.getMSort())
|
t.setSortCol(data.ComputeSortCol(t.getViewSetting(), t.getSortCol(), t.getMSort()))
|
||||||
t.setSortCol(sortCol)
|
|
||||||
|
|
||||||
return cdata
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Table) UpdateUI(cdata, data *model1.TableData) {
|
func (t *Table) UpdateUI(cdata, data *model1.TableData) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
|
@ -63,13 +62,12 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return a.GetTable().activateCmd(evt)
|
return a.GetTable().activateCmd(evt)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, _ := a.GetTable().GetSelection()
|
path := a.GetTable().GetSelectedItem()
|
||||||
if r != 0 {
|
if path == "" {
|
||||||
s := ui.TrimCell(a.GetTable().SelectTable, r, 1)
|
return evt
|
||||||
tokens := strings.Split(s, ",")
|
|
||||||
a.App().gotoResource(tokens[0], "", true, true)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
gvr := client.NewGVR(path)
|
||||||
|
a.App().gotoResource(gvr.AsResourceName(), "", true, true)
|
||||||
|
|
||||||
return evt
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -476,10 +476,6 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if client.IsAllNamespace(ns) {
|
|
||||||
b.GetTable().SetSortCol("NAMESPACE", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := b.app.switchNS(ns); err != nil {
|
if err := b.app.switchNS(ns); err != nil {
|
||||||
b.App().Flash().Err(err)
|
b.App().Flash().Err(err)
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -341,7 +341,7 @@ func (c *Command) exec(p *cmd.Interpreter, gvr client.GVR, comp model.Component,
|
||||||
if comp == nil {
|
if comp == nil {
|
||||||
return fmt.Errorf("no component found for %s", gvr)
|
return fmt.Errorf("no component found for %s", gvr)
|
||||||
}
|
}
|
||||||
c.app.Flash().Infof("Viewing %s...", gvr.R())
|
c.app.Flash().Infof("Viewing %s...", gvr)
|
||||||
if clearStack {
|
if clearStack {
|
||||||
cmd := contextRX.ReplaceAllString(p.GetLine(), "")
|
cmd := contextRX.ReplaceAllString(p.GetLine(), "")
|
||||||
c.app.Config.SetActiveView(cmd)
|
c.app.Config.SetActiveView(cmd)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
name: k9s
|
name: k9s
|
||||||
base: core22
|
base: core22
|
||||||
version: 'v0.40.3'
|
version: 'v0.40.4'
|
||||||
summary: K9s is a CLI to view and manage your Kubernetes clusters.
|
summary: K9s is a CLI to view and manage your Kubernetes clusters.
|
||||||
description: |
|
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.
|
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.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue