K9s/release v0.32.4 (#2637)
* [Bug] fix #2605 * [Bug] fix #2604 * [Bug] fix #2592 * [Bug] fix #2608 * [Bug] Fix #2612 * Rel v0.32.4mine
parent
c31a48fbee
commit
d3027c8f29
2
Makefile
2
Makefile
|
|
@ -11,7 +11,7 @@ DATE ?= $(shell TZ=UTC date -j -f "%s" ${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:
|
||||||
else
|
else
|
||||||
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
endif
|
endif
|
||||||
VERSION ?= v0.32.3
|
VERSION ?= v0.32.4
|
||||||
IMG_NAME := derailed/k9s
|
IMG_NAME := derailed/k9s
|
||||||
IMAGE := ${IMG_NAME}:${VERSION}
|
IMAGE := ${IMG_NAME}:${VERSION}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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},
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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: |
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue