From d72467a582a55c5067985da9cf86c809f1c16375 Mon Sep 17 00:00:00 2001 From: Fernand Galiana Date: Sun, 19 Jan 2025 09:47:29 -0700 Subject: [PATCH] Fix lint build (#3066) * clean up * fix bug computing resource limits * update rs rendering to match kubectl * display ctx view if all hell fails * clean ups and refactors * fix linter issues --- README.md | 4 ++-- internal/client/client.go | 5 +---- internal/config/data/helpers_test.go | 2 +- internal/config/data/proxy.go | 3 +++ internal/config/helpers.go | 8 ++------ internal/config/k9s.go | 6 +++--- internal/dao/container.go | 2 +- internal/dao/generic.go | 8 +++----- internal/dao/node.go | 2 +- internal/dao/registry.go | 8 ++------ internal/render/pod.go | 5 +++-- internal/render/rs.go | 16 +++++++++++++++- internal/view/app.go | 2 +- internal/view/command.go | 13 +++++++++---- internal/view/log.go | 3 ++- internal/view/pf.go | 2 -- internal/watch/factory.go | 13 +++++++++---- 17 files changed, 58 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index f1004cb7..af360d9f 100644 --- a/README.md +++ b/README.md @@ -1088,12 +1088,12 @@ to make this project a reality! ## Meet The Core Team! +If you have chops in GO and K8s and would like to offer your time to help maintain and enhance this project, please reach out to me. + * [Fernand Galiana](https://github.com/derailed) * email fernand@imhotep.io * twitter [@kitesurfer](https://twitter.com/kitesurfer?lang=en) -* [Aleksei Romanenko](https://github.com/slimus) - We always enjoy hearing from folks who benefit from our work! ## Contributions Guideline diff --git a/internal/client/client.go b/internal/client/client.go index 626e69e2..be29c54c 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -545,16 +545,13 @@ func (a *APIClient) SwitchContext(name string) error { if err := a.config.SwitchContext(name); err != nil { return err } - if err := a.invalidateCache(); err != nil { - return err - } a.reset() ResetMetrics() // Need reload to pick up any kubeconfig changes. a.config = NewConfig(a.config.flags) - return nil + return a.invalidateCache() } func (a *APIClient) reset() { diff --git a/internal/config/data/helpers_test.go b/internal/config/data/helpers_test.go index 0b41d432..db95f28a 100644 --- a/internal/config/data/helpers_test.go +++ b/internal/config/data/helpers_test.go @@ -63,7 +63,7 @@ func TestHelperInList(t *testing.T) { func TestEnsureDirPathNone(t *testing.T) { var mod os.FileMode = 0744 - dir := filepath.Join("/tmp", "fred") + dir := filepath.Join("/tmp", "k9s-test") os.Remove(dir) path := filepath.Join(dir, "duh.yaml") diff --git a/internal/config/data/proxy.go b/internal/config/data/proxy.go index 6bce7543..a5b53f5d 100644 --- a/internal/config/data/proxy.go +++ b/internal/config/data/proxy.go @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of K9s + package data // Proxy tracks a context's proxy configuration. diff --git a/internal/config/helpers.go b/internal/config/helpers.go index a62b3cd9..53627cfa 100644 --- a/internal/config/helpers.go +++ b/internal/config/helpers.go @@ -13,7 +13,8 @@ import ( v1 "k8s.io/api/core/v1" ) -func isBoolSet(b *bool) bool { +// IsBoolSet checks if a bool ptr is set. +func IsBoolSet(b *bool) bool { return b != nil && *b } @@ -70,8 +71,3 @@ func MustK9sUser() string { } return usr.Username } - -// IsBoolSet checks if a bool prt is set. -func IsBoolSet(b *bool) bool { - return b != nil && *b -} diff --git a/internal/config/k9s.go b/internal/config/k9s.go index df2e231a..5cc2ec41 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -292,7 +292,7 @@ func (k *K9s) Override(k9sFlags *Flags) { // IsHeadless returns headless setting. func (k *K9s) IsHeadless() bool { - if isBoolSet(k.manualHeadless) { + if IsBoolSet(k.manualHeadless) { return true } @@ -301,7 +301,7 @@ func (k *K9s) IsHeadless() bool { // IsLogoless returns logoless setting. func (k *K9s) IsLogoless() bool { - if isBoolSet(k.manualLogoless) { + if IsBoolSet(k.manualLogoless) { return true } @@ -310,7 +310,7 @@ func (k *K9s) IsLogoless() bool { // IsCrumbsless returns crumbsless setting. func (k *K9s) IsCrumbsless() bool { - if isBoolSet(k.manualCrumbsless) { + if IsBoolSet(k.manualCrumbsless) { return true } diff --git a/internal/dao/container.go b/internal/dao/container.go index 0fd1c469..14b20344 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -116,7 +116,7 @@ func getContainerStatus(kind string, name string, status v1.PodStatus) *v1.Conta func (c *Container) fetchPod(fqn string) (*v1.Pod, error) { o, err := c.getFactory().Get("v1/pods", fqn, true, labels.Everything()) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to locate pod %q: %w", fqn, err) } var po v1.Pod err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po) diff --git a/internal/dao/generic.go b/internal/dao/generic.go index e0aff2b6..e7d95ccc 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -43,15 +43,12 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) ns = client.BlankNamespace } - var ( - ll *unstructured.UnstructuredList - err error - ) dial, err := g.dynClient() if err != nil { return nil, err } + var ll *unstructured.UnstructuredList if client.IsClusterScoped(ns) { ll, err = dial.List(ctx, metav1.ListOptions{LabelSelector: labelSel}) } else { @@ -71,12 +68,13 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) // Get returns a given resource. func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error) { - var opts metav1.GetOptions ns, n := client.Namespaced(path) dial, err := g.dynClient() if err != nil { return nil, err } + + var opts metav1.GetOptions if client.IsClusterScoped(ns) { return dial.Get(ctx, n, opts) } diff --git a/internal/dao/node.go b/internal/dao/node.go index 08fea0b1..d348255b 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -257,7 +257,7 @@ func FetchNode(ctx context.Context, f Factory, path string) (*v1.Node, error) { return nil, fmt.Errorf("user is not authorized to list nodes") } - o, err := f.Get("v1/nodes", client.FQN(client.ClusterScope, path), false, labels.Everything()) + o, err := f.Get("v1/nodes", client.FQN(client.ClusterScope, path), true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/dao/registry.go b/internal/dao/registry.go index f512bc60..ac092c88 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -424,7 +424,8 @@ func loadCRDs(f Factory, m ResourceMetas) { var meta metav1.APIResource meta.Kind = crd.Spec.Names.Kind meta.Group = crd.Spec.Group - meta.Name = crd.Name + meta.Name = strings.TrimSuffix(crd.Name, "."+meta.Group) + meta.SingularName = crd.Spec.Names.Singular meta.ShortNames = crd.Spec.Names.ShortNames meta.Namespaced = crd.Spec.Scope == apiext.NamespaceScoped @@ -435,11 +436,6 @@ func loadCRDs(f Factory, m ResourceMetas) { } } - // meta, errs := extractMeta(o) - // if len(errs) > 0 { - // log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs)) - // continue - // } meta.Categories = append(meta.Categories, crdCat) gvr := client.NewGVRFromMeta(meta) m[gvr] = meta diff --git a/internal/render/pod.go b/internal/render/pod.go index dc3395a2..a501cad6 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -138,8 +138,8 @@ func (p Pod) Render(o interface{}, ns string, row *model1.Row) error { } c, r := gatherCoMX(&po.Spec, ccmx) phase := p.Phase(&po) - row.ID = client.MetaFQN(po.ObjectMeta) + row.ID = client.MetaFQN(po.ObjectMeta) row.Fields = model1.Fields{ po.Namespace, po.Name, @@ -254,7 +254,7 @@ func cosLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) { for _, c := range cc { limits := c.Resources.Limits if len(limits) == 0 { - return resource.Quantity{}, resource.Quantity{} + continue } if limits.Cpu() != nil { cpu.Add(*limits.Cpu()) @@ -263,6 +263,7 @@ func cosLimits(cc []v1.Container) (resource.Quantity, resource.Quantity) { mem.Add(*limits.Memory()) } } + return *cpu, *mem } diff --git a/internal/render/rs.go b/internal/render/rs.go index 85d5dbed..1046014f 100644 --- a/internal/render/rs.go +++ b/internal/render/rs.go @@ -6,6 +6,7 @@ package render import ( "fmt" "strconv" + "strings" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/model1" @@ -34,7 +35,9 @@ func (ReplicaSet) Header(ns string) model1.Header { model1.HeaderColumn{Name: "DESIRED", Align: tview.AlignRight}, model1.HeaderColumn{Name: "CURRENT", Align: tview.AlignRight}, model1.HeaderColumn{Name: "READY", Align: tview.AlignRight}, - model1.HeaderColumn{Name: "LABELS", Wide: true}, + model1.HeaderColumn{Name: "CONTAINERS", Wide: true}, + model1.HeaderColumn{Name: "IMAGES", Wide: true}, + model1.HeaderColumn{Name: "SELECTOR", Wide: true}, model1.HeaderColumn{Name: "VALID", Wide: true}, model1.HeaderColumn{Name: "AGE", Time: true}, } @@ -46,12 +49,21 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *model1.Row) error { if !ok { return fmt.Errorf("expected ReplicaSet, but got %T", o) } + var rs appsv1.ReplicaSet err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &rs) if err != nil { return err } + var ( + cc = rs.Spec.Template.Spec.Containers + cos, imgs = make([]string, 0, len(cc)), make([]string, 0, len(cc)) + ) + for _, co := range cc { + cos, imgs = append(cos, co.Name), append(imgs, co.Image) + } + row.ID = client.MetaFQN(rs.ObjectMeta) row.Fields = model1.Fields{ rs.Namespace, @@ -60,6 +72,8 @@ func (r ReplicaSet) Render(o interface{}, ns string, row *model1.Row) error { strconv.Itoa(int(*rs.Spec.Replicas)), strconv.Itoa(int(rs.Status.Replicas)), strconv.Itoa(int(rs.Status.ReadyReplicas)), + strings.Join(cos, ","), + strings.Join(imgs, ","), mapToStr(rs.Labels), AsStatus(r.diagnose(rs)), ToAge(rs.GetCreationTimestamp()), diff --git a/internal/view/app.go b/internal/view/app.go index 4ac7e7c2..72f9e028 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -530,7 +530,7 @@ func (a *App) Run() error { }) }() - if err := a.command.defaultCmd(); err != nil { + if err := a.command.defaultCmd(true); err != nil { return err } a.SetRunning(true) diff --git a/internal/view/command.go b/internal/view/command.go index bb81826c..c57dec80 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -209,19 +209,24 @@ func (c *Command) run(p *cmd.Interpreter, fqn string, clearStack bool) error { return c.exec(p, gvr, co, clearStack) } -func (c *Command) defaultCmd() error { +func (c *Command) defaultCmd(isRoot bool) error { if c.app.Conn() == nil || !c.app.Conn().ConnectionOK() { return c.run(cmd.NewInterpreter("context"), "", true) } + defCmd := "pod" + if isRoot { + defCmd = "ctx" + } p := cmd.NewInterpreter(c.app.Config.ActiveView()) if p.IsBlank() { - return c.run(p.Reset("pod"), "", true) + return c.run(p.Reset(defCmd), "", true) } if err := c.run(p, "", true); err != nil { - log.Error().Err(err).Msgf("Default run command failed %q", p.GetLine()) - return c.run(p.Reset("pod"), "", true) + p = p.Reset(defCmd) + log.Error().Err(fmt.Errorf("Command failed. Using default command: %s", p.GetLine())) + return c.run(p, "", true) } return nil diff --git a/internal/view/log.go b/internal/view/log.go index be01ed5a..d5682e78 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -393,6 +393,7 @@ func (l *Log) toggleAllContainers(evt *tcell.EventKey) *tcell.EventKey { func (l *Log) filterCmd(evt *tcell.EventKey) *tcell.EventKey { if !l.logs.cmdBuff.IsActive() { + fmt.Fprintln(l.ansiWriter) return evt } @@ -451,7 +452,7 @@ func (l *Log) clearCmd(*tcell.EventKey) *tcell.EventKey { func (l *Log) markCmd(*tcell.EventKey) *tcell.EventKey { _, _, w, _ := l.GetRect() - fmt.Fprintf(l.ansiWriter, "\n[%s:-:b]%s[-:-:-]", l.app.Styles.Views().Log.FgColor.String(), strings.Repeat("─", w-4)) + fmt.Fprintf(l.ansiWriter, "\n[%s:-:b]%s[-:-:-]", l.app.Styles.Views().Log.FgColor.String(), strings.Repeat("-", w-4)) l.follow = true return nil diff --git a/internal/view/pf.go b/internal/view/pf.go index 246bd9ed..252ea533 100644 --- a/internal/view/pf.go +++ b/internal/view/pf.go @@ -20,8 +20,6 @@ import ( "github.com/rs/zerolog/log" ) -const promptPage = "prompt" - // PortForward presents active portforward viewer. type PortForward struct { ResourceViewer diff --git a/internal/watch/factory.go b/internal/watch/factory.go index 6c896afa..eccbd38f 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -21,7 +21,7 @@ import ( const ( defaultResync = 10 * time.Minute - defaultWaitTime = 250 * time.Millisecond + defaultWaitTime = 500 * time.Millisecond ) // Factory tracks various resource informers. @@ -72,13 +72,13 @@ func (f *Factory) Terminate() { // List returns a resource collection. func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]runtime.Object, error) { + if client.IsAllNamespace(ns) { + ns = client.BlankNamespace + } inf, err := f.CanForResource(ns, gvr, client.ListAccess) if err != nil { return nil, err } - if client.IsAllNamespace(ns) { - ns = client.BlankNamespace - } var oo []runtime.Object if client.IsClusterScoped(ns) { @@ -110,6 +110,10 @@ func (f *Factory) HasSynced(gvr, ns string) (bool, error) { // Get retrieves a given resource. func (f *Factory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runtime.Object, error) { ns, n := namespaced(fqn) + if client.IsAllNamespace(ns) { + ns = client.BlankNamespace + } + inf, err := f.CanForResource(ns, gvr, []string{client.GetVerb}) if err != nil { return nil, err @@ -128,6 +132,7 @@ func (f *Factory) Get(gvr, fqn string, wait bool, sel labels.Selector) (runtime. if client.IsClusterScoped(ns) { return inf.Lister().Get(n) } + return inf.Lister().ByNamespace(ns).Get(n) }