K9s/release v0.32.4 (#2637)

* [Bug] fix #2605

* [Bug] fix #2604

* [Bug] fix #2592

* [Bug] fix #2608

* [Bug] Fix #2612

* Rel v0.32.4
mine
Fernand Galiana 2024-03-20 13:00:34 -06:00 committed by derailed
parent c31a48fbee
commit d3027c8f29
26 changed files with 214 additions and 142 deletions

View File

@ -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.32.3 VERSION ?= v0.32.4
IMG_NAME := derailed/k9s IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION} IMAGE := ${IMG_NAME}:${VERSION}

View File

@ -0,0 +1,65 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>
# Release v0.32.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!
---
## ♫ Sounds Behind The Release ♭
Thinking of all you at KubeCon Paris!!
May I suggest a nice glass of `cold Merlote` or other fine grape juices from my country?
* [Le Gorille - George Brassens](https://www.youtube.com/watch?v=KVfwvk_yVyA)
* [Les Funerailles D'antan (Love this guy!) - George Brassens](https://www.youtube.com/watch?v=bwb5k4k2EMc)
* [Poinconneur Des Lilas - Serge Gainsbourg](https://www.youtube.com/watch?v=eWkWCFzkOvU)
* [Mon Legionaire (Yup! same guy??) - Serge Gainsbourg](https://www.youtube.com/watch?v=gl8gopryqWI)
* [Les Cornichons - Nino Ferrer](https://www.youtube.com/watch?v=N7JSW4NhM8I)
* [Paris s'eveille - Jacques Dutronc](https://www.youtube.com/watch?v=3WcCg6rm3uM)
---
## 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
* [#2608](https://github.com/derailed/k9s/issues/2608) Make the sanitize feature easier to use
* [#2605](https://github.com/derailed/k9s/issues/2605) Built-in shortcuts being overridden by plugins result in excessive logging
* [#2604](https://github.com/derailed/k9s/issues/2604) Ability to mark a plugin as Dangerous/destructive
* [#2592](https://github.com/derailed/k9s/issues/2592) "list access denied" when switching contexts within k9s since 0.32.0
---
## 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!!
* [#2621](https://github.com/derailed/k9s/pull/2621) Fix snap build
---
<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)

View File

@ -217,10 +217,10 @@ func (a *APIClient) ServerVersion() (*version.Info, error) {
return info, nil return info, nil
} }
func (a *APIClient) IsValidNamespace(n string) bool { func (a *APIClient) IsValidNamespace(ns string) bool {
ok, err := a.isValidNamespace(n) ok, err := a.isValidNamespace(ns)
if err != nil { if err != nil {
log.Warn().Err(err).Msgf("namespace validation failed for: %q", n) log.Warn().Err(err).Msgf("namespace validation failed for: %q", ns)
} }
return ok return ok

View File

@ -4,6 +4,7 @@
package data package data
import ( import (
"os"
"sync" "sync"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
@ -70,6 +71,9 @@ func (c *Context) Validate(conn client.Connection, ks KubeSettings) {
c.mx.Lock() c.mx.Lock()
defer c.mx.Unlock() defer c.mx.Unlock()
if a := os.Getenv(envPFAddress); a != "" {
c.PortForwardAddress = a
}
if c.PortForwardAddress == "" { if c.PortForwardAddress == "" {
c.PortForwardAddress = defaultPFAddress() c.PortForwardAddress = defaultPFAddress()
} }

View File

@ -32,6 +32,7 @@ func NewActiveNamespace(n string) *Namespace {
if n == client.BlankNamespace { if n == client.BlankNamespace {
n = client.DefaultNamespace n = client.DefaultNamespace
} }
return &Namespace{ return &Namespace{
Active: n, Active: n,
Favorites: []string{client.DefaultNamespace}, Favorites: []string{client.DefaultNamespace},

View File

@ -14,6 +14,7 @@
"override": { "type": "boolean" }, "override": { "type": "boolean" },
"description": { "type": "string" }, "description": { "type": "string" },
"confirm": { "type": "boolean" }, "confirm": { "type": "boolean" },
"dangerous": { "type": "boolean" },
"scopes": { "scopes": {
"type": "array", "type": "array",
"items": { "type": "string" } "items": { "type": "string" }

View File

@ -36,6 +36,7 @@ type Plugin struct {
Command string `yaml:"command"` Command string `yaml:"command"`
Confirm bool `yaml:"confirm"` Confirm bool `yaml:"confirm"`
Background bool `yaml:"background"` Background bool `yaml:"background"`
Dangerous bool `yaml:"dangerous"`
} }
func (p Plugin) String() string { func (p Plugin) String() string {

View File

@ -207,19 +207,19 @@ func (p *Pod) TailLogs(ctx context.Context, opts *LogOptions) ([]LogChan, error)
return append(outs, tailLogs(ctx, p, opts)), nil return append(outs, tailLogs(ctx, p, opts)), nil
} }
for _, co := range po.Spec.InitContainers { for _, co := range po.Spec.InitContainers {
o := opts.Clone() cfg := opts.Clone()
o.Container = co.Name cfg.Container = co.Name
outs = append(outs, tailLogs(ctx, p, o)) outs = append(outs, tailLogs(ctx, p, cfg))
} }
for _, co := range po.Spec.Containers { for _, co := range po.Spec.Containers {
o := opts.Clone() cfg := opts.Clone()
o.Container = co.Name cfg.Container = co.Name
outs = append(outs, tailLogs(ctx, p, o)) outs = append(outs, tailLogs(ctx, p, cfg))
} }
for _, co := range po.Spec.EphemeralContainers { for _, co := range po.Spec.EphemeralContainers {
o := opts.Clone() cfg := opts.Clone()
o.Container = co.Name cfg.Container = co.Name
outs = append(outs, tailLogs(ctx, p, o)) outs = append(outs, tailLogs(ctx, p, cfg))
} }
return outs, nil return outs, nil

View File

@ -59,16 +59,20 @@ func (s StatefulSet) Render(o interface{}, ns string, r *model1.Row) error {
podContainerNames(sts.Spec.Template.Spec, true), podContainerNames(sts.Spec.Template.Spec, true),
podImageNames(sts.Spec.Template.Spec, true), podImageNames(sts.Spec.Template.Spec, true),
mapToStr(sts.Labels), mapToStr(sts.Labels),
AsStatus(s.diagnose(sts.Status.Replicas, sts.Status.ReadyReplicas)), AsStatus(s.diagnose(sts.Spec.Replicas, sts.Status.Replicas, sts.Status.ReadyReplicas)),
ToAge(sts.GetCreationTimestamp()), ToAge(sts.GetCreationTimestamp()),
} }
return nil return nil
} }
func (StatefulSet) diagnose(d, r int32) error { func (StatefulSet) diagnose(w *int32, d, r int32) error {
if d != r { if d != r {
return fmt.Errorf("desiring %d replicas got %d available", d, r) return fmt.Errorf("desired %d replicas got %d available", d, r)
} }
if w != nil && *w != r {
return fmt.Errorf("want %d replicas got %d available", *w, r)
}
return nil return nil
} }

View File

@ -80,7 +80,7 @@ func hotKeyActions(r Runner, aa *ui.KeyActions) error {
errs = errors.Join(errs, fmt.Errorf("duplicate hotkey found for %q in %q", hk.ShortCut, k)) errs = errors.Join(errs, fmt.Errorf("duplicate hotkey found for %q in %q", hk.ShortCut, k))
continue continue
} }
log.Info().Msgf("Action %q has been overridden by hotkey in %q", hk.ShortCut, k) log.Debug().Msgf("Action %q has been overridden by hotkey in %q", hk.ShortCut, k)
} }
command, err := r.EnvFn()().Substitute(hk.Command) command, err := r.EnvFn()().Substitute(hk.Command)
@ -110,7 +110,6 @@ func gotoCmd(r Runner, cmd, path string, clearStack bool) ui.ActionHandler {
} }
func pluginActions(r Runner, aa *ui.KeyActions) error { func pluginActions(r Runner, aa *ui.KeyActions) error {
pp := config.NewPlugins()
aa.Range(func(k tcell.Key, a ui.KeyAction) { aa.Range(func(k tcell.Key, a ui.KeyAction) {
if a.Opts.Plugin { if a.Opts.Plugin {
aa.Delete(k) aa.Delete(k)
@ -121,12 +120,16 @@ func pluginActions(r Runner, aa *ui.KeyActions) error {
if err != nil { if err != nil {
return err return err
} }
pp := config.NewPlugins()
if err := pp.Load(path); err != nil { if err := pp.Load(path); err != nil {
return err return err
} }
var errs error var (
aliases := r.Aliases() errs error
aliases = r.Aliases()
ro = r.App().Config.K9s.IsReadOnly()
)
for k, plugin := range pp.Plugins { for k, plugin := range pp.Plugins {
if !inScope(plugin.Scopes, aliases) { if !inScope(plugin.Scopes, aliases) {
continue continue
@ -141,15 +144,19 @@ func pluginActions(r Runner, aa *ui.KeyActions) error {
errs = errors.Join(errs, fmt.Errorf("duplicate plugin key found for %q in %q", plugin.ShortCut, k)) errs = errors.Join(errs, fmt.Errorf("duplicate plugin key found for %q in %q", plugin.ShortCut, k))
continue continue
} }
log.Info().Msgf("Action %q has been overridden by plugin in %q", plugin.ShortCut, k) log.Debug().Msgf("Action %q has been overridden by plugin in %q", plugin.ShortCut, k)
} }
if plugin.Dangerous && ro {
continue
}
aa.Add(key, ui.NewKeyActionWithOpts( aa.Add(key, ui.NewKeyActionWithOpts(
plugin.Description, plugin.Description,
pluginAction(r, plugin), pluginAction(r, plugin),
ui.ActionOpts{ ui.ActionOpts{
Visible: true, Visible: true,
Plugin: true, Plugin: true,
Dangerous: plugin.Dangerous,
}, },
)) ))
} }

View File

@ -111,10 +111,6 @@ func (a *App) Init(version string, rate int) error {
ns := a.Config.ActiveNamespace() ns := a.Config.ActiveNamespace()
a.factory = watch.NewFactory(a.Conn()) a.factory = watch.NewFactory(a.Conn())
ok, err := a.isValidNS(ns)
if !ok && err == nil {
return fmt.Errorf("app-init - invalid namespace: %q", ns)
}
a.initFactory(ns) a.initFactory(ns)
a.clusterModel = model.NewClusterInfo(a.factory, a.version, a.Config.K9s) a.clusterModel = model.NewClusterInfo(a.factory, a.version, a.Config.K9s)
@ -438,18 +434,6 @@ func (a *App) switchNS(ns string) error {
return a.factory.SetActiveNS(ns) return a.factory.SetActiveNS(ns)
} }
func (a *App) isValidNS(ns string) (bool, error) {
if ns == client.BlankNamespace || ns == client.NamespaceAll {
return true, nil
}
if !a.Conn().IsValidNamespace(ns) {
return false, fmt.Errorf("invalid namespace: %q", ns)
}
return true, nil
}
func (a *App) switchContext(ci *cmd.Interpreter, force bool) error { func (a *App) switchContext(ci *cmd.Interpreter, force bool) error {
name, ok := ci.HasContext() name, ok := ci.HasContext()
if !ok || a.Config.ActiveContextName() == name { if !ok || a.Config.ActiveContextName() == name {
@ -477,12 +461,13 @@ func (a *App) switchContext(ci *cmd.Interpreter, force bool) error {
} }
ns := a.Config.ActiveNamespace() ns := a.Config.ActiveNamespace()
if !a.Conn().IsValidNamespace(ns) { if !a.Conn().IsValidNamespace(ns) {
a.Flash().Errf("Unable to validate namespace %q. Using %q namespace", ns, client.DefaultNamespace) log.Warn().Msgf("Unable to validate namespace: %q. Using %q as active namespace", ns, ns)
ns = client.DefaultNamespace
if err := a.Config.SetActiveNamespace(ns); err != nil { if err := a.Config.SetActiveNamespace(ns); err != nil {
return err return err
} }
} }
a.Flash().Errf("Using %q namespace", ns)
if err := a.Config.Save(true); err != nil { if err := a.Config.Save(true); err != nil {
log.Error().Err(err).Msg("config save failed!") log.Error().Err(err).Msg("config save failed!")
} else { } else {

View File

@ -168,6 +168,7 @@ func (d *Details) StylesChanged(s *config.Styles) {
// Update updates the view content. // Update updates the view content.
func (d *Details) Update(buff string) *Details { func (d *Details) Update(buff string) *Details {
d.model.SetText(buff) d.model.SetText(buff)
return d return d
} }

View File

@ -53,49 +53,16 @@ func (d *Deploy) logOptions(prev bool) (*dao.LogOptions, error) {
if path == "" { if path == "" {
return nil, errors.New("you must provide a selection") return nil, errors.New("you must provide a selection")
} }
dp, err := d.getInstance(path)
sts, err := d.dp(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cc := sts.Spec.Template.Spec.Containers return podLogOptions(d.App(), path, prev, dp.ObjectMeta, dp.Spec.Template.Spec), nil
var (
co, dco string
allCos bool
)
if c, ok := dao.GetDefaultContainer(sts.Spec.Template.ObjectMeta, sts.Spec.Template.Spec); ok {
co, dco = c, c
} else if len(cc) == 1 {
co = cc[0].Name
} else {
dco, allCos = cc[0].Name, true
}
cfg := d.App().Config.K9s.Logger
opts := dao.LogOptions{
Path: path,
Container: co,
Lines: int64(cfg.TailCount),
SinceSeconds: cfg.SinceSeconds,
SingleContainer: len(cc) == 1,
AllContainers: allCos,
ShowTimestamp: cfg.ShowTime,
Previous: prev,
}
if co == "" {
opts.AllContainers = true
}
opts.DefaultContainer = dco
return &opts, nil
} }
func (d *Deploy) showPods(app *App, model ui.Tabular, gvr client.GVR, fqn string) { func (d *Deploy) showPods(app *App, model ui.Tabular, gvr client.GVR, fqn string) {
var ddp dao.Deployment dp, err := d.getInstance(fqn)
ddp.Init(d.App().factory, d.GVR())
dp, err := ddp.GetInstance(fqn)
if err != nil { if err != nil {
app.Flash().Err(err) app.Flash().Err(err)
return return
@ -104,7 +71,7 @@ func (d *Deploy) showPods(app *App, model ui.Tabular, gvr client.GVR, fqn string
showPodsFromSelector(app, fqn, dp.Spec.Selector) showPodsFromSelector(app, fqn, dp.Spec.Selector)
} }
func (d *Deploy) dp(fqn string) (*appsv1.Deployment, error) { func (d *Deploy) getInstance(fqn string) (*appsv1.Deployment, error) {
var dp dao.Deployment var dp dao.Deployment
dp.Init(d.App().factory, d.GVR()) dp.Init(d.App().factory, d.GVR())

View File

@ -4,9 +4,12 @@
package view package view
import ( import (
"errors"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
appsv1 "k8s.io/api/apps/v1"
) )
// DaemonSet represents a daemon set custom viewer. // DaemonSet represents a daemon set custom viewer.
@ -16,17 +19,16 @@ type DaemonSet struct {
// NewDaemonSet returns a new viewer. // NewDaemonSet returns a new viewer.
func NewDaemonSet(gvr client.GVR) ResourceViewer { func NewDaemonSet(gvr client.GVR) ResourceViewer {
d := DaemonSet{ var d DaemonSet
ResourceViewer: NewPortForwardExtender( d.ResourceViewer = NewPortForwardExtender(
NewVulnerabilityExtender( NewVulnerabilityExtender(
NewRestartExtender( NewRestartExtender(
NewImageExtender( NewImageExtender(
NewLogsExtender(NewBrowser(gvr), nil), NewLogsExtender(NewBrowser(gvr), d.logOptions),
), ),
), ),
), ),
), )
}
d.AddBindKeysFn(d.bindKeys) d.AddBindKeysFn(d.bindKeys)
d.GetTable().SetEnterFn(d.showPods) d.GetTable().SetEnterFn(d.showPods)
@ -55,3 +57,23 @@ func (d *DaemonSet) showPods(app *App, model ui.Tabular, _ client.GVR, path stri
showPodsFromSelector(app, path, ds.Spec.Selector) showPodsFromSelector(app, path, ds.Spec.Selector)
} }
func (d *DaemonSet) logOptions(prev bool) (*dao.LogOptions, error) {
path := d.GetTable().GetSelectedItem()
if path == "" {
return nil, errors.New("you must provide a selection")
}
ds, err := d.getInstance(path)
if err != nil {
return nil, err
}
return podLogOptions(d.App(), path, prev, ds.ObjectMeta, ds.Spec.Template.Spec), nil
}
func (d *DaemonSet) getInstance(fqn string) (*appsv1.DaemonSet, error) {
var ds dao.DaemonSet
ds.Init(d.App().factory, client.NewGVR("apps/v1/daemonsets"))
return ds.GetInstance(fqn)
}

View File

@ -4,7 +4,10 @@
package view package view
import ( import (
"errors"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -19,7 +22,11 @@ type Job struct {
// NewJob returns a new viewer. // NewJob returns a new viewer.
func NewJob(gvr client.GVR) ResourceViewer { func NewJob(gvr client.GVR) ResourceViewer {
j := Job{ResourceViewer: NewVulnerabilityExtender(NewLogsExtender(NewBrowser(gvr), nil))} var j Job
j.ResourceViewer = NewVulnerabilityExtender(
NewLogsExtender(NewBrowser(gvr), j.logOptions),
)
j.GetTable().SetEnterFn(j.showPods) j.GetTable().SetEnterFn(j.showPods)
j.GetTable().SetSortCol("AGE", true) j.GetTable().SetSortCol("AGE", true)
@ -42,3 +49,23 @@ func (*Job) showPods(app *App, model ui.Tabular, gvr client.GVR, path string) {
showPodsFromSelector(app, path, job.Spec.Selector) showPodsFromSelector(app, path, job.Spec.Selector)
} }
func (j *Job) logOptions(prev bool) (*dao.LogOptions, error) {
path := j.GetTable().GetSelectedItem()
if path == "" {
return nil, errors.New("you must provide a selection")
}
job, err := j.getInstance(path)
if err != nil {
return nil, err
}
return podLogOptions(j.App(), path, prev, job.ObjectMeta, job.Spec.Template.Spec), nil
}
func (j *Job) getInstance(fqn string) (*batchv1.Job, error) {
var job dao.Job
job.Init(j.App().factory, client.NewGVR("batch/v1/jobs"))
return job.GetInstance(fqn)
}

View File

@ -8,6 +8,8 @@ import (
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/tcell/v2" "github.com/derailed/tcell/v2"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// LogsExtender adds log actions to a given viewer. // LogsExtender adds log actions to a given viewer.
@ -91,3 +93,27 @@ func (l *LogsExtender) buildLogOpts(path, co string, prevLogs bool) *dao.LogOpti
return &opts return &opts
} }
func podLogOptions(app *App, fqn string, prev bool, m metav1.ObjectMeta, spec v1.PodSpec) *dao.LogOptions {
var (
cc = fetchContainers(m, spec, true)
cfg = app.Config.K9s.Logger
opts = dao.LogOptions{
Path: fqn,
Lines: int64(cfg.TailCount),
SinceSeconds: cfg.SinceSeconds,
SingleContainer: len(cc) == 1,
ShowTimestamp: cfg.ShowTime,
Previous: prev,
}
)
if c, ok := dao.GetDefaultContainer(m, spec); ok {
opts.Container, opts.DefaultContainer = c, c
} else if len(cc) == 1 {
opts.Container = cc[0]
} else {
opts.AllContainers = true
}
return &opts
}

View File

@ -38,6 +38,7 @@ const (
trDownload = "Download" trDownload = "Download"
pfIndicator = "[orange::b]Ⓕ" pfIndicator = "[orange::b]Ⓕ"
defaultTxRetries = 999 defaultTxRetries = 999
magicPrompt = "Yes Please!"
) )
// Pod represents a pod viewer. // Pod represents a pod viewer.
@ -146,24 +147,7 @@ func (p *Pod) logOptions(prev bool) (*dao.LogOptions, error) {
return nil, err return nil, err
} }
cc, cfg := fetchContainers(pod.ObjectMeta, pod.Spec, true), p.App().Config.K9s.Logger return podLogOptions(p.App(), path, prev, pod.ObjectMeta, pod.Spec), nil
opts := dao.LogOptions{
Path: path,
Lines: int64(cfg.TailCount),
SinceSeconds: cfg.SinceSeconds,
SingleContainer: len(cc) == 1,
ShowTimestamp: cfg.ShowTime,
Previous: prev,
}
if c, ok := dao.GetDefaultContainer(pod.ObjectMeta, pod.Spec); ok {
opts.Container, opts.DefaultContainer = c, c
} else if len(cc) == 1 {
opts.Container = cc[0]
} else {
opts.AllContainers = true
}
return &opts, nil
} }
func (p *Pod) showContainers(app *App, _ ui.Tabular, _ client.GVR, _ string) { func (p *Pod) showContainers(app *App, _ ui.Tabular, _ client.GVR, _ string) {
@ -287,9 +271,8 @@ func (p *Pod) sanitizeCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
ack := "sanitize me pods!" msg := fmt.Sprintf("Sanitize deletes all pods in completed/error state\nPlease enter [orange::b]%s[-::-] to proceed.", magicPrompt)
msg := fmt.Sprintf("Sanitize deletes all pods in completed/error state\nPlease enter [orange::b]%s[-::-] to proceed.", ack) dialog.ShowConfirmAck(p.App().App, p.App().Content.Pages, magicPrompt, true, "Sanitize", msg, func() {
dialog.ShowConfirmAck(p.App().App, p.App().Content.Pages, ack, true, "Sanitize", msg, func() {
ctx, cancel := context.WithTimeout(context.Background(), 5*p.App().Conn().Config().CallTimeout()) ctx, cancel := context.WithTimeout(context.Background(), 5*p.App().Conn().Config().CallTimeout())
defer cancel() defer cancel()
total, err := s.Sanitize(ctx, p.GetTable().GetModel().GetNamespace()) total, err := s.Sanitize(ctx, p.GetTable().GetModel().GetNamespace())

View File

@ -42,42 +42,12 @@ func (s *StatefulSet) logOptions(prev bool) (*dao.LogOptions, error) {
if path == "" { if path == "" {
return nil, errors.New("you must provide a selection") return nil, errors.New("you must provide a selection")
} }
sts, err := s.getInstance(path) sts, err := s.getInstance(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cc := sts.Spec.Template.Spec.Containers return podLogOptions(s.App(), path, prev, sts.ObjectMeta, sts.Spec.Template.Spec), nil
var (
co, dco string
allCos bool
)
if c, ok := dao.GetDefaultContainer(sts.Spec.Template.ObjectMeta, sts.Spec.Template.Spec); ok {
co, dco = c, c
} else if len(cc) == 1 {
co = cc[0].Name
} else {
dco, allCos = cc[0].Name, true
}
cfg := s.App().Config.K9s.Logger
opts := dao.LogOptions{
Path: path,
Container: co,
Lines: int64(cfg.TailCount),
SingleContainer: len(cc) == 1,
SinceSeconds: cfg.SinceSeconds,
AllContainers: allCos,
ShowTimestamp: cfg.ShowTime,
Previous: prev,
}
if co == "" {
opts.AllContainers = true
}
opts.DefaultContainer = dco
return &opts, nil
} }
func (s *StatefulSet) bindKeys(aa *ui.KeyActions) { func (s *StatefulSet) bindKeys(aa *ui.KeyActions) {
@ -96,5 +66,6 @@ func (s *StatefulSet) showPods(app *App, _ ui.Tabular, _ client.GVR, path string
func (s *StatefulSet) getInstance(path string) (*appsv1.StatefulSet, error) { func (s *StatefulSet) getInstance(path string) (*appsv1.StatefulSet, error) {
var sts dao.StatefulSet var sts dao.StatefulSet
return sts.GetInstance(s.App().factory, path) return sts.GetInstance(s.App().factory, path)
} }

View File

@ -4,6 +4,7 @@ plugins:
debug: debug:
shortCut: Shift-D shortCut: Shift-D
description: Add debug container description: Add debug container
dangerous: true
scopes: scopes:
- containers - containers
command: bash command: bash

View File

@ -4,6 +4,7 @@ plugins:
helm-purge: helm-purge:
shortCut: Ctrl-P shortCut: Ctrl-P
description: Helm Purge description: Helm Purge
dangerous: true
scopes: scopes:
- po - po
command: kubectl command: kubectl

View File

@ -3,6 +3,7 @@ plugins:
toggleCronjob: toggleCronjob:
shortCut: Ctrl-S shortCut: Ctrl-S
confirm: true confirm: true
dangerous: true
scopes: scopes:
- cj - cj
description: Toggle to suspend or resume a running cronjob description: Toggle to suspend or resume a running cronjob

View File

@ -3,6 +3,7 @@ plugins:
k3d-root-shell: k3d-root-shell:
shortCut: Shift-S shortCut: Shift-S
confirm: false confirm: false
dangerous: true
description: "Root Shell" description: "Root Shell"
scopes: scopes:
- containers - containers

View File

@ -17,6 +17,7 @@ plugins:
description: Live Migrate moves VM to another compute node description: Live Migrate moves VM to another compute node
# Enable confirmation dialog # Enable confirmation dialog
confirm: true confirm: true
dangerous: true
# Collections of views that support this shortcut. (You can use `all`) # Collections of views that support this shortcut. (You can use `all`)
scopes: scopes:
- virtualmachineinstance - virtualmachineinstance

View File

@ -11,6 +11,7 @@ plugins:
remove_finalizers: remove_finalizers:
shortCut: Ctrl-F shortCut: Ctrl-F
confirm: true confirm: true
dangerous: true
scopes: scopes:
- all - all
description: | description: |

View File

@ -3,6 +3,7 @@ plugins:
rm-ns: rm-ns:
shortCut: n shortCut: n
confirm: true confirm: true
dangerous: true
description: Remove NS Finalizers description: Remove NS Finalizers
scopes: scopes:
- namespace - namespace

View File

@ -1,6 +1,6 @@
name: k9s name: k9s
base: core22 base: core22
version: 'v0.32.3' version: 'v0.32.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.