k9s: release v0.25.9 (#1364)

* release v0.25.9

* update docs
mine
Fernand Galiana 2021-12-13 13:02:44 -07:00 committed by GitHub
parent 0249f7cf2c
commit 34eab38afb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 163 additions and 85 deletions

View File

@ -5,7 +5,7 @@ PACKAGE := github.com/derailed/$(NAME)
GIT_REV ?= $(shell git rev-parse --short HEAD) GIT_REV ?= $(shell git rev-parse --short HEAD)
SOURCE_DATE_EPOCH ?= $(shell date +%s) SOURCE_DATE_EPOCH ?= $(shell date +%s)
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")
VERSION ?= v0.25.8 VERSION ?= v0.25.9
IMG_NAME := derailed/k9s IMG_NAME := derailed/k9s
IMAGE := ${IMG_NAME}:${VERSION} IMAGE := ${IMG_NAME}:${VERSION}

View File

@ -883,4 +883,4 @@ We always enjoy hearing from folks who benefit from our work!
--- ---
<img src="assets/imhotep_logo.png" width="32" height="auto" alt="Imhotep"/> &nbsp;© 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) <img src="assets/imhotep_logo.png" width="32" height="auto" alt="Imhotep"/> &nbsp;© 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -0,0 +1,54 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.25.9
## 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!
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)
### A Word From Our Sponsors...
I want to recognize the following folks that have been kind enough to join our sponsorship program and opted to `pay it forward`!
* [Joshua Kapellen](https://github.com/joshuakapellen)
* [Qdentity](https://github.com/qdentity)
* [Maxim](https://github.com/bsod90)
* [Sönke Schau](https://github.com/xgcssch)
So if you feel K9s is helping with your productivity while administering your Kubernetes clusters, please consider pitching in as it will go a long way in ensuring a thriving environment for this repo and our k9ers community at large.
Also please take some time and give a huge shoot out to all the good folks below that have spent time plowing thru the code to help improve K9s for all of us!
Thank you!!
---
## Maintenance Release!
---
## Resolved Issues
* [Issue #1361](https://github.com/derailed/k9s/issues/1361) Pulses not displaying graphs
* [Issue #1358](https://github.com/derailed/k9s/issues/1358) Namespace list is empty
* [Issue #1357](https://github.com/derailed/k9s/issues/1357) Benchmarks doesn't work on windows
* [Issue #1355](https://github.com/derailed/k9s/issues/1355) Trace log level does not exists
* [Issue #1345](https://github.com/derailed/k9s/issues/1345) Access denied after context switch
---
## PRs
* [PR #1363](https://github.com/derailed/k9s/pull/1363) Add rose-pine skin.
[Sergio Soria](https://github.com/sasoria)
* [PR #1356](https://github.com/derailed/k9s/pull/1356) Add flux trace shortcut to flux plugin.
[Guillaume Berche](https://github.com/gberche-orange)
* [PR #1321](https://github.com/derailed/k9s/pull/1321) Add customizable dump directory property.
[Vlasov Artem](https://github.com/VlasovArtem)
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -161,7 +161,7 @@ func initK9sFlags() {
k9sFlags.LogLevel, k9sFlags.LogLevel,
"logLevel", "l", "logLevel", "l",
config.DefaultLogLevel, config.DefaultLogLevel,
"Specify a log level (info, warn, debug, error, fatal, panic, trace)", "Specify a log level (info, warn, debug, error)",
) )
rootCmd.Flags().StringVarP( rootCmd.Flags().StringVarP(
k9sFlags.LogFile, k9sFlags.LogFile,
@ -221,7 +221,7 @@ func initK9sFlags() {
} }
func initK8sFlags() { func initK8sFlags() {
k8sFlags = genericclioptions.NewConfigFlags(false) k8sFlags = genericclioptions.NewConfigFlags(client.UsePersistentConfig)
rootCmd.Flags().StringVar( rootCmd.Flags().StringVar(
k8sFlags.KubeConfig, k8sFlags.KubeConfig,

View File

@ -15,14 +15,15 @@ import (
) )
const ( const (
defaultCallTimeoutDuration time.Duration = 5 * time.Second defaultCallTimeoutDuration time.Duration = 10 * time.Second
// UsePersistentConfig caches client config to avoid reloads.
UsePersistentConfig = true
) )
// Config tracks a kubernetes configuration. // Config tracks a kubernetes configuration.
type Config struct { type Config struct {
flags *genericclioptions.ConfigFlags flags *genericclioptions.ConfigFlags
clientCfg clientcmd.ClientConfig
rawCfg *clientcmdapi.Config
mutex *sync.RWMutex mutex *sync.RWMutex
OverrideNS bool OverrideNS bool
} }
@ -58,60 +59,35 @@ func (c *Config) Flags() *genericclioptions.ConfigFlags {
return c.flags return c.flags
} }
func (c *Config) rawConfig() (*clientcmdapi.Config, error) { func (c *Config) RawConfig() (clientcmdapi.Config, error) {
if c.rawCfg != nil { return c.clientConfig().RawConfig()
return c.rawCfg, nil
}
cfg, err := c.clientConfig().RawConfig()
if err != nil {
return nil, err
}
c.rawCfg = &cfg
return c.rawCfg, nil
} }
func (c *Config) clientConfig() clientcmd.ClientConfig { func (c *Config) clientConfig() clientcmd.ClientConfig {
if c.clientCfg != nil { return c.flags.ToRawKubeConfigLoader()
return c.clientCfg
}
c.clientCfg = c.flags.ToRawKubeConfigLoader()
return c.clientCfg
} }
func (c *Config) reset() { func (c *Config) reset() {}
c.clientCfg, c.rawCfg = nil, nil
}
// SwitchContext changes the kubeconfig context to a new cluster. // SwitchContext changes the kubeconfig context to a new cluster.
func (c *Config) SwitchContext(name string) error { func (c *Config) SwitchContext(name string) error {
if n, err := c.CurrentContextName(); err == nil && n == name { if _, err := c.GetContext(name); err != nil {
return nil
}
context, err := c.GetContext(name)
if err != nil {
return fmt.Errorf("context %q does not exist", name) return fmt.Errorf("context %q does not exist", name)
} }
c.flags.Namespace = &context.Namespace flags := genericclioptions.NewConfigFlags(UsePersistentConfig)
c.flags.Context = &name flags.Context = &name
c.flags.ClusterName = &(context.Cluster) flags.Timeout = c.flags.Timeout
c.reset() c.flags = flags
return nil return nil
} }
func (c *Config) RawConfig() *clientcmdapi.Config {
return c.rawCfg
}
// CurrentContextName returns the currently active config context. // CurrentContextName returns the currently active config context.
func (c *Config) CurrentContextName() (string, error) { func (c *Config) CurrentContextName() (string, error) {
if isSet(c.flags.Context) { if isSet(c.flags.Context) {
return *c.flags.Context, nil return *c.flags.Context, nil
} }
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -121,7 +97,7 @@ func (c *Config) CurrentContextName() (string, error) {
// GetContext fetch a given context or error if it does not exists. // GetContext fetch a given context or error if it does not exists.
func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) { func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) {
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -134,7 +110,7 @@ func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) {
// Contexts fetch all available contexts. // Contexts fetch all available contexts.
func (c *Config) Contexts() (map[string]*clientcmdapi.Context, error) { func (c *Config) Contexts() (map[string]*clientcmdapi.Context, error) {
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -144,7 +120,7 @@ func (c *Config) Contexts() (map[string]*clientcmdapi.Context, error) {
// DelContext remove a given context from the configuration. // DelContext remove a given context from the configuration.
func (c *Config) DelContext(n string) error { func (c *Config) DelContext(n string) error {
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return err return err
} }
@ -155,12 +131,12 @@ func (c *Config) DelContext(n string) error {
return err return err
} }
return clientcmd.ModifyConfig(acc, *cfg, true) return clientcmd.ModifyConfig(acc, cfg, true)
} }
// ContextNames fetch all available contexts. // ContextNames fetch all available contexts.
func (c *Config) ContextNames() ([]string, error) { func (c *Config) ContextNames() ([]string, error) {
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -174,7 +150,7 @@ func (c *Config) ContextNames() ([]string, error) {
// ClusterNameFromContext returns the cluster associated with the given context. // ClusterNameFromContext returns the cluster associated with the given context.
func (c *Config) ClusterNameFromContext(context string) (string, error) { func (c *Config) ClusterNameFromContext(context string) (string, error) {
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -190,7 +166,7 @@ func (c *Config) CurrentClusterName() (string, error) {
if isSet(c.flags.ClusterName) { if isSet(c.flags.ClusterName) {
return *c.flags.ClusterName, nil return *c.flags.ClusterName, nil
} }
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -208,7 +184,7 @@ func (c *Config) CurrentClusterName() (string, error) {
// ClusterNames fetch all kubeconfig defined clusters. // ClusterNames fetch all kubeconfig defined clusters.
func (c *Config) ClusterNames() ([]string, error) { func (c *Config) ClusterNames() ([]string, error) {
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -258,7 +234,7 @@ func (c *Config) CurrentUserName() (string, error) {
return *c.flags.AuthInfoName, nil return *c.flags.AuthInfoName, nil
} }
cfg, err := c.rawConfig() cfg, err := c.RawConfig()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -276,8 +252,8 @@ func (c *Config) CurrentUserName() (string, error) {
// CurrentNamespaceName retrieves the active namespace. // CurrentNamespaceName retrieves the active namespace.
func (c *Config) CurrentNamespaceName() (string, error) { func (c *Config) CurrentNamespaceName() (string, error) {
ns, _, err := c.clientConfig().Namespace() ns, ov, err := c.clientConfig().Namespace()
fmt.Printf("!!!ZOB!!! %q -- %t\n", ns, ov)
return ns, err return ns, err
} }

View File

@ -146,6 +146,14 @@ func (c *Config) ActiveNamespace() string {
cl = NewCluster() cl = NewCluster()
c.K9s.Clusters[c.K9s.CurrentCluster] = cl c.K9s.Clusters[c.K9s.CurrentCluster] = cl
} }
if ns, err := c.settings.CurrentNamespaceName(); err == nil && ns != "" {
if cl.Namespace == nil {
cl.Namespace = NewNamespace()
}
cl.Namespace.Active = ns
return ns
}
if cl.Namespace != nil { if cl.Namespace != nil {
return cl.Namespace.Active return cl.Namespace.Active
} }

View File

@ -60,8 +60,7 @@ func TestConfigRefine(t *testing.T) {
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
mc := NewMockConnection() mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil) m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
mk := NewMockKubeSettings() mk := newMockSettings(u.flags)
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
cfg := config.NewConfig(mk) cfg := config.NewConfig(mk)
err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags)) err := cfg.Refine(u.flags, nil, client.NewConfig(u.flags))
@ -256,6 +255,24 @@ func TestSetup(t *testing.T) {
}) })
} }
type mockSettings struct {
flags *genericclioptions.ConfigFlags
}
var _ config.KubeSettings = (*mockSettings)(nil)
func newMockSettings(flags *genericclioptions.ConfigFlags) *mockSettings {
return &mockSettings{flags: flags}
}
func (m *mockSettings) CurrentContextName() (string, error) {
return *m.flags.Context, nil
}
func (m *mockSettings) CurrentClusterName() (string, error) { return "", nil }
func (m *mockSettings) CurrentNamespaceName() (string, error) {
return *m.flags.Namespace, nil
}
func (m *mockSettings) ClusterNames() ([]string, error) { return nil, nil }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Test Data... // Test Data...

View File

@ -2,13 +2,11 @@ package dao
import ( import (
"context" "context"
"errors"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/tools/clientcmd"
) )
var ( var (
@ -61,17 +59,3 @@ func (c *Context) MustCurrentContextName() string {
func (c *Context) Switch(ctx string) error { func (c *Context) Switch(ctx string) error {
return c.Factory.Client().SwitchContext(ctx) return c.Factory.Client().SwitchContext(ctx)
} }
// KubeUpdate modifies kubeconfig default context.
func (c *Context) KubeUpdate(n string) error {
cfg := c.config().RawConfig()
if cfg == nil {
return errors.New("unable to fetch raw config")
}
if err := c.Switch(n); err != nil {
return err
}
return clientcmd.ModifyConfig(
clientcmd.NewDefaultPathOptions(), *cfg, true,
)
}

View File

@ -9,6 +9,7 @@ import (
"github.com/derailed/k9s/internal/health" "github.com/derailed/k9s/internal/health"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -98,7 +99,11 @@ func (h *PulseHealth) checkMetrics(ctx context.Context) (health.Checks, error) {
func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check, error) { func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check, error) {
meta, ok := Registry[gvr] meta, ok := Registry[gvr]
if !ok { if !ok {
return nil, fmt.Errorf("No meta for %q", gvr) meta = ResourceMeta{
DAO: &dao.Table{},
Renderer: &render.Generic{},
}
// return nil, fmt.Errorf("No meta for %q", gvr)
} }
if meta.DAO == nil { if meta.DAO == nil {
meta.DAO = &dao.Resource{} meta.DAO = &dao.Resource{}
@ -109,8 +114,29 @@ func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check,
if err != nil { if err != nil {
return nil, err return nil, err
} }
c := health.NewCheck(gvr) c := health.NewCheck(gvr)
if _, ok := meta.Renderer.(*render.Generic); ok {
table, ok := oo[0].(*metav1beta1.Table)
if !ok {
return nil, fmt.Errorf("expecting a meta table but got %T", oo[0])
}
rows := make(render.Rows, len(table.Rows))
gr, _ := meta.Renderer.(*render.Generic)
gr.SetTable(table)
for i, row := range table.Rows {
if err := gr.Render(row, ns, &rows[i]); err != nil {
return nil, err
}
if !render.Happy(ns, gr.Header(ns), rows[i]) {
c.Inc(health.S2)
continue
}
c.Inc(health.S1)
}
c.Total(int64(len(table.Rows)))
return c, nil
}
c.Total(int64(len(oo))) c.Total(int64(len(oo)))
rr, re := make(render.Rows, len(oo)), meta.Renderer rr, re := make(render.Rows, len(oo)), meta.Renderer
for i, o := range oo { for i, o := range oo {
@ -119,9 +145,9 @@ func (h *PulseHealth) check(ctx context.Context, ns, gvr string) (*health.Check,
} }
if !render.Happy(ns, re.Header(ns), rr[i]) { if !render.Happy(ns, re.Header(ns), rr[i]) {
c.Inc(health.S2) c.Inc(health.S2)
} else { continue
c.Inc(health.S1)
} }
c.Inc(health.S1)
} }
return c, nil return c, nil

View File

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"sync" "sync"
"time" "time"
@ -24,8 +25,13 @@ const (
k9sUA = "k9s/" k9sUA = "k9s/"
) )
var (
// K9sBenchDir directory to store K9s Benchmark files. // K9sBenchDir directory to store K9s Benchmark files.
var K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser())) K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser()))
pathRx = regexp.MustCompile(`[:|]+`)
)
// Benchmark puts a workload under load. // Benchmark puts a workload under load.
type Benchmark struct { type Benchmark struct {
@ -126,7 +132,7 @@ func (b *Benchmark) save(cluster string, r io.Reader) error {
} }
ns, n := client.Namespaced(b.config.Name) ns, n := client.Namespaced(b.config.Name)
file := filepath.Join(dir, fmt.Sprintf(benchFmat, ns, n, time.Now().UnixNano())) file := filepath.Join(dir, fmt.Sprintf(benchFmat, ns, pathRx.ReplaceAllString(n, "_"), time.Now().UnixNano()))
f, err := os.Create(file) f, err := os.Create(file)
if err != nil { if err != nil {
return err return err

View File

@ -39,6 +39,10 @@ func TestPortForwardRender(t *testing.T) {
type fwd struct{} type fwd struct{}
func (f fwd) ID() string {
return "blee/fred"
}
func (f fwd) Path() string { func (f fwd) Path() string {
return "blee/fred" return "blee/fred"
} }

View File

@ -12,7 +12,10 @@ import (
// Forwarder represents a port forwarder. // Forwarder represents a port forwarder.
type Forwarder interface { type Forwarder interface {
// Path returns a resource FQN. // ID returns the PF FQN.
ID() string
// Path returns a resource path.
Path() string Path() string
// Container returns a container name. // Container returns a container name.
@ -61,9 +64,9 @@ func (f PortForward) Render(o interface{}, gvr string, r *Row) error {
} }
ports := strings.Split(pf.Port(), ":") ports := strings.Split(pf.Port(), ":")
ns, n := client.Namespaced(pf.Path()) ns, n := client.Namespaced(pf.ID())
r.ID = pf.Path() r.ID = pf.ID()
r.Fields = Fields{ r.Fields = Fields{
ns, ns,
trimContainer(n), trimContainer(n),

View File

@ -409,10 +409,11 @@ func (a *App) switchCtx(name string, loadPods bool) error {
v = "pod" v = "pod"
a.Config.SetActiveView(v) a.Config.SetActiveView(v)
} }
a.Config.Reset()
a.Config.K9s.CurrentContext = name
if err := a.Config.Save(); err != nil { if err := a.Config.Save(); err != nil {
log.Error().Err(err).Msg("Config save failed!") log.Error().Err(err).Msg("Config save failed!")
} }
a.Config.Reset()
a.Flash().Infof("Switching context to %s", name) a.Flash().Infof("Switching context to %s", name)
a.ReloadStyles(name) a.ReloadStyles(name)
@ -632,7 +633,7 @@ func (a *App) gotoResource(cmd, path string, clearStack bool) {
func (a *App) inject(c model.Component) error { func (a *App) inject(c model.Component) error {
ctx := context.WithValue(context.Background(), internal.KeyApp, a) ctx := context.WithValue(context.Background(), internal.KeyApp, a)
if err := c.Init(ctx); err != nil { if err := c.Init(ctx); err != nil {
log.Error().Err(err).Msgf("component init failed for %q %v", c.Name(), err) log.Error().Err(err).Msgf("component init failed for %q", c.Name())
dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, err.Error()) dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, err.Error())
} }
a.Content.Push(c) a.Content.Push(c)

View File

@ -42,7 +42,6 @@ func (p *PortForwardExtender) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt return evt
} }
p.fetchPodName(path)
pod, err := fetchPod(p.App().factory, path) pod, err := fetchPod(p.App().factory, path)
if err != nil { if err != nil {
p.App().Flash().Err(err) p.App().Flash().Err(err)

View File

@ -245,7 +245,7 @@ func (f *Factory) AddForwarder(pf Forwarder) {
f.mx.Lock() f.mx.Lock()
defer f.mx.Unlock() defer f.mx.Unlock()
f.forwarders[pf.Path()] = pf f.forwarders[pf.ID()] = pf
} }
// DeleteForwarder deletes portforward for a given container. // DeleteForwarder deletes portforward for a given container.