Release v0.32.6 (#2955)

* update releaser

* fix err messages

* make pf display the correct address

* fix regex filters

* fix cust res loading

* add container index

* update deps + clean up

* release v0.32.6
mine
Fernand Galiana 2024-11-10 10:08:54 -07:00 committed by GitHub
parent 99d47ab7e7
commit 9984e3f4bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
52 changed files with 318 additions and 138 deletions

View File

@ -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

View File

@ -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: |

View File

@ -1,7 +1,7 @@
language: go
go_import_path: github.com/derailed/k9s
go:
- "1.15"
- "1.23"
jobs:
include:

View File

@ -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}

View File

@ -0,0 +1,97 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
# 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
---
<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)

6
go.mod
View File

@ -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

8
go.sum
View File

@ -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=

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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()
allNS := []string{"ns9", "ns8", "ns7", "ns6", "ns5", "ns4", "ns3", "ns2", "ns1", "all", "default"}
ns := data.NewNamespace()
ns.Favorites = allNS
ns.Favorites = allNS
ns.Validate(mock.NewMockConnection())
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) {

View File

@ -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{}
// }

View File

@ -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{})
}

View File

@ -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.

View File

@ -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 _, co := range po.Spec.Containers {
res = append(res, makeContainerRes(co, po, cmx[co.Name], false))
for i, co := range po.Spec.Containers {
res = append(res, makeContainerRes(mainIDX, i, co, po, cmx[co.Name]))
}
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]
}
}

View File

@ -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")
}

View File

@ -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)

View File

@ -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)

View File

@ -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")
}

View File

@ -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 {

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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)
}

View File

@ -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)

View File

@ -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) {

View File

@ -16,7 +16,6 @@ type HeaderColumn struct {
Name string
Align int
Decorator DecoratorFunc
Hide bool
Wide bool
MX bool
Time bool

View File

@ -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]
}

View File

@ -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
})

View File

@ -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
}

View File

@ -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

View File

@ -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,

View File

@ -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", "<none>", "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", "<none>", "0", "10", "20", "0", "0", "4000", "7874"}
assert.Equal(t, e, r.Fields[:17])
}
func BenchmarkNodeRender(b *testing.B) {

View File

@ -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
}

View File

@ -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))
})
}
}

View File

@ -66,3 +66,7 @@ func (f fwd) Active() bool {
func (f fwd) Age() time.Time {
return testTime()
}
func (f fwd) Address() string {
return ""
}

View File

@ -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 = "/"

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package dialog
import (

View File

@ -1,3 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Authors of K9s
package ui
import (

View File

@ -5,6 +5,7 @@ package ui
import (
"fmt"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/tview"
"github.com/rs/zerolog/log"

View File

@ -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()))
}

View File

@ -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()))
}

View File

@ -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)

View File

@ -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/tcell/v2"
"github.com/go-errors/errors"
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"
"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"
)
// OwnerExtender adds owner actions to a given viewer.

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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 "" }

View File

@ -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
}

View File

@ -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.