diff --git a/.golangci.yml b/.golangci.yml index f880f84a8d..b91459128d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -53,10 +53,8 @@ linters-settings: - nilness goimports: local-prefixes: github.com/cilium/cilium - staticcheck: - go: "1.20" unused: - go: "1.20" + go: "1.23" goheader: values: regexp: @@ -93,7 +91,7 @@ issues: text: "SA9003: empty branch" - linters: [staticcheck] text: "SA2001: empty critical section" - - linters: [goerr113] + - linters: [err113] text: "do not define dynamic errors, use wrapped static errors instead" # This rule to avoid opinionated check fmt.Errorf("text") # Skip goimports check on generated files - path: \\.(generated\\.deepcopy|pb)\\.go$ @@ -107,13 +105,12 @@ issues: linters: disable-all: true enable: - - goerr113 + - err113 - gofmt - goimports - govet - ineffassign - misspell - - staticcheck - unused - goheader - gosec diff --git a/.goreleaser.yml b/.goreleaser.yml index b2ffc71807..bb65270347 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + project_name: k9s before: @@ -90,7 +92,7 @@ brews: commit_author: name: derailed email: fernand@imhotep.io - folder: Formula + directory: Formula homepage: https://k9scli.io/ description: Kubernetes CLI To Manage Your Clusters In Style! test: | diff --git a/.travis.yml b/.travis.yml index dc64775bc6..09bf491802 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: go go_import_path: github.com/derailed/k9s go: - - "1.15" + - "1.23" jobs: include: diff --git a/Makefile b/Makefile index a2b5a10ba3..686bfb1c3b 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.32.5 +VERSION ?= v0.32.6 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.32.6.md b/change_logs/release_v0.32.6.md new file mode 100644 index 0000000000..edb420208d --- /dev/null +++ b/change_logs/release_v0.32.6.md @@ -0,0 +1,97 @@ + + +# Release v0.32.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/enQtOTA5MDEyNzI5MTU0LWQ1ZGI3MzliYzZhZWEyNzYxYzA3NjE0YTk1YmFmNzViZjIyNzhkZGI0MmJjYzhlNjdlMGJhYzE2ZGU1NjkyNTM) + +## Maintenance Release! + +--- + +## Videos Are In The Can! + +Please dial [K9s Channel](https://www.youtube.com/channel/UC897uwPygni4QIjkPCpgjmw) for up coming content... + +* [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 + +* [#2947](https://github.com/derailed/k9s/issues/2947) CTRL+Z causes k9s to crash +* [#2938](https://github.com/derailed/k9s/issues/2938) Critical Vulnerability CVE-2024-41110 in v26.0.1 of docker included in k9s +* [#2929](https://github.com/derailed/k9s/issues/2929) conflicting plugins shortcuts +* [#2896](https://github.com/derailed/k9s/issues/2896) Add a plugin to disable/enable a keda ScaledObject +* [#2811](https://github.com/derailed/k9s/issues/2811) Dockerfile build step fails due to misaligned Go versions (1.21.5 vs 1.22.0) +* [#2767](https://github.com/derailed/k9s/issues/2767) Manually triggered jobs don't get automatically cleaned up +* [#2761](https://github.com/derailed/k9s/issues/2761) Enable "jump to owner" for more kinds +* [#2754](https://github.com/derailed/k9s/issues/2754) Plugins not loaded/shown in UI +* [#2747](https://github.com/derailed/k9s/issues/2747) Combining context and namespace switching only works sporadically (e.g. ":pod foo-ns @ctx-dev") +* [#2746](https://github.com/derailed/k9s/issues/2746) k9s does not display "[::]" string in its logs +* [#2738](https://github.com/derailed/k9s/issues/2738) "Faults" view should show all Terminating pods + +--- + +## 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!! + +* [#2937](https://github.com/derailed/k9s/pull/2937) Adding Argo Rollouts plugin version for PowerShell +* [#2935](https://github.com/derailed/k9s/pull/2935) fix: show all terminating pods in Faults view (#2738) +* [#2933](https://github.com/derailed/k9s/pull/2933) chore: broken url in build-status tag in the readme.md +* [#2932](https://github.com/derailed/k9s/pull/2932) fix: add kubeconfig if k9s is launched with --kubeconfig +* [#2930](https://github.com/derailed/k9s/pull/2930) fixed conflicting plugin shortcuts, and added 2 new plugins +* [#2927](https://github.com/derailed/k9s/pull/2927) Fix "Mark Range": reduce maximum namespaces in favorites, fix shadowing of ctrl+space +* [#2926](https://github.com/derailed/k9s/pull/2926) chore(plugins,remove-finalizers): make sure the resources api group is respected +* [#2921](https://github.com/derailed/k9s/pull/2921) feat: Add plugins for kubectl node-shell +* [#2920](https://github.com/derailed/k9s/pull/2920) eat: added StartupProbes status (S) to the PROBES column in the container render +* [#2914](https://github.com/derailed/k9s/pull/2914) Adding eks-node-viewer plugin +* [#2898](https://github.com/derailed/k9s/pull/2898) Add argocd plugin to community plugins +* [#2896](https://github.com/derailed/k9s/pull/2896) feat(2896): Add toggle keda plugin +* [#2890](https://github.com/derailed/k9s/pull/2890) Update README.md +* [#2881](https://github.com/derailed/k9s/pull/2881) Fix Mark-Range command: ensure that NS Favorite doesn't exceed the limit +* [#2861](https://github.com/derailed/k9s/pull/2861) chore: fix function name +* [#2856](https://github.com/derailed/k9s/pull/2856) fix internal/render/hpa.go merge issue +* [#2848](https://github.com/derailed/k9s/pull/2848) Include sidecar containers requests and limits +* [#2844](https://github.com/derailed/k9s/pull/2844) Update README GO Version Required +* [#2830](https://github.com/derailed/k9s/pull/2830) update tview to fix log escaping problem completely +* [#2822](https://github.com/derailed/k9s/pull/2822) Adding HolmesGPT plugin +* [#2821](https://github.com/derailed/k9s/pull/2821) Add a spark-operator plugin +* [#2817](https://github.com/derailed/k9s/pull/2817) Add comment about Escape keybinding +* [#2812](https://github.com/derailed/k9s/pull/2812) fix: align build image Go version with go.mod +* [#2795](https://github.com/derailed/k9s/pull/2795) add new plugin current-ctx-terminal +* [#2791](https://github.com/derailed/k9s/pull/2791) Add leading space to Kubernetes context suggestions +* [#2789](https://github.com/derailed/k9s/pull/2789) Create kubectl-get-in-shell.yaml +* [#2788](https://github.com/derailed/k9s/pull/2788) Update README.md plugin format +* [#2787](https://github.com/derailed/k9s/pull/2787) Update helm-purge.yaml +* [#2786](https://github.com/derailed/k9s/pull/2786) Update README.md with plugin dangerous field +* [#2780](https://github.com/derailed/k9s/pull/2780) install copyright file into correct location +* [#2775](https://github.com/derailed/k9s/pull/2775) fix freebsd build failure +* [#2780](https://github.com/derailed/k9s/pull/2780) install copyright file into correct location +* [#2772](https://github.com/derailed/k9s/pull/2772) proper handle OwnerReference for manually created job +* [#2771](https://github.com/derailed/k9s/pull/2771) feat: add duplik8s plugin +* [#2770](https://github.com/derailed/k9s/pull/2770) feat: allow plugins block in plugin files +* [#2765](https://github.com/derailed/k9s/pull/2765) fix: Shellin -> ShellIn +* [#2763](https://github.com/derailed/k9s/pull/2763) enable "jump to owner" for more kinds +* [#2755](https://github.com/derailed/k9s/pull/2755) Loki plugin +* [#2751](https://github.com/derailed/k9s/pull/2751) container logs should be escaped when printed +* [#2750](https://github.com/derailed/k9s/pull/2750) fix: should switching ctx before ns + +--- + + © 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/go.mod b/go.mod index db00a1f254..8ad7a138f5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/derailed/k9s -go 1.22.0 +go 1.23.0 require ( github.com/adrg/xdg v0.5.0 @@ -314,8 +314,8 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gorm.io/gorm v1.25.9 // indirect - k8s.io/apiserver v0.31.1 // indirect - k8s.io/component-base v0.31.1 // indirect + k8s.io/apiserver v0.31.2 // indirect + k8s.io/component-base v0.31.2 // indirect k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect modernc.org/libc v1.41.0 // indirect diff --git a/go.sum b/go.sum index 39c4e5e116..8b3aeeef8e 100644 --- a/go.sum +++ b/go.sum @@ -1864,14 +1864,14 @@ k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/ k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= k8s.io/apimachinery v0.31.2 h1:i4vUt2hPK56W6mlT7Ry+AO8eEsyxMD1U44NR22CLTYw= k8s.io/apimachinery v0.31.2/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/apiserver v0.31.1 h1:Sars5ejQDCRBY5f7R3QFHdqN3s61nhkpaX8/k1iEw1c= -k8s.io/apiserver v0.31.1/go.mod h1:lzDhpeToamVZJmmFlaLwdYZwd7zB+WYRYIboqA1kGxM= +k8s.io/apiserver v0.31.2 h1:VUzOEUGRCDi6kX1OyQ801m4A7AUPglpsmGvdsekmcI4= +k8s.io/apiserver v0.31.2/go.mod h1:o3nKZR7lPlJqkU5I3Ove+Zx3JuoFjQobGX1Gctw6XuE= k8s.io/cli-runtime v0.31.1 h1:/ZmKhmZ6hNqDM+yf9s3Y4KEYakNXUn5sod2LWGGwCuk= k8s.io/cli-runtime v0.31.1/go.mod h1:pKv1cDIaq7ehWGuXQ+A//1OIF+7DI+xudXtExMCbe9U= k8s.io/client-go v0.31.2 h1:Y2F4dxU5d3AQj+ybwSMqQnpZH9F30//1ObxOKlTI9yc= k8s.io/client-go v0.31.2/go.mod h1:NPa74jSVR/+eez2dFsEIHNa+3o09vtNaWwWwb1qSxSs= -k8s.io/component-base v0.31.1 h1:UpOepcrX3rQ3ab5NB6g5iP0tvsgJWzxTyAo20sgYSy8= -k8s.io/component-base v0.31.1/go.mod h1:WGeaw7t/kTsqpVTaCoVEtillbqAhF2/JgvO0LDOMa0w= +k8s.io/component-base v0.31.2 h1:Z1J1LIaC0AV+nzcPRFqfK09af6bZ4D1nAOpWsy9owlA= +k8s.io/component-base v0.31.2/go.mod h1:9PeyyFN/drHjtJZMCTkSpQJS3U9OXORnHQqMLDz0sUQ= 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-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= diff --git a/internal/client/metrics.go b/internal/client/metrics.go index 13485c71a4..f79aeac4d8 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -98,7 +98,7 @@ func (m *MetricsServer) checkAccess(ns, gvr, msg string) error { return err } if !auth { - return fmt.Errorf(msg) + return errors.New(msg) } return nil } diff --git a/internal/config/data/ns.go b/internal/config/data/ns.go index 0e0e2228df..14b1c667fe 100644 --- a/internal/config/data/ns.go +++ b/internal/config/data/ns.go @@ -72,7 +72,7 @@ func (n *Namespace) Validate(c client.Connection) { if len(n.Favorites) > MaxFavoritesNS { log.Debug().Msgf("[Namespace] Number of favorite exceeds hard limit of %v. Trimming.", MaxFavoritesNS) for _, ns := range n.Favorites[MaxFavoritesNS:] { - n.rmFavNS(ns) + n.rmFavNS(ns) } } } diff --git a/internal/config/data/ns_test.go b/internal/config/data/ns_test.go index a6e94aa36f..5d39cd378a 100644 --- a/internal/config/data/ns_test.go +++ b/internal/config/data/ns_test.go @@ -36,14 +36,12 @@ func TestNSValidateNoNS(t *testing.T) { } func TestNsValidateMaxNS(t *testing.T) { - allNS := []string{"ns9","ns8","ns7","ns6","ns5","ns4", "ns3", "ns2", "ns1", "all", "default"} - ns := data.NewNamespace() - - ns.Favorites = allNS - - ns.Validate(mock.NewMockConnection()) + allNS := []string{"ns9", "ns8", "ns7", "ns6", "ns5", "ns4", "ns3", "ns2", "ns1", "all", "default"} + ns := data.NewNamespace() + ns.Favorites = allNS - assert.Equal(t, data.MaxFavoritesNS, len(ns.Favorites)) + ns.Validate(mock.NewMockConnection()) + assert.Equal(t, data.MaxFavoritesNS, len(ns.Favorites)) } func TestNSSetActive(t *testing.T) { diff --git a/internal/config/feature.go b/internal/config/feature.go deleted file mode 100644 index 4b1fd75bcd..0000000000 --- a/internal/config/feature.go +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// Copyright Authors of K9s - -package config - -// // FeatureGates represents K9s opt-in features. -// type FeatureGates struct { -// NodeShell bool `yaml:"nodeShell"` -// } - -// // NewFeatureGates returns a new feature gate. -// func NewFeatureGates() *FeatureGates { -// return &FeatureGates{} -// } diff --git a/internal/config/views.go b/internal/config/views.go index cafb9053da..41c6b9b5e6 100644 --- a/internal/config/views.go +++ b/internal/config/views.go @@ -47,7 +47,7 @@ func (v *ViewSetting) SortCol() (string, bool, error) { return "", false, fmt.Errorf("invalid sort column spec: %q. must be col-name:asc|desc", v.SortColumn) } - return tt[0], tt[1] == "desc", nil + return tt[0], tt[1] == "asc", nil } func (v *ViewSetting) Equals(vs *ViewSetting) bool { @@ -116,8 +116,8 @@ func (v *CustomView) RemoveListener(gvr string) { func (v *CustomView) fireConfigChanged() { for gvr, list := range v.listeners { - if v, ok := v.Views[gvr]; ok { - list.ViewSettingsChanged(v) + if view, ok := v.Views[gvr]; ok { + list.ViewSettingsChanged(view) } else { list.ViewSettingsChanged(ViewSetting{}) } diff --git a/internal/dao/alias.go b/internal/dao/alias.go index bf4bf308d5..dc4e3df914 100644 --- a/internal/dao/alias.go +++ b/internal/dao/alias.go @@ -84,7 +84,7 @@ func (a *Alias) AsGVR(c string) (client.GVR, string, bool) { // Get fetch a resource. func (a *Alias) Get(_ context.Context, _ string) (runtime.Object, error) { - return nil, errors.New("NYI!!") + return nil, errors.New("nyi") } // Ensure makes sure alias are loaded. diff --git a/internal/dao/container.go b/internal/dao/container.go index 2074607f26..9ccae861c1 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -6,6 +6,7 @@ package dao import ( "context" "fmt" + "strconv" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" @@ -22,6 +23,12 @@ var ( _ Loggable = (*Container)(nil) ) +const ( + initIDX = "I" + mainIDX = "M" + ephIDX = "E" +) + // Container represents a pod's container dao. type Container struct { NonResource @@ -46,12 +53,15 @@ func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error if err != nil { return nil, err } - res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)) - for _, co := range po.Spec.InitContainers { - res = append(res, makeContainerRes(co, po, cmx[co.Name], true)) + res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)+len(po.Spec.EphemeralContainers)) + for i, co := range po.Spec.InitContainers { + res = append(res, makeContainerRes(initIDX, i, co, po, cmx[co.Name])) + } + for i, co := range po.Spec.Containers { + res = append(res, makeContainerRes(mainIDX, i, co, po, cmx[co.Name])) } - for _, co := range po.Spec.Containers { - res = append(res, makeContainerRes(co, po, cmx[co.Name], false)) + for i, co := range po.Spec.EphemeralContainers { + res = append(res, makeContainerRes(ephIDX, i, v1.Container(co.EphemeralContainerCommon), po, cmx[co.Name])) } return res, nil @@ -68,25 +78,29 @@ func (c *Container) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, // ---------------------------------------------------------------------------- // Helpers... -func makeContainerRes(co v1.Container, po *v1.Pod, cmx *mv1beta1.ContainerMetrics, isInit bool) render.ContainerRes { +func makeContainerRes(kind string, idx int, co v1.Container, po *v1.Pod, cmx *mv1beta1.ContainerMetrics) render.ContainerRes { return render.ContainerRes{ + Idx: kind + strconv.Itoa(idx+1), Container: &co, - Status: getContainerStatus(co.Name, po.Status), + Status: getContainerStatus(kind, idx, po.Status), MX: cmx, - IsInit: isInit, Age: po.GetCreationTimestamp(), } } -func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { - for _, c := range status.ContainerStatuses { - if c.Name == co { - return &c +func getContainerStatus(kind string, idx int, status v1.PodStatus) *v1.ContainerStatus { + switch kind { + case mainIDX: + if idx < len(status.ContainerStatuses) { + return &status.ContainerStatuses[idx] } - } - for _, c := range status.InitContainerStatuses { - if c.Name == co { - return &c + case initIDX: + if idx < len(status.InitContainerStatuses) { + return &status.InitContainerStatuses[idx] + } + case ephIDX: + if idx < len(status.EphemeralContainerStatuses) { + return &status.EphemeralContainerStatuses[idx] } } diff --git a/internal/dao/dir.go b/internal/dao/dir.go index 95239cc070..ef9c67baf1 100644 --- a/internal/dao/dir.go +++ b/internal/dao/dir.go @@ -37,7 +37,7 @@ var yamlRX = regexp.MustCompile(`.*\.(yml|yaml|json)`) func (a *Dir) List(ctx context.Context, _ string) ([]runtime.Object, error) { dir, ok := ctx.Value(internal.KeyPath).(string) if !ok { - return nil, errors.New("No dir in context") + return nil, errors.New("no dir in context") } files, err := os.ReadDir(dir) @@ -61,5 +61,5 @@ func (a *Dir) List(ctx context.Context, _ string) ([]runtime.Object, error) { // Get fetch a resource. func (a *Dir) Get(_ context.Context, _ string) (runtime.Object, error) { - return nil, errors.New("NYI!!") + return nil, errors.New("nyi") } diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 5df3432113..600ee6c1a7 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -130,7 +130,7 @@ func (d *Deployment) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, return nil, err } if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 { - return nil, fmt.Errorf("No valid selector found on Deployment %s", opts.Path) + return nil, fmt.Errorf("no valid selector found on deployment: %s", opts.Path) } return podLogs(ctx, dp.Spec.Selector.MatchLabels, opts) diff --git a/internal/dao/job.go b/internal/dao/job.go index 286cde59be..0c3ba1a12c 100644 --- a/internal/dao/job.go +++ b/internal/dao/job.go @@ -85,7 +85,7 @@ func (j *Job) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error) } if job.Spec.Selector == nil || len(job.Spec.Selector.MatchLabels) == 0 { - return nil, fmt.Errorf("No valid selector found on Job %s", opts.Path) + return nil, fmt.Errorf("no valid selector found for job: %s", opts.Path) } return podLogs(ctx, job.Spec.Selector.MatchLabels, opts) diff --git a/internal/dao/non_resource.go b/internal/dao/non_resource.go index 9840532ec4..0dc7208661 100644 --- a/internal/dao/non_resource.go +++ b/internal/dao/non_resource.go @@ -53,5 +53,5 @@ func (n *NonResource) GVR() string { // Get returns the given resource. func (n *NonResource) Get(context.Context, string) (runtime.Object, error) { - return nil, fmt.Errorf("NYI!") + return nil, fmt.Errorf("nyi") } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 6e5919381a..f4ee453dbe 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -385,11 +385,11 @@ func readLogs(ctx context.Context, wg *sync.WaitGroup, stream io.ReadCloser, out item = opts.ToLogItem(tview.EscapeBytes(bytes)) } else { if errors.Is(err, io.EOF) { - e := fmt.Errorf("Stream closed %w for %s", err, opts.Info()) + e := fmt.Errorf("stream closed %w for %s", err, opts.Info()) item = opts.ToErrLogItem(e) log.Warn().Err(e).Msg("log-reader EOF") } else { - e := fmt.Errorf("Stream canceled %w for %s", err, opts.Info()) + e := fmt.Errorf("stream canceled %w for %s", err, opts.Info()) item = opts.ToErrLogItem(e) log.Warn().Err(e).Msg("log-reader canceled") } @@ -439,7 +439,7 @@ func (p *Pod) SetImages(ctx context.Context, path string, imageSpecs ImageSpecs) return err } if isManaged { - return fmt.Errorf("Unable to set image. This pod is managed by %s. Please set the image on the controller", manager) + return fmt.Errorf("unable to set image. This pod is managed by %s. Please set the image on the controller", manager) } jsonPatch, err := GetJsonPatch(imageSpecs) if err != nil { diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index 454af94285..e0bef73f40 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -72,6 +72,11 @@ func (p *PortForwarder) Port() string { return p.tunnel.PortMap() } +// Address returns the port Address. +func (p *PortForwarder) Address() string { + return p.tunnel.Address +} + // ContainerPort returns the container port. func (p *PortForwarder) ContainerPort() string { return p.tunnel.ContainerPort diff --git a/internal/dao/reference.go b/internal/dao/reference.go index 8ea76f5159..3ea129f6c4 100644 --- a/internal/dao/reference.go +++ b/internal/dao/reference.go @@ -24,7 +24,7 @@ type Reference struct { func (r *Reference) List(ctx context.Context, ns string) ([]runtime.Object, error) { gvr, ok := ctx.Value(internal.KeyGVR).(client.GVR) if !ok { - return nil, errors.New("No context GVR found") + return nil, errors.New("no context for gvr found") } switch gvr { case SaGVR: diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 85416a752b..f512bc60cd 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -11,6 +11,7 @@ import ( "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" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -413,11 +414,32 @@ func loadCRDs(f Factory, m ResourceMetas) { } for _, o := range oo { - meta, errs := extractMeta(o) - if len(errs) > 0 { - log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs)) + var crd apiext.CustomResourceDefinition + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crd) + if err != nil { + log.Err(err).Msg("boom") continue } + + var meta metav1.APIResource + meta.Kind = crd.Spec.Names.Kind + meta.Group = crd.Spec.Group + meta.Name = crd.Name + meta.SingularName = crd.Spec.Names.Singular + meta.ShortNames = crd.Spec.Names.ShortNames + meta.Namespaced = crd.Spec.Scope == apiext.NamespaceScoped + for _, v := range crd.Spec.Versions { + if v.Served && !v.Deprecated { + meta.Version = v.Name + break + } + } + + // meta, errs := extractMeta(o) + // if len(errs) > 0 { + // log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs)) + // continue + // } meta.Categories = append(meta.Categories, crdCat) gvr := client.NewGVRFromMeta(meta) m[gvr] = meta diff --git a/internal/dao/rs.go b/internal/dao/rs.go index fea9d3265d..07ad2fa378 100644 --- a/internal/dao/rs.go +++ b/internal/dao/rs.go @@ -80,7 +80,7 @@ func controllerInfo(rs *appsv1.ReplicaSet) (string, string, string, error) { } return ref.Name, ref.Kind, group, nil } - return "", "", "", fmt.Errorf("Unable to find controller for ReplicaSet %s", rs.ObjectMeta.Name) + return "", "", "", fmt.Errorf("unable to find controller for replicaset: %s", rs.ObjectMeta.Name) } // Rollback reverses the last deployment. diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 9c111d523f..4e352f164b 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -152,7 +152,7 @@ func (s *StatefulSet) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan return nil, errors.New("expecting StatefulSet resource") } if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 { - return nil, fmt.Errorf("No valid selector found on StatefulSet %s", opts.Path) + return nil, fmt.Errorf("no valid selector found on statefulset: %s", opts.Path) } return podLogs(ctx, sts.Spec.Selector.MatchLabels, opts) diff --git a/internal/dao/svc.go b/internal/dao/svc.go index 1e8fcfe877..cee13178d5 100644 --- a/internal/dao/svc.go +++ b/internal/dao/svc.go @@ -32,7 +32,7 @@ func (s *Service) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, er if err != nil { return nil, err } - if svc.Spec.Selector == nil || len(svc.Spec.Selector) == 0 { + if len(svc.Spec.Selector) == 0 { return nil, fmt.Errorf("no valid selector found on Service %s", opts.Path) } diff --git a/internal/dao/table.go b/internal/dao/table.go index 6936772f82..362ce0c21a 100644 --- a/internal/dao/table.go +++ b/internal/dao/table.go @@ -10,7 +10,6 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/client-go/rest" @@ -66,7 +65,7 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) { LabelSelector: labelSel, FieldSelector: fieldSel, ResourceVersion: "0", - ResourceVersionMatch: v1.ResourceVersionMatchNotOlderThan, + ResourceVersionMatch: metav1.ResourceVersionMatchNotOlderThan, }, p). Do(ctx).Get() if err != nil { @@ -102,7 +101,7 @@ func (t *Table) getClient(f serializer.CodecFactory) (*rest.RESTClient, error) { func (t *Table) codec() (serializer.CodecFactory, runtime.ParameterCodec) { var tt metav1.Table - opts := metav1.TableOptions{IncludeObject: v1.IncludeObject} + opts := metav1.TableOptions{IncludeObject: metav1.IncludeObject} gv := t.gvr.GV() metav1.AddToGroupVersion(genScheme, gv) genScheme.AddKnownTypes(gv, &tt, &opts) diff --git a/internal/model/helpers_int_test.go b/internal/model/helpers_int_test.go index 5c3f39a911..3e7e54064f 100644 --- a/internal/model/helpers_int_test.go +++ b/internal/model/helpers_int_test.go @@ -36,6 +36,33 @@ func Test_rxFilter(t *testing.T) { }, }, }, + "start-rx-match": { + q: "(?i)^foo", + lines: []string{"foo", "fob", "barfoo"}, + e: fuzzy.Matches{ + { + Str: "(?i)^foo", + Index: 0, + MatchedIndexes: []int{0, 1, 2}, + }, + }, + }, + "end-rx-match": { + q: "foo$", + lines: []string{"foo", "fob", "barfoo"}, + e: fuzzy.Matches{ + { + Str: "foo$", + Index: 0, + MatchedIndexes: []int{0, 1, 2}, + }, + { + Str: "foo$", + Index: 2, + MatchedIndexes: []int{3, 4, 5}, + }, + }, + }, "multiple-matches": { q: "foo", lines: []string{"foo", "bar", "foo bar foo", "baz"}, @@ -58,6 +85,7 @@ func Test_rxFilter(t *testing.T) { }, }, } + for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { diff --git a/internal/model1/header.go b/internal/model1/header.go index 798f1d92ce..d4f6d4c850 100644 --- a/internal/model1/header.go +++ b/internal/model1/header.go @@ -16,7 +16,6 @@ type HeaderColumn struct { Name string Align int Decorator DecoratorFunc - Hide bool Wide bool MX bool Time bool diff --git a/internal/model1/row_event.go b/internal/model1/row_event.go index 628ddab057..c475f38d38 100644 --- a/internal/model1/row_event.go +++ b/internal/model1/row_event.go @@ -255,7 +255,7 @@ func (r *RowEvents) FindIndex(id string) (int, bool) { // Sort rows based on column index and order. func (r *RowEvents) Sort(ns string, sortCol int, isDuration, numCol, isCapacity, asc bool) { - if sortCol == -1 { + if sortCol == -1 || r == nil { return } @@ -290,6 +290,7 @@ func (r RowEventSorter) Len() int { } func (r RowEventSorter) Swap(i, j int) { + r.Events.events[i], r.Events.events[j] = r.Events.events[j], r.Events.events[i] } diff --git a/internal/model1/table_data.go b/internal/model1/table_data.go index 13ef48ce81..aa2c2f2269 100644 --- a/internal/model1/table_data.go +++ b/internal/model1/table_data.go @@ -167,19 +167,22 @@ func (t *TableData) rxFilter(q string, inverse bool) (*RowEvents, error) { return nil, fmt.Errorf("invalid rx filter %q: %w", q, err) } - ageIndex, ok := t.header.IndexOf("AGE", true) - - rr := NewRowEvents(t.RowCount() / 2) + var startIndex int + if _, ok := t.header.IndexOf("NAMESPACE", true); ok && client.IsNamespaced(t.namespace) { + startIndex = 1 + } + rr := NewRowEvents(50) + ageIndex, _ := t.header.IndexOf("AGE", true) t.rowEvents.Range(func(_ int, re RowEvent) bool { - ff := re.Row.Fields - if ok && ageIndex+1 <= len(ff) { + ff := re.Row.Fields[startIndex:] + if ageIndex >= 0 && ageIndex+1 <= len(ff) { ff = append(ff[0:ageIndex], ff[ageIndex+1:]...) } - fields := strings.Join(ff, spacer) - if (inverse && !rx.MatchString(fields)) || - ((!inverse) && rx.MatchString(fields)) { + match := rx.MatchString(strings.Join(ff, spacer)) + if (inverse && !match) || (!inverse && match) { rr.Add(re) } + return true }) diff --git a/internal/render/container.go b/internal/render/container.go index c4982b3b38..95f3e834f4 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -72,12 +72,12 @@ func (c Container) ColorerFunc() model1.ColorerFunc { // Header returns a header row. func (Container) Header(ns string) model1.Header { return model1.Header{ + model1.HeaderColumn{Name: "IDX"}, model1.HeaderColumn{Name: "NAME"}, model1.HeaderColumn{Name: "PF"}, model1.HeaderColumn{Name: "IMAGE"}, model1.HeaderColumn{Name: "READY"}, model1.HeaderColumn{Name: "STATE"}, - model1.HeaderColumn{Name: "INIT"}, model1.HeaderColumn{Name: "RESTARTS", Align: tview.AlignRight}, model1.HeaderColumn{Name: "PROBES(L:R:S)"}, model1.HeaderColumn{Name: "CPU", Align: tview.AlignRight, MX: true}, @@ -109,12 +109,12 @@ func (c Container) Render(o interface{}, name string, r *model1.Row) error { r.ID = co.Container.Name r.Fields = model1.Fields{ + co.Idx, co.Container.Name, "●", co.Container.Image, ready, state, - boolToStr(co.IsInit), restarts, probe(co.Container.LivenessProbe) + ":" + probe(co.Container.ReadinessProbe) + ":" + probe(co.Container.StartupProbe), toMc(cur.cpu), @@ -241,7 +241,7 @@ type ContainerRes struct { Container *v1.Container Status *v1.ContainerStatus MX *mv1beta1.ContainerMetrics - IsInit bool + Idx string Age metav1.Time } diff --git a/internal/render/container_test.go b/internal/render/container_test.go index b05d775ae9..d7f4f6f3d5 100644 --- a/internal/render/container_test.go +++ b/internal/render/container_test.go @@ -24,19 +24,18 @@ func TestContainer(t *testing.T) { Container: makeContainer(), Status: makeContainerStatus(), MX: makeContainerMetrics(), - IsInit: false, Age: makeAge(), } var r model1.Row assert.Nil(t, c.Render(cres, "blee", &r)) assert.Equal(t, "fred", r.ID) assert.Equal(t, model1.Fields{ + "", "fred", "●", "img", "false", "Running", - "false", "0", "off:off:off", "10", @@ -61,7 +60,6 @@ func BenchmarkContainerRender(b *testing.B) { Container: makeContainer(), Status: makeContainerStatus(), MX: makeContainerMetrics(), - IsInit: false, Age: makeAge(), } var r model1.Row diff --git a/internal/render/node.go b/internal/render/node.go index b207f61a2b..353816de0b 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -39,6 +39,7 @@ func (Node) Header(string) model1.Header { model1.HeaderColumn{Name: "ARCH", Wide: true}, model1.HeaderColumn{Name: "TAINTS"}, model1.HeaderColumn{Name: "VERSION"}, + model1.HeaderColumn{Name: "OS-IMAGE", Wide: true}, model1.HeaderColumn{Name: "KERNEL", Wide: true}, model1.HeaderColumn{Name: "INTERNAL-IP", Wide: true}, model1.HeaderColumn{Name: "EXTERNAL-IP", Wide: true}, @@ -95,6 +96,7 @@ func (n Node) Render(o interface{}, ns string, r *model1.Row) error { no.Status.NodeInfo.Architecture, strconv.Itoa(len(no.Spec.Taints)), no.Status.NodeInfo.KubeletVersion, + no.Status.NodeInfo.OSImage, no.Status.NodeInfo.KernelVersion, iIP, eIP, diff --git a/internal/render/node_test.go b/internal/render/node_test.go index 09fb4a6889..156b054cf1 100644 --- a/internal/render/node_test.go +++ b/internal/render/node_test.go @@ -25,8 +25,8 @@ func TestNodeRender(t *testing.T) { assert.Nil(t, err) assert.Equal(t, "minikube", r.ID) - e := model1.Fields{"minikube", "Ready", "master", "amd64", "0", "v1.15.2", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"} - assert.Equal(t, e, r.Fields[:16]) + e := model1.Fields{"minikube", "Ready", "master", "amd64", "0", "v1.15.2", "Buildroot 2018.05.3", "4.15.0", "192.168.64.107", "", "0", "10", "20", "0", "0", "4000", "7874"} + assert.Equal(t, e, r.Fields[:17]) } func BenchmarkNodeRender(b *testing.B) { diff --git a/internal/render/pod.go b/internal/render/pod.go index 33ce957304..7366257d1f 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -194,7 +194,7 @@ func asReadinessGate(pod v1.Pod) string { return MissingValue } - trueConditions := 0 + var trueConditions int for _, readinessGate := range pod.Spec.ReadinessGates { conditionType := readinessGate.ConditionType for _, condition := range pod.Status.Conditions { @@ -228,7 +228,7 @@ func (p *PodWithMetrics) DeepCopyObject() runtime.Object { func gatherCoMX(spec *v1.PodSpec, ccmx []mv1beta1.ContainerMetrics) (c, r metric) { cc := make([]v1.Container, 0, len(spec.InitContainers)+len(spec.Containers)) - cc = append(cc, filterRestartableInitCO(spec.InitContainers)...) + cc = append(cc, filterSidecarCO(spec.InitContainers)...) cc = append(cc, spec.Containers...) rcpu, rmem := cosRequests(cc) @@ -498,12 +498,13 @@ func restartableInitCO(p *v1.ContainerRestartPolicy) bool { return p != nil && *p == v1.ContainerRestartPolicyAlways } -func filterRestartableInitCO(cc []v1.Container) []v1.Container { +func filterSidecarCO(cc []v1.Container) []v1.Container { rcc := make([]v1.Container, 0, len(cc)) for _, c := range cc { if c.RestartPolicy != nil && *c.RestartPolicy == v1.ContainerRestartPolicyAlways { rcc = append(rcc, c) } } + return rcc } diff --git a/internal/render/pod_int_test.go b/internal/render/pod_int_test.go index 08ca4ed402..c29f230d87 100644 --- a/internal/render/pod_int_test.go +++ b/internal/render/pod_int_test.go @@ -295,12 +295,11 @@ func Test_restartableInitCO(t *testing.T) { } } -func Test_filterRestartableInitCO(t *testing.T) { +func Test_filterSidecarCO(t *testing.T) { always := v1.ContainerRestartPolicyAlways uu := map[string]struct { - cc []v1.Container - ecc []v1.Container + cc, ecc []v1.Container }{ "empty": { cc: []v1.Container{}, @@ -350,7 +349,7 @@ func Test_filterRestartableInitCO(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.ecc, filterRestartableInitCO(u.cc)) + assert.Equal(t, u.ecc, filterSidecarCO(u.cc)) }) } } diff --git a/internal/render/port_forward_test.go b/internal/render/port_forward_test.go index 6c4cd18819..20e0683ac0 100644 --- a/internal/render/port_forward_test.go +++ b/internal/render/port_forward_test.go @@ -66,3 +66,7 @@ func (f fwd) Active() bool { func (f fwd) Age() time.Time { return testTime() } + +func (f fwd) Address() string { + return "" +} diff --git a/internal/render/portforward.go b/internal/render/portforward.go index 267be33cf6..915e325b4b 100644 --- a/internal/render/portforward.go +++ b/internal/render/portforward.go @@ -24,9 +24,12 @@ type Forwarder interface { // Container returns a container name. Container() string - // Ports returns container exposed ports. + // Port returns container exposed port. Port() string + // Address returns the host address. + Address() string + // Active returns forwarder current state. Active() bool @@ -77,7 +80,7 @@ func (f PortForward) Render(o interface{}, gvr string, r *model1.Row) error { trimContainer(n), pf.Container(), pf.Port(), - UrlFor(pf.Config.Host, pf.Config.Path, ports[0]), + UrlFor(pf.Config.Host, pf.Config.Path, ports[0], pf.Address()), AsThousands(int64(pf.Config.C)), AsThousands(int64(pf.Config.N)), "", @@ -100,9 +103,9 @@ func trimContainer(n string) string { } // UrlFor computes fq url for a given benchmark configuration. -func UrlFor(host, path, port string) string { +func UrlFor(host, path, port, address string) string { if host == "" { - host = "localhost" + host = address } if path == "" { path = "/" diff --git a/internal/ui/dialog/selection.go b/internal/ui/dialog/selection.go index fc6e545cca..592105c4db 100644 --- a/internal/ui/dialog/selection.go +++ b/internal/ui/dialog/selection.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package dialog import ( diff --git a/internal/ui/modal_list.go b/internal/ui/modal_list.go index 1827561a36..ac331a9eaf 100644 --- a/internal/ui/modal_list.go +++ b/internal/ui/modal_list.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package ui import ( diff --git a/internal/ui/pages.go b/internal/ui/pages.go index b5864e3bec..eed87e5d53 100644 --- a/internal/ui/pages.go +++ b/internal/ui/pages.go @@ -5,6 +5,7 @@ package ui import ( "fmt" + "github.com/derailed/k9s/internal/model" "github.com/derailed/tview" "github.com/rs/zerolog/log" diff --git a/internal/view/container.go b/internal/view/container.go index 01f4f1e5e5..babcb2b8db 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -33,6 +33,7 @@ func NewContainer(gvr client.GVR) ResourceViewer { c.SetEnvFn(c.k9sEnv) c.GetTable().SetEnterFn(c.viewLogs) c.GetTable().SetDecorateFn(c.decorateRows) + c.GetTable().SetSortCol("IDX", true) c.AddBindKeysFn(c.bindKeys) c.GetTable().SetDecorateFn(c.portForwardIndicator) @@ -90,6 +91,7 @@ func (c *Container) bindKeys(aa *ui.KeyActions) { ui.KeyF: ui.NewKeyAction("Show PortForward", c.showPFCmd, true), ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true), ui.KeyShiftT: ui.NewKeyAction("Sort Restart", c.GetTable().SortColCmd("RESTARTS", false), false), + ui.KeyShiftI: ui.NewKeyAction("Sort Idx", c.GetTable().SortColCmd("IDX", true), false), }) aa.Merge(resourceSorters(c.GetTable())) } diff --git a/internal/view/container_test.go b/internal/view/container_test.go index cc1133e88a..84787f6d1b 100644 --- a/internal/view/container_test.go +++ b/internal/view/container_test.go @@ -16,5 +16,5 @@ func TestContainerNew(t *testing.T) { assert.Nil(t, c.Init(makeCtx())) assert.Equal(t, "Containers", c.Name()) - assert.Equal(t, 18, len(c.Hints())) + assert.Equal(t, 19, len(c.Hints())) } diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 8ec027a9b0..db90747299 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -153,7 +153,7 @@ func showPods(app *App, path, labelSel, fieldSel string) { } } -func podCtx(app *App, path, fieldSel string) ContextFunc { +func podCtx(_ *App, path, fieldSel string) ContextFunc { return func(ctx context.Context) context.Context { ctx = context.WithValue(ctx, internal.KeyPath, path) return context.WithValue(ctx, internal.KeyFields, fieldSel) diff --git a/internal/view/owner_extender.go b/internal/view/owner_extender.go index 89b42449ec..52679c6522 100644 --- a/internal/view/owner_extender.go +++ b/internal/view/owner_extender.go @@ -1,23 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package view import ( "context" "fmt" - "github.com/derailed/k9s/internal/ui/dialog" - "github.com/rs/zerolog/log" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" + "github.com/derailed/k9s/internal/render" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/ui/dialog" "github.com/derailed/tcell/v2" "github.com/go-errors/errors" + "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/derailed/k9s/internal" - "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/dao" - "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/ui" ) // OwnerExtender adds owner actions to a given viewer. diff --git a/internal/view/reference.go b/internal/view/reference.go index 2f08cc7bba..5d2e97f42a 100644 --- a/internal/view/reference.go +++ b/internal/view/reference.go @@ -54,8 +54,9 @@ func (r *Reference) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { } path := r.GetTable().GetSelectedItem() + ns, _ := client.Namespaced(path) gvr := ui.TrimCell(r.GetTable().SelectTable, row, 2) - r.App().gotoResource(client.NewGVR(gvr).R(), path, false) + r.App().gotoResource(client.NewGVR(gvr).R()+" "+ns, path, false) return evt } diff --git a/internal/view/table.go b/internal/view/table.go index d0f60d37a0..47a8d6163b 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -11,6 +11,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" @@ -46,16 +47,16 @@ func (t *Table) Init(ctx context.Context) (err error) { if t.app.Conn() != nil { ctx = context.WithValue(ctx, internal.KeyHasMetrics, t.app.Conn().HasMetrics()) } + t.app.CustomView = config.NewCustomView() ctx = context.WithValue(ctx, internal.KeyStyles, t.app.Styles) + ctx = context.WithValue(ctx, internal.KeyViewConfig, t.app.CustomView) + t.Table.Init(ctx) if !t.app.Config.K9s.UI.Reactive { if err := t.app.RefreshCustomViews(); err != nil { log.Warn().Err(err).Msg("CustomViews load failed") t.app.Logo().Warn("Views load failed!") } } - - ctx = context.WithValue(ctx, internal.KeyViewConfig, t.app.CustomView) - t.Table.Init(ctx) t.SetInputCapture(t.keyboard) t.bindKeys() t.GetModel().SetRefreshRate(time.Duration(t.app.Config.K9s.GetRefreshRate()) * time.Second) diff --git a/internal/view/workload.go b/internal/view/workload.go index 4f0ba02fcd..78bba38fa2 100644 --- a/internal/view/workload.go +++ b/internal/view/workload.go @@ -79,7 +79,7 @@ func parsePath(path string) (client.GVR, string, bool) { func (w *Workload) showRes(app *App, _ ui.Tabular, _ client.GVR, path string) { gvr, fqn, ok := parsePath(path) if !ok { - app.Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) + app.Flash().Err(fmt.Errorf("unable to parse path: %q", path)) return } app.gotoResource(gvr.R(), fqn, false) @@ -130,7 +130,7 @@ func (w *Workload) resourceDelete(selections []string, msg string) { for _, sel := range selections { gvr, fqn, ok := parsePath(sel) if !ok { - w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", sel)) + w.App().Flash().Err(fmt.Errorf("unable to parse path: %q", sel)) return } @@ -157,7 +157,7 @@ func (w *Workload) describeCmd(evt *tcell.EventKey) *tcell.EventKey { } gvr, fqn, ok := parsePath(path) if !ok { - w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) + w.App().Flash().Err(fmt.Errorf("unable to parse path: %q", path)) return evt } @@ -173,7 +173,7 @@ func (w *Workload) editCmd(evt *tcell.EventKey) *tcell.EventKey { } gvr, fqn, ok := parsePath(path) if !ok { - w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) + w.App().Flash().Err(fmt.Errorf("unable to parse path: %q", path)) return evt } @@ -193,7 +193,7 @@ func (w *Workload) yamlCmd(evt *tcell.EventKey) *tcell.EventKey { } gvr, fqn, ok := parsePath(path) if !ok { - w.App().Flash().Err(fmt.Errorf("Unable to parse path: %q", path)) + w.App().Flash().Err(fmt.Errorf("unable to parse path: %q", path)) return evt } diff --git a/internal/watch/forwarders.go b/internal/watch/forwarders.go index 8eab5c0657..0c16f38ddf 100644 --- a/internal/watch/forwarders.go +++ b/internal/watch/forwarders.go @@ -29,6 +29,9 @@ type Forwarder interface { // Port returns the port mapping. Port() string + // Address returns the host address. + Address() string + // FQN returns the full port-forward name. FQN() string diff --git a/internal/watch/forwarders_test.go b/internal/watch/forwarders_test.go index 19e847853b..eae8ae633c 100644 --- a/internal/watch/forwarders_test.go +++ b/internal/watch/forwarders_test.go @@ -173,15 +173,16 @@ func newNoOpForwarder() noOpForwarder { return noOpForwarder{} } -func (m noOpForwarder) Start(path string, tunnel port.PortTunnel) (*portforward.PortForwarder, error) { +func (noOpForwarder) Start(path string, tunnel port.PortTunnel) (*portforward.PortForwarder, error) { return nil, nil } -func (m noOpForwarder) Stop() {} -func (m noOpForwarder) ID() string { return "" } -func (m noOpForwarder) Container() string { return "" } -func (m noOpForwarder) Port() string { return "" } -func (m noOpForwarder) FQN() string { return "" } -func (m noOpForwarder) Active() bool { return false } -func (m noOpForwarder) SetActive(bool) {} -func (m noOpForwarder) Age() time.Time { return time.Now() } -func (m noOpForwarder) HasPortMapping(string) bool { return false } +func (noOpForwarder) Stop() {} +func (noOpForwarder) ID() string { return "" } +func (noOpForwarder) Container() string { return "" } +func (noOpForwarder) Port() string { return "" } +func (noOpForwarder) FQN() string { return "" } +func (noOpForwarder) Active() bool { return false } +func (noOpForwarder) SetActive(bool) {} +func (noOpForwarder) Age() time.Time { return time.Now() } +func (noOpForwarder) HasPortMapping(string) bool { return false } +func (noOpForwarder) Address() string { return "" } diff --git a/internal/xray/pod.go b/internal/xray/pod.go index dbcbf92008..0a618ef0e8 100644 --- a/internal/xray/pod.go +++ b/internal/xray/pod.go @@ -41,7 +41,7 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error { node := NewTreeNode("v1/pods", client.FQN(po.Namespace, po.Name)) parent, ok := ctx.Value(KeyParent).(*TreeNode) if !ok { - return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent)) + return fmt.Errorf("expecting a TreeNode but got %T", ctx.Value(KeyParent)) } if err := p.containerRefs(ctx, node, po.Namespace, po.Spec); err != nil { @@ -95,6 +95,11 @@ func (*Pod) containerRefs(ctx context.Context, parent *TreeNode, ns string, spec return err } } + for i := 0; i < len(spec.EphemeralContainers); i++ { + if err := cre.Render(ctx, ns, render.ContainerRes{Container: &spec.Containers[i]}); err != nil { + return err + } + } return nil } diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index d6ef0648a2..46510bf778 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: k9s base: core22 -version: 'v0.32.5' +version: 'v0.32.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.