diff --git a/Makefile b/Makefile index d11c7a09..9985c448 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ PACKAGE := github.com/derailed/$(NAME) GIT_REV ?= $(shell git rev-parse --short HEAD) SOURCE_DATE_EPOCH ?= $(shell date +%s) DATE ?= $(shell date -u -d @${SOURCE_DATE_EPOCH} +"%Y-%m-%dT%H:%M:%SZ") -VERSION ?= v0.25.11 +VERSION ?= v0.25.12 IMG_NAME := derailed/k9s IMAGE := ${IMG_NAME}:${VERSION} diff --git a/change_logs/release_v0.25.12.md b/change_logs/release_v0.25.12.md new file mode 100644 index 00000000..aac6d0b4 --- /dev/null +++ b/change_logs/release_v0.25.12.md @@ -0,0 +1,54 @@ + + +# Release v0.25.12 + +## 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!! + +--- + +## ♫ Sounds Behind The Release ♭ + +* [Border Patrol - Eek A Mouse](https://www.youtube.com/watch?v=pQVNzolpoII) +* [All Mine - Portishead](https://www.youtube.com/watch?v=cuclNJiE8NY) +* [Come on up to the house - Tom Waits](https://www.youtube.com/watch?v=9XVGAatyeNk) + +## Maintenance Release! + +Hoy! end of year is... sucking! Feeling the burn ;( Apologies for the disruptions!! + +`You're either a pigeon or... the statue!` + +--- + +## Resolved Issues + +* [Issue #1378](https://github.com/derailed/k9s/issues/1378) Regression: Namespace filters are no longer applied on startup +* [Issue #1376](https://github.com/derailed/k9s/issues/1376) Events not sorted correctly by dates +* [Issue #1375](https://github.com/derailed/k9s/issues/1375) Unable to show port forwards +* [Issue #1374](https://github.com/derailed/k9s/issues/1374) --all-namespaces does not work v0.25.10 +* [Issue #1373](https://github.com/derailed/k9s/issues/1373) change namespace not possible + +--- + + © 2021 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0) diff --git a/cmd/info_test.go b/cmd/info_test.go index a7f88954..867d3478 100644 --- a/cmd/info_test.go +++ b/cmd/info_test.go @@ -1,45 +1,42 @@ package cmd import ( + "testing" + "github.com/derailed/k9s/internal/config" "github.com/stretchr/testify/assert" - "testing" ) func Test_getScreenDumpDirForInfo(t *testing.T) { - tests := []struct { - name string + tests := map[string]struct { k9sConfigFile string expectedScreenDumpDir string }{ - { - name: "withK9sConfigFile", + "withK9sConfigFile": { k9sConfigFile: "testdata/k9s.yml", expectedScreenDumpDir: "/tmp", }, - { - name: "withEmptyK9sConfigFile", + "withEmptyK9sConfigFile": { k9sConfigFile: "", expectedScreenDumpDir: config.K9sDefaultScreenDumpDir, }, - { - name: "withInvalidK9sConfigFilePath", + "withInvalidK9sConfigFilePath": { k9sConfigFile: "invalid", expectedScreenDumpDir: config.K9sDefaultScreenDumpDir, }, - { - name: "withScreenDumpDirEmptyInK9sConfigFile", + "withScreenDumpDirEmptyInK9sConfigFile": { k9sConfigFile: "testdata/k9s1.yml", expectedScreenDumpDir: config.K9sDefaultScreenDumpDir, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for k := range tests { + u := tests[k] + t.Run(k, func(t *testing.T) { initK9sConfigFile := config.K9sConfigFile - config.K9sConfigFile = tt.k9sConfigFile + config.K9sConfigFile = u.k9sConfigFile - assert.Equal(t, tt.expectedScreenDumpDir, getScreenDumpDirForInfo()) + assert.Equal(t, u.expectedScreenDumpDir, getScreenDumpDirForInfo()) config.K9sConfigFile = initK9sConfigFile }) diff --git a/internal/config/config.go b/internal/config/config.go index f94cf4e3..7680fbc7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -99,7 +99,10 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags, k9sFlags *Flags, c ns = *flags.Namespace } else if context.Namespace != "" { ns = context.Namespace - } else if cl := c.K9s.ActiveCluster(); cl != nil { + if cl := c.K9s.ActiveCluster(); cl != nil && cl.Namespace != nil && cl.Namespace.Active != "" { + ns = cl.Namespace.Active + } + } else if cl := c.K9s.ActiveCluster(); cl != nil && cl.Namespace != nil { ns = cl.Namespace.Active } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index f7883241..3d54f1ce 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -30,7 +30,7 @@ func TestConfigRefine(t *testing.T) { issue: false, context: "test1", cluster: "cluster1", - namespace: "ns1", + namespace: "default", }, "overrideNS": { flags: &genericclioptions.ConfigFlags{ diff --git a/internal/dao/benchmark.go b/internal/dao/benchmark.go index 65f85a8e..9d81223a 100644 --- a/internal/dao/benchmark.go +++ b/internal/dao/benchmark.go @@ -5,6 +5,7 @@ import ( "errors" "os" "path/filepath" + "regexp" "strings" "github.com/derailed/k9s/internal" @@ -15,6 +16,8 @@ import ( var ( _ Accessor = (*Benchmark)(nil) _ Nuker = (*Benchmark)(nil) + + BenchRx = regexp.MustCompile(`[:|]+`) ) // Benchmark represents a benchmark resource. @@ -45,9 +48,10 @@ func (b *Benchmark) List(ctx context.Context, _ string) ([]runtime.Object, error return nil, err } + fileName := BenchRx.ReplaceAllString(strings.Replace(path, "/", "_", 1), "_") oo := make([]runtime.Object, 0, len(ff)) for _, f := range ff { - if path != "" && !strings.HasPrefix(f.Name(), strings.Replace(path, "/", "_", 1)) { + if path != "" && !strings.HasPrefix(f.Name(), fileName) { continue } diff --git a/internal/dao/port_forward.go b/internal/dao/port_forward.go index 58454510..1c3cbb8d 100644 --- a/internal/dao/port_forward.go +++ b/internal/dao/port_forward.go @@ -41,7 +41,7 @@ func (p *PortForward) List(ctx context.Context, _ string) ([]runtime.Object, err config, err := config.NewBench(benchFile) if err != nil { - log.Debug().Msgf("No custom benchmark config file found") + log.Warn().Msgf("No custom benchmark config file found") } ff, cc := p.Factory.Forwarders(), config.Benchmarks.Containers diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index a1681566..5940eb45 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -4,6 +4,7 @@ import ( "fmt" "net/http" "net/url" + "strings" "time" "github.com/derailed/k9s/internal/client" @@ -72,11 +73,6 @@ func (p *PortForwarder) LocalPort() string { return p.tunnel.LocalPort } -// Path returns the pod resource path. -func (p *PortForwarder) Path() string { - return PortForwardID(p.path, p.tunnel.Container, p.tunnel.PortMap()) -} - // ID returns a pf id. func (p *PortForwarder) ID() string { return PortForwardID(p.path, p.tunnel.Container, p.tunnel.PortMap()) @@ -117,9 +113,10 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por return nil, fmt.Errorf("user is not authorized to get pods") } + podName := strings.Split(n, "|")[0] var res Pod res.Init(p, client.NewGVR("v1/pods")) - pod, err := res.GetInstance(path) + pod, err := res.GetInstance(client.FQN(ns, podName)) if err != nil { return nil, err } @@ -150,7 +147,7 @@ func (p *PortForwarder) Start(path string, tt port.PortTunnel) (*portforward.Por req := clt.Post(). Resource("pods"). Namespace(ns). - Name(n). + Name(podName). SubResource("portforward") return p.forwardPorts("POST", req.URL(), tt.Address, tt.PortMap()) @@ -175,6 +172,10 @@ func (p *PortForwarder) forwardPorts(method string, url *url.URL, addr, portMap // PortForwardID computes port-forward identifier. func PortForwardID(path, co, portMap string) string { + if strings.Contains(path, "|") { + return path + "|" + portMap + } + return path + "|" + co + "|" + portMap } diff --git a/internal/perf/benchmark.go b/internal/perf/benchmark.go index 1bf19a8a..9ed0af01 100644 --- a/internal/perf/benchmark.go +++ b/internal/perf/benchmark.go @@ -4,11 +4,11 @@ import ( "bytes" "context" "fmt" + "github.com/derailed/k9s/internal/dao" "io" "net/http" "os" "path/filepath" - "regexp" "sync" "time" @@ -29,8 +29,6 @@ const ( var ( // K9sBenchDir directory to store K9s Benchmark files. K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser())) - - pathRx = regexp.MustCompile(`[:|]+`) ) // Benchmark puts a workload under load. @@ -132,7 +130,7 @@ func (b *Benchmark) save(cluster string, r io.Reader) error { } ns, n := client.Namespaced(b.config.Name) - file := filepath.Join(dir, fmt.Sprintf(benchFmat, ns, pathRx.ReplaceAllString(n, "_"), time.Now().UnixNano())) + file := filepath.Join(dir, fmt.Sprintf(benchFmat, ns, dao.BenchRx.ReplaceAllString(n, "_"), time.Now().UnixNano())) f, err := os.Create(file) if err != nil { return err diff --git a/internal/render/portforward.go b/internal/render/portforward.go index 2465d255..ae5af4e1 100644 --- a/internal/render/portforward.go +++ b/internal/render/portforward.go @@ -15,9 +15,6 @@ type Forwarder interface { // ID returns the PF FQN. ID() string - // Path returns a resource path. - Path() string - // Container returns a container name. Container() string @@ -66,9 +63,9 @@ func (f PortForward) Render(o interface{}, gvr string, r *Row) error { } ports := strings.Split(pf.Port(), ":") - ns, n := client.Namespaced(pf.ID()) - r.ID = pf.ID() + ns, n := client.Namespaced(r.ID) + r.Fields = Fields{ ns, trimContainer(n), diff --git a/internal/view/container.go b/internal/view/container.go index c111949f..7925aa0a 100644 --- a/internal/view/container.go +++ b/internal/view/container.go @@ -124,7 +124,7 @@ func (c *Container) showPFCmd(evt *tcell.EventKey) *tcell.EventKey { } if !c.App().factory.Forwarders().IsContainerForwarded(c.GetTable().Path, path) { - c.App().Flash().Errf("no portforwards defined") + c.App().Flash().Errf("no port-forward defined") return nil } pf := NewPortForward(client.NewGVR("portforwards")) @@ -174,7 +174,7 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { } if _, ok := c.App().factory.ForwarderFor(fwFQN(c.GetTable().Path, path)); ok { - c.App().Flash().Err(fmt.Errorf("A PortForward already exist on container %s", c.GetTable().Path)) + c.App().Flash().Err(fmt.Errorf("A port-forward already exist on container %s", c.GetTable().Path)) return nil } @@ -182,7 +182,7 @@ func (c *Container) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { if !ok { return nil } - ShowPortForwards(c, c.GetTable().Path, ports, ann, startFwdCB) + ShowPortForwards(c, c.GetTable().Path+"|"+path, ports, ann, startFwdCB) return nil } @@ -232,11 +232,5 @@ func (c *Container) listForwardable(path string) (port.ContainerPortSpecs, map[s return nil, nil, false } - exposedPorts := port.FromContainerPorts(path, co.Ports) - if len(exposedPorts) == 0 { - c.App().Flash().Err(errors.New("Container exposes no ports")) - return nil, nil, false - } - return port.FromContainerPorts(path, co.Ports), po.Annotations, true } diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 51eb57df..0518e1fa 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -142,7 +142,7 @@ func asKey(key string) (tcell.Key, error) { // FwFQN returns a fully qualified ns/name:container id. func fwFQN(po, co string) string { - return po + ":" + co + return po + "|" + co } func isTCPPort(p string) bool { diff --git a/internal/view/helpers_test.go b/internal/view/helpers_test.go index c08289f1..63af0f8e 100644 --- a/internal/view/helpers_test.go +++ b/internal/view/helpers_test.go @@ -85,7 +85,7 @@ func TestFwFQN(t *testing.T) { uu := map[string]struct { po, co, e string }{ - "cool": {po: "p1", co: "c1", e: "p1:c1"}, + "cool": {po: "p1", co: "c1", e: "p1|c1"}, } for k := range uu { diff --git a/internal/view/pf_dialog.go b/internal/view/pf_dialog.go index 85a10c80..90c78074 100644 --- a/internal/view/pf_dialog.go +++ b/internal/view/pf_dialog.go @@ -34,7 +34,7 @@ func ShowPortForwards(v ResourceViewer, path string, ports port.ContainerPortSpe pf, err := aa.PreferredPorts(ports) if err != nil { - log.Warn().Err(err).Msgf("unable to resolve ports") + log.Warn().Err(err).Msgf("unable to resolve ports on %s", path) } p1, p2 := pf.ToPortSpec(ports) diff --git a/internal/view/pf_extender.go b/internal/view/pf_extender.go index 7a14affa..bcf24139 100644 --- a/internal/view/pf_extender.go +++ b/internal/view/pf_extender.go @@ -42,7 +42,12 @@ func (p *PortForwardExtender) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - pod, err := fetchPod(p.App().factory, path) + podName, err := p.fetchPodName(path) + if err != nil { + p.App().Flash().Err(err) + return nil + } + pod, err := fetchPod(p.App().factory, podName) if err != nil { p.App().Flash().Err(err) return nil @@ -52,10 +57,10 @@ func (p *PortForwardExtender) portFwdCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } if p.App().factory.Forwarders().IsPodForwarded(path) { - p.App().Flash().Errf("A PortForward already exist for pod %s", pod) + p.App().Flash().Errf("A PortForward already exist for pod %s", pod.Name) return nil } - if err := showFwdDialog(p, path, startFwdCB); err != nil { + if err := showFwdDialog(p, podName, startFwdCB); err != nil { p.App().Flash().Err(err) } diff --git a/internal/view/pod.go b/internal/view/pod.go index 6146442a..a028f4be 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -137,7 +137,7 @@ func (p *Pod) showPFCmd(evt *tcell.EventKey) *tcell.EventKey { } if !p.App().factory.Forwarders().IsPodForwarded(path) { - p.App().Flash().Errf("no portforwards defined") + p.App().Flash().Errf("no port-forward defined") return nil } pf := NewPortForward(client.NewGVR("portforwards")) diff --git a/internal/watch/factory.go b/internal/watch/factory.go index bef45022..5d358e6d 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -267,10 +267,8 @@ func (f *Factory) ForwarderFor(path string) (Forwarder, bool) { f.mx.RLock() defer f.mx.RUnlock() - for k := range f.forwarders { - log.Debug().Msgf("KEY %q::%q", k, path) - } fwd, ok := f.forwarders[path] + return fwd, ok } diff --git a/internal/watch/forwarders.go b/internal/watch/forwarders.go index c0a0c28b..c69f8b98 100644 --- a/internal/watch/forwarders.go +++ b/internal/watch/forwarders.go @@ -19,9 +19,6 @@ type Forwarder interface { // ID returns the pf id. ID() string - // Path returns a resource FQN. - Path() string - // Container returns a container name. Container() string @@ -54,27 +51,32 @@ func NewForwarders() Forwarders { // BOZO!! Review!!! // IsPodForwarded checks if pod has a forward. -func (ff Forwarders) IsPodForwarded(path string) bool { +func (ff Forwarders) IsPodForwarded(fqn string) bool { for k := range ff { - fqn := strings.Split(k, "|") - if fqn[0] == path { + if strings.HasPrefix(k, fqn) { return true } } + return false } // IsContainerForwarded checks if pod has a forward. -func (ff Forwarders) IsContainerForwarded(path, co string) bool { - _, ok := ff[path+":"+co] +func (ff Forwarders) IsContainerForwarded(fqn, co string) bool { + prefix := fqn+"|"+co + for k := range ff { + if strings.HasPrefix(k, prefix) { + return true + } + } - return ok + return false } // DeleteAll stops and delete all port-forwards. func (ff Forwarders) DeleteAll() { for k, f := range ff { - log.Debug().Msgf("Deleting forwarder %s", f.Path()) + log.Debug().Msgf("Deleting forwarder %s", f.ID()) f.Stop() delete(ff, k) }