diff --git a/.golangci.yml b/.golangci.yml index bf0d6d22..5f004fc9 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -299,8 +299,7 @@ issues: # of integration: much better don't allow issues in new code. # Default is false. new: false - # Show only new issues created after git revision `REV` - new-from-rev: REV + # new-from-rev: REV # Show only new issues created in git patch with set file path. # new-from-patch: path/to/patch/file diff --git a/internal/client/client.go b/internal/client/client.go index 7d3d9461..2b5d2caa 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -1,7 +1,6 @@ package client import ( - "fmt" "path/filepath" "sync" "time" @@ -10,7 +9,6 @@ import ( authorizationv1 "k8s.io/api/authorization/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery/cached/disk" @@ -26,53 +24,46 @@ const NA = "n/a" var supportedMetricsAPIVersions = []string{"v1beta1"} -type ( - // Collection of empty interfaces. - Collection []interface{} +// Authorizer checks what a user can or cannot do to a resource. +type Authorizer interface { + // CanI returns true if the user can use these actions for a given resource. + CanI(ns, gvr string, verbs []string) (bool, error) +} - // Cruder represent a crudable Kubernetes resource. - Cruder interface { - Get(ns string, name string) (interface{}, error) - List(ns string) (Collection, error) - Delete(ns string, name string) error - SetFieldSelector(string) - SetLabelSelector(string) - } +// BOZO!! Refactor! +// Connection represents a Kubenetes apiserver connection. +type Connection interface { + Authorizer - // Connection represents a Kubenetes apiserver connection. - Connection interface { - Config() *Config - DialOrDie() kubernetes.Interface - SwitchContextOrDie(ctx string) - NSDialOrDie() dynamic.NamespaceableResourceInterface - CachedDiscovery() (*disk.CachedDiscoveryClient, error) - RestConfigOrDie() *restclient.Config - MXDial() (*versioned.Clientset, error) - DynDialOrDie() dynamic.Interface - HasMetrics() bool - IsNamespaced(n string) bool - SupportsResource(group string) bool - ValidNamespaces() ([]v1.Namespace, error) - NodePods(node string) (*v1.PodList, error) - SupportsRes(grp string, versions []string) (string, bool, error) - ServerVersion() (*version.Info, error) - FetchNodes() (*v1.NodeList, error) - CurrentNamespaceName() (string, error) - CanI(ns, gvr string, verbs []string) (bool, error) - } + Config() *Config + DialOrDie() kubernetes.Interface + SwitchContextOrDie(ctx string) + NSDialOrDie() dynamic.NamespaceableResourceInterface + CachedDiscovery() (*disk.CachedDiscoveryClient, error) + RestConfigOrDie() *restclient.Config + MXDial() (*versioned.Clientset, error) + DynDialOrDie() dynamic.Interface + HasMetrics() bool + IsNamespaced(n string) bool + SupportsResource(group string) bool + ValidNamespaces() ([]v1.Namespace, error) + SupportsRes(grp string, versions []string) (string, bool, error) + ServerVersion() (*version.Info, error) + FetchNodes() (*v1.NodeList, error) + CurrentNamespaceName() (string, error) +} - // APIClient represents a Kubernetes api client. - APIClient struct { - client kubernetes.Interface - dClient dynamic.Interface - nsClient dynamic.NamespaceableResourceInterface - mxsClient *versioned.Clientset - cachedDiscovery *disk.CachedDiscoveryClient - config *Config - useMetricServer bool - mx sync.Mutex - } -) +// APIClient represents a Kubernetes api client. +type APIClient struct { + client kubernetes.Interface + dClient dynamic.Interface + nsClient dynamic.NamespaceableResourceInterface + mxsClient *versioned.Clientset + cachedDiscovery *disk.CachedDiscoveryClient + config *Config + useMetricServer bool + mx sync.Mutex +} // InitConnectionOrDie initialize connection from command line args. // Checks for connectivity with the api server. @@ -143,20 +134,6 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) { return nn.Items, nil } -// NodePods returns a collection of all available pods on a given node. -func (a *APIClient) NodePods(node string) (*v1.PodList, error) { - panic("NYI") - const selFmt = "spec.nodeName=%s,status.phase!=%s,status.phase!=%s" - fieldSelector, err := fields.ParseSelector(fmt.Sprintf(selFmt, node, v1.PodSucceeded, v1.PodFailed)) - if err != nil { - return nil, err - } - - return a.DialOrDie().CoreV1().Pods("").List(metav1.ListOptions{ - FieldSelector: fieldSelector.String(), - }) -} - // IsNamespaced check on server if given resource is namespaced func (a *APIClient) IsNamespaced(res string) bool { discovery, err := a.CachedDiscovery() diff --git a/internal/dao/container.go b/internal/dao/container.go index 06b83806..aca7aefe 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -7,7 +7,6 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/watch" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -24,9 +23,6 @@ var _ Loggable = &Container{} // Logs tails a given container logs func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error { - log.Debug().Msgf("CO TAILLOGS %#v", ctx) - log.Debug().Msgf("CO TAILLOGS %#v", opts) - fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) if !ok { return errors.New("Expecting an informer") @@ -37,7 +33,7 @@ func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts Lo } var po v1.Pod - if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { return err } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 4fca4450..b67f0d5e 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -29,7 +29,7 @@ type Pod struct { } var _ Accessor = &Pod{} -var _Loggable = &Pod{} +var _ Loggable = &Pod{} // Logs fetch container logs for a given pod and container. func (p *Pod) Logs(path string, opts *v1.PodLogOptions) *restclient.Request { @@ -84,7 +84,7 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error } var po v1.Pod - if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { return err } opts.Color = asColor(po.Name) @@ -166,6 +166,7 @@ func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts L } } +// ---------------------------------------------------------------------------- // Helpers... func loggableContainers(s v1.PodStatus) []string { diff --git a/internal/dao/reconcile.go b/internal/dao/reconcile.go index d14b8ffe..4b173f32 100644 --- a/internal/dao/reconcile.go +++ b/internal/dao/reconcile.go @@ -46,8 +46,8 @@ func Reconcile(ctx context.Context, table render.TableData, gvr client.GVR) (ren return table, err } log.Debug().Msgf("Model returned [%d] items", len(oo)) + rows := make(render.Rows, len(oo)) - // BOZO!! Pass in header len to avoid recomputing the header. if err := m.Model.Hydrate(oo, rows, m.Renderer); err != nil { return table, err } diff --git a/internal/dao/registry.go b/internal/dao/registry.go index 36c92301..52f791e9 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -100,83 +100,60 @@ func Load(f *watch.Factory) error { return loadCRDs(f, resMetas) } +// BOZO!! Need contermeasure for direct commands! func loadNonResource(m ResourceMetas) error { m["aliases"] = metav1.APIResource{ - Name: "aliases", - SingularName: "alias", - Namespaced: false, - Kind: "Aliases", - Verbs: []string{}, - Categories: []string{"k9s"}, + Name: "aliases", + Kind: "Aliases", + Categories: []string{"k9s"}, } m["contexts"] = metav1.APIResource{ - Name: "contexts", - SingularName: "context", - Namespaced: false, - Kind: "Contexts", - ShortNames: []string{"ctx"}, - Verbs: []string{}, - Categories: []string{"k9s"}, + Name: "contexts", + Kind: "Contexts", + ShortNames: []string{"ctx"}, + Categories: []string{"k9s"}, } m["screendumps"] = metav1.APIResource{ - Name: "screendumps", - SingularName: "screendump", - Namespaced: false, - Kind: "ScreenDumps", - ShortNames: []string{"sd"}, - Verbs: []string{"delete"}, - Categories: []string{"k9s"}, + Name: "screendumps", + Kind: "ScreenDumps", + ShortNames: []string{"sd"}, + Verbs: []string{"delete"}, + Categories: []string{"k9s"}, } m["benchmarks"] = metav1.APIResource{ - Name: "benchmarks", - SingularName: "benchmark", - Namespaced: false, - Kind: "Benchmarks", - ShortNames: []string{"be"}, - Verbs: []string{"delete"}, - Categories: []string{"k9s"}, + Name: "benchmarks", + Kind: "Benchmarks", + ShortNames: []string{"be"}, + Verbs: []string{"delete"}, + Categories: []string{"k9s"}, } m["portforwards"] = metav1.APIResource{ - Name: "portforwards", - SingularName: "portforward", - Namespaced: true, - Kind: "PortForwards", - ShortNames: []string{"pf"}, - Verbs: []string{"delete"}, - Categories: []string{"k9s"}, + Name: "portforwards", + Namespaced: true, + Kind: "PortForwards", + ShortNames: []string{"pf"}, + Verbs: []string{"delete"}, + Categories: []string{"k9s"}, } - // BOZO!! policies can't be launch on command m["rbac"] = metav1.APIResource{ - Name: "Rbac", - SingularName: "Rbac", - Namespaced: false, - Kind: "RBAC", - Categories: []string{"k9s"}, + Name: "Rbac", + Kind: "RBAC", + Categories: []string{"k9s"}, } - // BOZO!! Containers can't be launch on command m["containers"] = metav1.APIResource{ - Name: "containers", - SingularName: "container", - Namespaced: false, - Kind: "Containers", - Verbs: []string{}, - Categories: []string{"k9s"}, + Name: "containers", + Kind: "Containers", + Categories: []string{"k9s"}, } m["users"] = metav1.APIResource{ - Name: "users", - SingularName: "user", - Namespaced: false, - Kind: "User", - Verbs: []string{}, - Categories: []string{"k9s"}, + Name: "users", + Kind: "User", + Categories: []string{"k9s"}, } m["groups"] = metav1.APIResource{ - Name: "groups", - SingularName: "group", - Namespaced: false, - Kind: "group", - Verbs: []string{}, - Categories: []string{"k9s"}, + Name: "groups", + Kind: "group", + Categories: []string{"k9s"}, } return nil diff --git a/internal/keys.go b/internal/keys.go index 597db607..935389de 100644 --- a/internal/keys.go +++ b/internal/keys.go @@ -3,19 +3,19 @@ package internal // ContextKey represents context key. type ContextKey string +// A collection of context keys. const ( - // Factory represents a factory context key. KeyFactory ContextKey = "factory" - KeyLabels = "labels" - KeyFields = "fields" - KeyTable = "table" - KeyDir = "dir" - KeyPath = "path" - KeySubject = "subject" - KeyGVR = "gvr" - KeyForwards = "forwards" - KeyContainers = "containers" - KeyBenchCfg = "benchcfg" - KeyAliases = "aliases" - KeyUID = "uid" + KeyLabels ContextKey = "labels" + KeyFields ContextKey = "fields" + KeyTable ContextKey = "table" + KeyDir ContextKey = "dir" + KeyPath ContextKey = "path" + KeySubject ContextKey = "subject" + KeyGVR ContextKey = "gvr" + KeyForwards ContextKey = "forwards" + KeyContainers ContextKey = "containers" + KeyBenchCfg ContextKey = "benchcfg" + KeyAliases ContextKey = "aliases" + KeyUID ContextKey = "uid" ) diff --git a/internal/model/container.go b/internal/model/container.go index 1c176ced..bcd0620e 100644 --- a/internal/model/container.go +++ b/internal/model/container.go @@ -68,8 +68,11 @@ func (c *Container) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) er var index int for _, o := range oo { - co := o.(ContainerRes) - row, err := renderCoRow(co.Container.Name, index, coMetricsFor(co.Container, c.pod, mmx, true), re) + co, ok := o.(ContainerRes) + if !ok { + return fmt.Errorf("expecting containerres but got `%T", o) + } + row, err := renderCoRow(co.Container.Name, coMetricsFor(co.Container, c.pod, mmx, true), re) if err != nil { return err } @@ -80,7 +83,7 @@ func (c *Container) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) er return nil } -func renderCoRow(n string, index int, pmx *ContainerWithMetrics, re Renderer) (render.Row, error) { +func renderCoRow(n string, pmx *ContainerWithMetrics, re Renderer) (render.Row, error) { var row render.Row if err := re.Render(pmx, n, &row); err != nil { return render.Row{}, err @@ -99,7 +102,11 @@ func coMetricsFor(co v1.Container, po *v1.Pod, mmx *mv1beta1.PodMetrics, isInit } func containerMetrics(n string, mx runtime.Object) *mv1beta1.ContainerMetrics { - pmx := mx.(*mv1beta1.PodMetrics) + pmx, ok := mx.(*mv1beta1.PodMetrics) + if !ok { + log.Error().Err(fmt.Errorf("expecting podmetrics but got `%T", mx)) + return nil + } for _, m := range pmx.Containers { if m.Name == n { return &m diff --git a/internal/model/generic.go b/internal/model/generic.go index 60719e4d..ea6d9ae7 100644 --- a/internal/model/generic.go +++ b/internal/model/generic.go @@ -71,10 +71,6 @@ func (g *Generic) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) erro if !ok { return fmt.Errorf("expecting RowRes but got %#v", o) } - count := len(res.Cells) - if g.namespace == "" { - count++ - } if err := gr.Render(res.TableRow, g.namespace, &rr[i]); err != nil { return err } diff --git a/internal/model/helpers.go b/internal/model/helpers.go index e19d37ee..401ae815 100644 --- a/internal/model/helpers.go +++ b/internal/model/helpers.go @@ -1,20 +1,39 @@ package model import ( + "fmt" + "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" + "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) func extractFQN(o runtime.Object) string { - u := o.(*unstructured.Unstructured) - m := u.Object["metadata"].(map[string]interface{}) - if _, ok := m["namespace"]; !ok { - return FQN("", m["name"].(string)) + u, ok := o.(*unstructured.Unstructured) + if !ok { + log.Error().Err(fmt.Errorf("expecting unstructured but got %T", o)) + return "na" } - ns, n := m["namespace"].(string), m["name"].(string) + m, ok := u.Object["metadata"].(map[string]interface{}) + if !ok { + log.Error().Err(fmt.Errorf("expecting interface map for metadata but got %T", u.Object["metadata"])) + return "na" + } + + n, ok := m["name"].(string) + if !ok { + log.Error().Err(fmt.Errorf("expecting interface map for name but got %T", m["name"])) + return "na" + } + + ns, ok := m["namespace"].(string) + if !ok { + return FQN("", n) + } + return FQN(ns, n) } diff --git a/internal/model/node.go b/internal/model/node.go index 74234f74..d4409516 100644 --- a/internal/model/node.go +++ b/internal/model/node.go @@ -2,6 +2,7 @@ package model import ( "context" + "fmt" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" @@ -29,8 +30,8 @@ func (n *Node) List(_ context.Context) ([]runtime.Object, error) { } oo := make([]runtime.Object, len(nn.Items)) - for i, no := range nn.Items { - o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&no) + for i, n := range nn.Items { + o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(n) if err != nil { return nil, err } @@ -39,6 +40,20 @@ func (n *Node) List(_ context.Context) ([]runtime.Object, error) { return oo, nil } +func nameFromMeta(m map[string]interface{}) string { + meta, ok := m["metadata"].(map[string]interface{}) + if !ok { + return "n/a" + } + + name, ok := meta["name"].(string) + if !ok { + return "n/a" + } + + return name +} + // Hydrate returns nodes as rows. func (n *Node) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { mx := client.NewMetricsServer(n.factory.Client()) @@ -47,23 +62,28 @@ func (n *Node) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { log.Warn().Err(err).Msg("No node metrics") } - var index int - for _, no := range oo { - o := no.(*unstructured.Unstructured) - pods, err := n.nodePods(n.factory, o.Object["metadata"].(map[string]interface{})["name"].(string)) + for i, o := range oo { + no, ok := o.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("expecting unstructured but got %T", o) + } + pods, err := n.nodePods(n.factory, nameFromMeta(no.Object)) if err != nil { return err } var ( row render.Row - nmx = NodeWithMetrics{object: o, mx: nodeMetricsFor(o, mmx), pods: pods} + nmx = NodeWithMetrics{ + object: no, + mx: nodeMetricsFor(o, mmx), + pods: pods, + } ) if err := re.Render(&nmx, "", &row); err != nil { return err } - rr[index] = row - index++ + rr[i] = row } return nil @@ -87,8 +107,10 @@ func (n *Node) nodePods(f Factory, node string) ([]*v1.Pod, error) { pods := make([]*v1.Pod, 0, len(pp)) for _, p := range pp { - o := p.(*unstructured.Unstructured) - + o, ok := p.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("expecting unstructured but got %T", p) + } var pod v1.Pod err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &pod) if err != nil { diff --git a/internal/model/pod.go b/internal/model/pod.go index a64b85fc..f74ada55 100644 --- a/internal/model/pod.go +++ b/internal/model/pod.go @@ -2,6 +2,7 @@ package model import ( "context" + "fmt" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" @@ -37,8 +38,14 @@ func (p *Pod) List(ctx context.Context) ([]runtime.Object, error) { var res []runtime.Object for _, o := range oo { - u := o.(*unstructured.Unstructured) - spec := u.Object["spec"].(map[string]interface{}) + u, ok := o.(*unstructured.Unstructured) + if !ok { + return res, fmt.Errorf("expecting unstructured but got `%T", o) + } + spec, ok := u.Object["spec"].(map[string]interface{}) + if !ok { + return res, fmt.Errorf("expecting interface map but got `%T", o) + } if nodeName == "" || spec["nodeName"] == nodeName { res = append(res, o) } diff --git a/internal/model/portforward.go b/internal/model/portforward.go index 86c8e5bb..ac2ea7fe 100644 --- a/internal/model/portforward.go +++ b/internal/model/portforward.go @@ -9,16 +9,12 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) // PortForward represents a portforward model. type PortForward struct { Resource - - pod *v1.Pod } // List returns a collection of screen dumps. @@ -51,7 +47,6 @@ func (c *PortForward) List(ctx context.Context) ([]runtime.Object, error) { // Hydrate returns a pod as container rows. func (c *PortForward) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { for i, o := range oo { - log.Debug().Msgf("PortFWD GOT %#v", o) res, ok := o.(render.ForwardRes) if !ok { return fmt.Errorf("expecting a forwardres but got %T", o) diff --git a/internal/model/rbac.go b/internal/model/rbac.go index 59e4741c..eff90697 100644 --- a/internal/model/rbac.go +++ b/internal/model/rbac.go @@ -76,16 +76,15 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { } var rb rbacv1.RoleBinding - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) - if err != nil { + if err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb); err != nil { return nil, err } if rb.RoleRef.Kind == "ClusterRole" { kind := "rbac.authorization.k8s.io/v1/clusterroles" - o, err := r.factory.Get(kind, client.FQN("-", rb.RoleRef.Name), labels.Everything()) - if err != nil { - return nil, err + o, e := r.factory.Get(kind, client.FQN("-", rb.RoleRef.Name), labels.Everything()) + if e != nil { + return nil, e } var cr rbacv1.ClusterRole err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) @@ -186,7 +185,11 @@ func upsert(rr []runtime.Object, p *render.PolicyRes) []runtime.Object { // Find locates a row by id. Retturns false is not found. func find(rr []runtime.Object, res string) (int, bool) { for i, r := range rr { - p := r.(*render.PolicyRes) + p, ok := r.(*render.PolicyRes) + if !ok { + log.Error().Err(fmt.Errorf("expecting policyres but got `%T", r)) + return 0, false + } if p.Resource == res { return i, true } diff --git a/internal/model/screen_dump.go b/internal/model/screen_dump.go index a61b3b0c..2135fb75 100644 --- a/internal/model/screen_dump.go +++ b/internal/model/screen_dump.go @@ -7,15 +7,12 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/render" - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" ) // ScreenDump represents a collections of screendumps. type ScreenDump struct { Resource - - pod *v1.Pod } // List returns a collection of screen dumps. diff --git a/internal/model/subject.go b/internal/model/subject.go index 2d162d7d..0b5620eb 100644 --- a/internal/model/subject.go +++ b/internal/model/subject.go @@ -7,7 +7,6 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/render" - rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -58,58 +57,59 @@ func (s *Subject) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) erro return nil } -func (s *Subject) fetchClusterRoleBindings() ([]runtime.Object, error) { - oo, err := s.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) - if err != nil { - return nil, err - } +// BOZO!! +// func (s *Subject) fetchClusterRoleBindings() ([]runtime.Object, error) { +// oo, err := s.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) +// if err != nil { +// return nil, err +// } - rows := make([]runtime.Object, 0, len(oo)) - for _, o := range oo { - var crb rbacv1.ClusterRoleBinding - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) - if err != nil { - return nil, err - } - for _, subject := range crb.Subjects { - if subject.Kind != s.subjectKind { - continue - } - rows = append(rows, SubjectRes{ - id: subject.Name, - fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name}, - }) - } - } +// rows := make([]runtime.Object, 0, len(oo)) +// for _, o := range oo { +// var crb rbacv1.ClusterRoleBinding +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) +// if err != nil { +// return nil, err +// } +// for _, subject := range crb.Subjects { +// if subject.Kind != s.subjectKind { +// continue +// } +// rows = append(rows, SubjectRes{ +// id: subject.Name, +// fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name}, +// }) +// } +// } - return rows, nil -} +// return rows, nil +// } -func (s *Subject) fetchRoleBindings() ([]runtime.Object, error) { - oo, err := s.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) - if err != nil { - return nil, err - } +// func (s *Subject) fetchRoleBindings() ([]runtime.Object, error) { +// oo, err := s.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) +// if err != nil { +// return nil, err +// } - rows := make([]runtime.Object, 0, len(oo)) - for _, o := range oo { - var rb rbacv1.RoleBinding - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) - if err != nil { - return nil, err - } - for _, subject := range rb.Subjects { - if subject.Kind == s.subjectKind { - rows = append(rows, SubjectRes{ - id: subject.Name, - fields: render.Fields{subject.Name, "RoleBinding", rb.Name}, - }) - } - } - } +// rows := make([]runtime.Object, 0, len(oo)) +// for _, o := range oo { +// var rb rbacv1.RoleBinding +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) +// if err != nil { +// return nil, err +// } +// for _, subject := range rb.Subjects { +// if subject.Kind == s.subjectKind { +// rows = append(rows, SubjectRes{ +// id: subject.Name, +// fields: render.Fields{subject.Name, "RoleBinding", rb.Name}, +// }) +// } +// } +// } - return rows, nil -} +// return rows, nil +// } // ---------------------------------------------------------------------------- diff --git a/internal/model/types.go b/internal/model/types.go index e2a950f8..ecc92068 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -67,7 +67,7 @@ type Lister interface { List(context.Context) ([]runtime.Object, error) // Hydrate converts resource rows into tabular data. - Hydrate([]runtime.Object, render.Rows, Renderer) error + Hydrate(oo []runtime.Object, rr render.Rows, r Renderer) error } type Factory interface { diff --git a/internal/render/colorer_test.go b/internal/render/colorer_test.go index 44cc92aa..62d93dee 100644 --- a/internal/render/colorer_test.go +++ b/internal/render/colorer_test.go @@ -10,31 +10,22 @@ package render // colorerUCs []colorerUC // ) -// func TestNSColorer(t *testing.T) { -// var ( -// ns = Row{Fields: Fields{"blee", "Active"}} -// term = Row{Fields: Fields{"blee", Terminating}} -// dead = Row{Fields: Fields{"blee", "Inactive"}} -// ) - -// uu := colorerUCs{ -// // Add AllNS -// {"", RowEvent{ -// Kind: EventAdd, -// Row: ns, -// }, -// AddColor}, -// // Mod AllNS -// {"", RowEvent{Kind: EventUpdate, Row: ns}, ModColor}, -// // MoChange AllNS -// {"", RowEvent{Kind: EventUnchanged, Row: ns}, StdColor}, -// // Bust NS -// {"", RowEvent{Kind: EventUnchanged, Row: term}, ErrColor}, -// // Bust NS -// {"", RowEvent{Kind: EventUnchanged, Row: dead}, ErrColor}, +// func TestDefaultColorer(t *testing.T) { +// uu := map[string]struct { +// re render.RowEvent +// e tcell.Color +// }{ +// "default": {render.RowEvent{}, ui.StdColor}, +// "add": {render.RowEvent{Kind: render.EventAdd}, ui.AddColor}, +// "delete": {render.RowEvent{Kind: render.EventDelete}, ui.KillColor}, +// "update": {render.RowEvent{Kind: render.EventUpdate}, ui.ModColor}, // } -// for _, u := range uu { -// assert.Equal(t, u.e, nsColorer(u.ns, u.r)) + +// for k := range uu { +// u := uu[k] +// t.Run(k, func(t *testing.T) { +// assert.Equal(t, u.e, ui.DefaultColorer("", u.re)) +// }) // } // } diff --git a/internal/render/container.go b/internal/render/container.go index 5100d065..0fdae814 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -131,7 +131,7 @@ func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p metric mem: ToMi(mem), } - rcpu, rmem := containerResources(co) + rcpu, rmem := containerResources(*co) if rcpu != nil { p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue()))) } @@ -177,19 +177,9 @@ func toState(s v1.ContainerState) string { } } -func toRes(r v1.ResourceList) (string, string) { - cpu, mem := r[v1.ResourceCPU], r[v1.ResourceMemory] - - return ToMillicore(cpu.MilliValue()), ToMi(ToMB(mem.Value())) -} - func probe(p *v1.Probe) string { if p == nil { return "off" } return "on" } - -func asMi(v int64) float64 { - return float64(v) / 1024 * 1024 -} diff --git a/internal/render/context_test.go b/internal/render/context_test.go index 6341fb66..b1ebbd96 100644 --- a/internal/render/context_test.go +++ b/internal/render/context_test.go @@ -38,27 +38,21 @@ func TestContextRender(t *testing.T) { } var r render.Context - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { row := render.NewRow(4) - err := r.Render(u.ctx, "", &row) + err := r.Render(uc.ctx, "", &row) assert.Nil(t, err) - assert.Equal(t, u.e, row) + assert.Equal(t, uc.e, row) }) } } +// ---------------------------------------------------------------------------- // Helpers... -func newContext(n string) *api.Context { - return &api.Context{ - Cluster: n, - AuthInfo: "blee", - Namespace: "zorg", - } -} - type config struct{} func (k config) CurrentContextName() (string, error) { diff --git a/internal/render/crd.go b/internal/render/crd.go index 30342d33..2218b25a 100644 --- a/internal/render/crd.go +++ b/internal/render/crd.go @@ -32,17 +32,36 @@ func (CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error { return fmt.Errorf("Expected CustomResourceDefinition, but got %T", o) } - meta := crd.Object["metadata"].(map[string]interface{}) - t, err := time.Parse(time.RFC3339, meta["creationTimestamp"].(string)) + meta, ok := crd.Object["metadata"].(map[string]interface{}) + if !ok { + return fmt.Errorf("expecting an interface map but got %T", crd.Object["metadata"]) + } + t, err := time.Parse(time.RFC3339, extractMetaField(meta, "creationTimestamp")) if err != nil { log.Error().Err(err).Msgf("Fields timestamp %v", err) } - r.ID = FQN(ClusterScope, meta["name"].(string)) + r.ID = FQN(ClusterScope, extractMetaField(meta, "name")) r.Fields = Fields{ - meta["name"].(string), - toAge(metav1.Time{t}), + extractMetaField(meta, "name"), + toAge(metav1.Time{Time: t}), } return nil } + +func extractMetaField(m map[string]interface{}, field string) string { + f, ok := m[field] + if !ok { + log.Error().Err(fmt.Errorf("failed to extract field from meta %s", field)) + return "n/a" + } + + fs, ok := f.(string) + if !ok { + log.Error().Err(fmt.Errorf("failed to extract string from field %s", field)) + return "n/a" + } + + return fs +} diff --git a/internal/render/delta_test.go b/internal/render/delta_test.go index 1fb3ca60..5b492b45 100644 --- a/internal/render/delta_test.go +++ b/internal/render/delta_test.go @@ -53,11 +53,12 @@ func TestDelta(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - d := render.NewDeltaRow(u.o, u.n, false) - assert.Equal(t, u.e, d) - assert.Equal(t, u.blank, d.IsBlank()) + d := render.NewDeltaRow(uc.o, uc.n, false) + assert.Equal(t, uc.e, d) + assert.Equal(t, uc.blank, d.IsBlank()) }) } } @@ -80,9 +81,10 @@ func TestDeltaBlank(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, u.r.IsBlank()) + assert.Equal(t, uc.e, uc.r.IsBlank()) }) } } diff --git a/internal/render/ep.go b/internal/render/ep.go index 330ae551..37f496b8 100644 --- a/internal/render/ep.go +++ b/internal/render/ep.go @@ -33,7 +33,7 @@ func (Endpoints) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (Endpoints) Render(o interface{}, ns string, r *Row) error { +func (e Endpoints) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected Endpoints, but got %T", o) @@ -44,16 +44,16 @@ func (Endpoints) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(ep.ObjectMeta) + r.Fields = make(Fields, 0, len(e.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, ep.Namespace) + r.Fields = append(r.Fields, ep.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, ep.Name, missing(toEPs(ep.Subsets)), toAge(ep.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(ep.ObjectMeta), fields return nil } diff --git a/internal/render/ev.go b/internal/render/ev.go index d35ee3d6..a03aa9cb 100644 --- a/internal/render/ev.go +++ b/internal/render/ev.go @@ -53,7 +53,7 @@ func (Event) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (Event) Render(o interface{}, ns string, r *Row) error { +func (e Event) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected Event, but got %T", o) @@ -64,18 +64,18 @@ func (Event) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(ev.ObjectMeta) + r.Fields = make(Fields, 0, len(e.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, ev.Namespace) + r.Fields = append(r.Fields, ev.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, ev.Name, ev.Reason, ev.Source.Component, strconv.Itoa(int(ev.Count)), Truncate(ev.Message, 80), toAge(ev.LastTimestamp)) - r.ID, r.Fields = MetaFQN(ev.ObjectMeta), fields return nil } diff --git a/internal/render/event_test.go b/internal/render/event_test.go index 56c65693..a0d4b31d 100644 --- a/internal/render/event_test.go +++ b/internal/render/event_test.go @@ -31,10 +31,11 @@ func TestSort(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - u.re.Sort("", u.col, u.asc) - assert.Equal(t, u.e, u.re) + uc.re.Sort("", uc.col, uc.asc) + assert.Equal(t, uc.e, uc.re) }) } } @@ -50,9 +51,10 @@ func TestDefaultColorer(t *testing.T) { "std": {100, render.StdColor}, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, render.DefaultColorer("", render.RowEvent{})) + assert.Equal(t, uc.e, render.DefaultColorer("", render.RowEvent{})) }) } } diff --git a/internal/render/helpers_test.go b/internal/render/helpers_test.go index 39549b65..510f4807 100644 --- a/internal/render/helpers_test.go +++ b/internal/render/helpers_test.go @@ -1,7 +1,6 @@ package render import ( - "fmt" "testing" "time" @@ -49,9 +48,10 @@ func TestToAge(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, toAge(metav1.Time{Time: u.t})[:2]) + assert.Equal(t, uc.e, toAge(metav1.Time{Time: uc.t})[:2]) }) } } @@ -67,10 +67,11 @@ func TestToAgeHuma(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - ti := toAge(metav1.Time{Time: u.t}) - assert.Equal(t, u.e, toAgeHuman(ti)[:2]) + ti := toAge(metav1.Time{Time: uc.t}) + assert.Equal(t, uc.e, toAgeHuman(ti)[:2]) }) } } @@ -86,9 +87,10 @@ func TestJoin(t *testing.T) { "sparse": {[]string{"a", "", "c"}, "a,c"}, } - for k, v := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, v.e, join(v.i, ",")) + assert.Equal(t, uc.e, join(uc.i, ",")) }) } } @@ -195,11 +197,12 @@ func TestToSelector(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - s := toSelector(u.m) + s := toSelector(uc.m) var match bool - for _, e := range u.e { + for _, e := range uc.e { if e == s { match = true } @@ -225,9 +228,10 @@ func TestBlank(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, blank(u.a)) + assert.Equal(t, uc.e, blank(uc.a)) }) } } @@ -252,9 +256,10 @@ func TestIn(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, in(u.a, u.v)) + assert.Equal(t, uc.e, in(uc.a, uc.v)) }) } } @@ -268,9 +273,10 @@ func TestMetaFQN(t *testing.T) { "nons": {metav1.ObjectMeta{Name: "blee"}, "blee"}, } - for k, v := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, v.e, MetaFQN(v.m)) + assert.Equal(t, uc.e, MetaFQN(uc.m)) }) } } @@ -284,9 +290,10 @@ func TestFQN(t *testing.T) { "nons": {n: "blee", e: "blee"}, } - for k, v := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, v.e, FQN(v.ns, v.n)) + assert.Equal(t, uc.e, FQN(uc.ns, uc.n)) }) } } @@ -374,10 +381,11 @@ func BenchmarkAsPerc(b *testing.B) { // Helpers... -func testTime() time.Time { - t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00") - if err != nil { - fmt.Println("TestTime Failed", err) - } - return t -} +// BOZO!! +// func testTime() time.Time { +// t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00") +// if err != nil { +// fmt.Println("TestTime Failed", err) +// } +// return t +// } diff --git a/internal/render/hpa.go b/internal/render/hpa.go index 810d329d..64ce40b0 100644 --- a/internal/render/hpa.go +++ b/internal/render/hpa.go @@ -37,7 +37,7 @@ func (HorizontalPodAutoscaler) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error { +func (h HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected HorizontalPodAutoscaler, but got %T", o) @@ -48,11 +48,12 @@ func (HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(hpa.ObjectMeta) + r.Fields = make(Fields, 0, len(h.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, hpa.Namespace) + r.Fields = append(r.Fields, hpa.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, hpa.ObjectMeta.Name, hpa.Spec.ScaleTargetRef.Name, toMetrics(hpa.Spec, hpa.Status), @@ -61,7 +62,6 @@ func (HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error { strconv.Itoa(int(hpa.Status.CurrentReplicas)), toAge(hpa.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(hpa.ObjectMeta), fields return nil } diff --git a/internal/render/node.go b/internal/render/node.go index f6294b8a..a28edba2 100644 --- a/internal/render/node.go +++ b/internal/render/node.go @@ -9,7 +9,6 @@ import ( v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/util/sets" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) @@ -77,8 +76,9 @@ func (n Node) Render(o interface{}, ns string, r *Row) error { ro := make([]string, 10) nodeRoles(&no, ro) - fields := make(Fields, 0, len(r.Fields)) - fields = append(fields, + r.ID = MetaFQN(no.ObjectMeta) + r.Fields = make(Fields, 0, len(n.Header(ns))) + r.Fields = append(r.Fields, no.Name, join(sta, ","), join(ro, ","), @@ -94,11 +94,8 @@ func (n Node) Render(o interface{}, ns string, r *Row) error { a.mem, toAge(no.ObjectMeta.CreationTimestamp), ) - r.ID = MetaFQN(no.ObjectMeta) - r.Fields = fields return nil - } // ---------------------------------------------------------------------------- @@ -132,10 +129,6 @@ func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c metric, a metric, p return } -func withPerc(v, p string) string { - return v + " (" + p + ")" -} - func nodeRoles(node *v1.Node, res []string) { index := 0 for k, v := range node.Labels { @@ -200,87 +193,6 @@ func status(status v1.NodeStatus, exempt bool, res []string) { } } -func findNodeRoles(no *v1.Node) []string { - roles := sets.NewString() - for k, v := range no.Labels { - switch { - case strings.HasPrefix(k, labelNodeRolePrefix): - if role := strings.TrimPrefix(k, labelNodeRolePrefix); len(role) > 0 { - roles.Insert(role) - } - case k == nodeLabelRole && v != "": - roles.Insert(v) - } - } - - return roles.List() -} - -func podsResources(name string, pods []*v1.Pod) (v1.ResourceList, v1.ResourceList, error) { - reqs, limits := v1.ResourceList{}, v1.ResourceList{} - for _, p := range pods { - preq, plim := podResources(p) - for k, v := range preq { - if value, ok := reqs[k]; !ok { - reqs[k] = v.DeepCopy() - } else { - value.Add(v) - reqs[k] = value - } - } - for k, v := range plim { - if value, ok := limits[k]; !ok { - limits[k] = v.DeepCopy() - } else { - value.Add(v) - limits[k] = value - } - } - } - - return reqs, limits, nil -} - -func podResources(pod *v1.Pod) (v1.ResourceList, v1.ResourceList) { - reqs, limits := v1.ResourceList{}, v1.ResourceList{} - for _, container := range pod.Spec.Containers { - addResources(reqs, container.Resources.Requests) - addResources(limits, container.Resources.Limits) - } - // init containers define the minimum of any resource - for _, container := range pod.Spec.InitContainers { - maxResources(reqs, container.Resources.Requests) - maxResources(limits, container.Resources.Limits) - } - - return reqs, limits -} - -// AddResources adds the resources from l2 to l1. -func addResources(l1, l2 v1.ResourceList) { - for name, quantity := range l2 { - if value, ok := l1[name]; ok { - value.Add(quantity) - l1[name] = value - } else { - l1[name] = quantity.DeepCopy() - } - } -} - -// MaxResourceList sets list to the greater of l1/l2 for every resource. -func maxResources(l1, l2 v1.ResourceList) { - for name, quantity := range l2 { - if value, ok := l1[name]; ok { - if quantity.Cmp(value) > 0 { - l1[name] = quantity.DeepCopy() - } - } else { - l1[name] = quantity.DeepCopy() - } - } -} - func empty(s []string) bool { for _, v := range s { if len(v) != 0 { diff --git a/internal/render/np.go b/internal/render/np.go index 820d5d80..f3a45a08 100644 --- a/internal/render/np.go +++ b/internal/render/np.go @@ -38,7 +38,7 @@ func (NetworkPolicy) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (NetworkPolicy) Render(o interface{}, ns string, r *Row) error { +func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected NetworkPolicy, but got %T", o) @@ -52,11 +52,12 @@ func (NetworkPolicy) Render(o interface{}, ns string, r *Row) error { ip, is, ib := ingress(np.Spec.Ingress) ep, es, eb := egress(np.Spec.Egress) - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(np.ObjectMeta) + r.Fields = make(Fields, 0, len(n.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, np.Namespace) + r.Fields = append(r.Fields, np.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, np.Name, is, ip, @@ -66,7 +67,6 @@ func (NetworkPolicy) Render(o interface{}, ns string, r *Row) error { eb, toAge(np.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(np.ObjectMeta), fields return nil } diff --git a/internal/render/ns_test.go b/internal/render/ns_test.go index 445e05b7..89736e22 100644 --- a/internal/render/ns_test.go +++ b/internal/render/ns_test.go @@ -7,6 +7,33 @@ import ( "github.com/stretchr/testify/assert" ) +func TestNSColorer(t *testing.T) { + var ( + ns = render.Row{Fields: render.Fields{"blee", "Active"}} + term = render.Row{Fields: render.Fields{"blee", render.Terminating}} + dead = render.Row{Fields: render.Fields{"blee", "Inactive"}} + ) + + uu := colorerUCs{ + // Add AllNS + {"", render.RowEvent{Kind: render.EventAdd, Row: ns}, render.AddColor}, + // Mod AllNS + {"", render.RowEvent{Kind: render.EventUpdate, Row: ns}, render.ModColor}, + // MoChange AllNS + {"", render.RowEvent{Kind: render.EventUnchanged, Row: ns}, render.StdColor}, + // Bust NS + {"", render.RowEvent{Kind: render.EventUnchanged, Row: term}, render.ErrColor}, + // Bust NS + {"", render.RowEvent{Kind: render.EventUnchanged, Row: dead}, render.ErrColor}, + } + + var n render.Namespace + f := n.ColorerFunc() + for _, u := range uu { + assert.Equal(t, u.e, f(u.ns, u.r)) + } +} + func TestNamespaceRender(t *testing.T) { c := render.Namespace{} r := render.NewRow(3) diff --git a/internal/render/pdb.go b/internal/render/pdb.go index 690a19d8..167492a7 100644 --- a/internal/render/pdb.go +++ b/internal/render/pdb.go @@ -57,7 +57,7 @@ func (PodDisruptionBudget) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { +func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected PodDisruptionBudget, but got %T", o) @@ -68,11 +68,12 @@ func (PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(pdb.ObjectMeta) + r.Fields = make(Fields, 0, len(p.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, pdb.Namespace) + r.Fields = append(r.Fields, pdb.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, pdb.Name, numbToStr(pdb.Spec.MinAvailable), numbToStr(pdb.Spec.MaxUnavailable), @@ -82,7 +83,6 @@ func (PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { strconv.Itoa(int(pdb.Status.ExpectedPods)), toAge(pdb.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(pdb.ObjectMeta), fields return nil } diff --git a/internal/render/pod.go b/internal/render/pod.go index 21eb5e6b..42a0a48d 100644 --- a/internal/render/pod.go +++ b/internal/render/pod.go @@ -5,7 +5,6 @@ import ( "strconv" "strings" - "github.com/derailed/k9s/internal/color" "github.com/derailed/tview" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" @@ -158,7 +157,7 @@ func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, p metric) { return } -func containerResources(co *v1.Container) (cpu, mem *resource.Quantity) { +func containerResources(co v1.Container) (cpu, mem *resource.Quantity) { req, limit := co.Resources.Requests, co.Resources.Limits switch { case len(req) != 0: @@ -171,7 +170,7 @@ func containerResources(co *v1.Container) (cpu, mem *resource.Quantity) { func requestedRes(po *v1.Pod) (cpu, mem resource.Quantity) { for _, co := range po.Spec.Containers { - c, m := containerResources(&co) + c, m := containerResources(co) if c != nil { cpu.Add(*c) } @@ -225,13 +224,13 @@ func (p *Pod) phase(po *v1.Pod) string { status = po.Status.Reason } - init, status := p.initContainerPhase(po.Status, len(po.Spec.InitContainers), status) - if init { + status, ok := p.initContainerPhase(po.Status, len(po.Spec.InitContainers), status) + if ok { return status } - running, status := p.containerPhase(po.Status, status) - if running && status == "Completed" { + status, ok = p.containerPhase(po.Status, status) + if ok && status == "Completed" { status = "Running" } if po.DeletionTimestamp == nil { @@ -241,7 +240,7 @@ func (p *Pod) phase(po *v1.Pod) string { return "Terminated" } -func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) { +func (*Pod) containerPhase(st v1.PodStatus, status string) (string, bool) { var running bool for i := len(st.ContainerStatuses) - 1; i >= 0; i-- { cs := st.ContainerStatuses[i] @@ -261,29 +260,22 @@ func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) { } } - return running, status + return status, running } -func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (bool, string) { +func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (string, bool) { for i, cs := range st.InitContainerStatuses { - status := checkContainerStatus(cs, i, initCount) - if status == "" { + s := checkContainerStatus(cs, i, initCount) + if s == "" { continue } - return true, status + return s, true } - return false, status -} - -func (*Pod) loggableContainers(s v1.PodStatus) []string { - var rcos []string - for _, c := range s.ContainerStatuses { - rcos = append(rcos, c.Name) - } - return rcos + return status, false } +// ---------------------------------------------------------------------------- // Helpers.. func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string { @@ -305,15 +297,3 @@ func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string { return "Init:" + strconv.Itoa(i) + "/" + strconv.Itoa(initCount) } } - -func asColor(n string) color.Paint { - var sum int - for _, r := range n { - sum += int(r) - } - return color.Paint(30 + 2 + sum%6) -} - -func isSet(s *string) bool { - return s != nil && *s != "" -} diff --git a/internal/render/pod_test.go b/internal/render/pod_test.go index 15f99065..a735e6fd 100644 --- a/internal/render/pod_test.go +++ b/internal/render/pod_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/derailed/k9s/internal/render" + "github.com/gdamore/tcell" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" res "k8s.io/apimachinery/pkg/api/resource" @@ -13,6 +14,52 @@ import ( mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) +type ( + colorerUC struct { + ns string + r render.RowEvent + e tcell.Color + } + + colorerUCs []colorerUC +) + +func TestPodColorer(t *testing.T) { + var ( + nsRow = render.Row{Fields: render.Fields{"blee", "fred", "1/1", "Running"}} + toastNS = render.Row{Fields: render.Fields{"blee", "fred", "1/1", "Boom"}} + notReadyNS = render.Row{Fields: render.Fields{"blee", "fred", "0/1", "Boom"}} + row = render.Row{Fields: render.Fields{"fred", "1/1", "Running"}} + toast = render.Row{Fields: render.Fields{"fred", "1/1", "Boom"}} + notReady = render.Row{Fields: render.Fields{"fred", "0/1", "Boom"}} + ) + + uu := colorerUCs{ + // Add allNS + {"", render.RowEvent{Kind: render.EventAdd, Row: nsRow}, render.AddColor}, + // Add Namespaced + {"blee", render.RowEvent{Kind: render.EventAdd, Row: row}, render.AddColor}, + // Mod AllNS + {"", render.RowEvent{Kind: render.EventUpdate, Row: nsRow}, render.ModColor}, + // Mod Namespaced + {"blee", render.RowEvent{Kind: render.EventUpdate, Row: row}, render.ModColor}, + // Mod Busted AllNS + {"", render.RowEvent{Kind: render.EventUpdate, Row: toastNS}, render.ErrColor}, + // Mod Busted Namespaced + {"blee", render.RowEvent{Kind: render.EventUpdate, Row: toast}, render.ErrColor}, + // NotReady AllNS + {"", render.RowEvent{Kind: render.EventUpdate, Row: notReadyNS}, render.ErrColor}, + // NotReady Namespaced + {"blee", render.RowEvent{Kind: render.EventUpdate, Row: notReady}, render.ErrColor}, + } + + var p render.Pod + f := p.ColorerFunc() + for _, u := range uu { + assert.Equal(t, u.e, f(u.ns, u.r)) + } +} + func TestPodRender(t *testing.T) { pom := podMetrics{load(t, "po"), makePodMX("nginx", "10m", "10Mi")} diff --git a/internal/render/policy.go b/internal/render/policy.go index 16728634..b90e8b9f 100644 --- a/internal/render/policy.go +++ b/internal/render/policy.go @@ -42,8 +42,5 @@ func (Policy) Header(ns string) HeaderRow { // Render renders a K8s resource to screen. func (Policy) Render(o interface{}, gvr string, r *Row) error { - panic("NYI") return nil } - -// Helpers... diff --git a/internal/render/pv.go b/internal/render/pv.go index 6181f7d8..c142abb0 100644 --- a/internal/render/pv.go +++ b/internal/render/pv.go @@ -53,7 +53,7 @@ func (PersistentVolume) Header(string) HeaderRow { } // Render renders a K8s resource to screen. -func (PersistentVolume) Render(o interface{}, ns string, r *Row) error { +func (p PersistentVolume) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected PersistentVolume, but got %T", o) @@ -79,8 +79,8 @@ func (PersistentVolume) Render(o interface{}, ns string, r *Row) error { size := pv.Spec.Capacity[v1.ResourceStorage] - fields := make(Fields, 0, len(r.Fields)) - fields = append(fields, + r.ID = MetaFQN(pv.ObjectMeta) + r.Fields = Fields{ pv.Name, size.String(), accessMode(pv.Spec.AccessModes), @@ -90,8 +90,7 @@ func (PersistentVolume) Render(o interface{}, ns string, r *Row) error { class, pv.Status.Reason, toAge(pv.ObjectMeta.CreationTimestamp), - ) - r.ID, r.Fields = MetaFQN(pv.ObjectMeta), fields + } return nil } diff --git a/internal/render/pvc.go b/internal/render/pvc.go index dc37cc6b..6cb71af6 100644 --- a/internal/render/pvc.go +++ b/internal/render/pvc.go @@ -54,7 +54,7 @@ func (PersistentVolumeClaim) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { +func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected PersistentVolumeClaim, but got %T", o) @@ -84,11 +84,12 @@ func (PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { } } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(pvc.ObjectMeta) + r.Fields = make(Fields, 0, len(p.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, pvc.Namespace) + r.Fields = append(r.Fields, pvc.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, pvc.Name, string(phase), pvc.Spec.VolumeName, @@ -97,7 +98,6 @@ func (PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { class, toAge(pvc.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(pvc.ObjectMeta), fields return nil } diff --git a/internal/render/row.go b/internal/render/row.go index b6146c7a..d15e6b95 100644 --- a/internal/render/row.go +++ b/internal/render/row.go @@ -31,6 +31,16 @@ type Header struct { // HeaderRow represents a table header. type HeaderRow []Header +// Columns return header row columns as strings. +func (h HeaderRow) Columns() []string { + cc := make([]string, len(h)) + for i, c := range h { + cc[i] = c.Name + } + + return cc +} + // HasAge returns true if table has an age column. func (h HeaderRow) HasAge() bool { for _, r := range h { diff --git a/internal/render/row_test.go b/internal/render/row_test.go index 4b8e4110..f7149791 100644 --- a/internal/render/row_test.go +++ b/internal/render/row_test.go @@ -58,10 +58,11 @@ func TestRowDelete(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - rows := u.rows.Delete(u.id) - assert.Equal(t, u.e, rows) + rows := uc.rows.Delete(uc.id) + assert.Equal(t, uc.e, rows) }) } } @@ -135,10 +136,11 @@ func TestSortText(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - u.rows.Sort(u.col, u.asc) - assert.Equal(t, u.e, u.rows) + uc.rows.Sort(uc.col, uc.asc) + assert.Equal(t, uc.e, uc.rows) }) } } @@ -175,10 +177,11 @@ func TestSortDuration(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - u.rows.Sort(u.col, u.asc) - assert.Equal(t, u.e, u.rows) + uc.rows.Sort(uc.col, uc.asc) + assert.Equal(t, uc.e, uc.rows) }) } } @@ -216,10 +219,11 @@ func TestSortMetrics(t *testing.T) { }, } - for k, u := range uu { + for k := range uu { + uc := uu[k] t.Run(k, func(t *testing.T) { - u.rows.Sort(u.col, u.asc) - assert.Equal(t, u.e, u.rows) + uc.rows.Sort(uc.col, uc.asc) + assert.Equal(t, uc.e, uc.rows) }) } } diff --git a/internal/render/rs.go b/internal/render/rs.go index 571ec738..880d6b12 100644 --- a/internal/render/rs.go +++ b/internal/render/rs.go @@ -53,7 +53,7 @@ func (ReplicaSet) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (ReplicaSet) Render(o interface{}, ns string, r *Row) error { +func (s ReplicaSet) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected ReplicaSet, but got %T", o) @@ -64,18 +64,18 @@ func (ReplicaSet) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(rs.ObjectMeta) + r.Fields = make(Fields, 0, len(s.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, rs.Namespace) + r.Fields = append(r.Fields, rs.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, rs.Name, strconv.Itoa(int(*rs.Spec.Replicas)), strconv.Itoa(int(rs.Status.Replicas)), strconv.Itoa(int(rs.Status.ReadyReplicas)), toAge(rs.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(rs.ObjectMeta), fields return nil } diff --git a/internal/render/sa.go b/internal/render/sa.go index 36cb5dc0..760a77a9 100644 --- a/internal/render/sa.go +++ b/internal/render/sa.go @@ -32,28 +32,27 @@ func (ServiceAccount) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (ServiceAccount) Render(o interface{}, ns string, r *Row) error { +func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected ServiceAccount, but got %T", o) } - var s v1.ServiceAccount - err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &s) + var sa v1.ServiceAccount + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sa) if err != nil { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(sa.ObjectMeta) + r.Fields = make(Fields, 0, len(s.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, s.Namespace) + r.Fields = append(r.Fields, sa.Namespace) } - fields = append(fields, - s.Name, - strconv.Itoa(len(s.Secrets)), - toAge(s.ObjectMeta.CreationTimestamp), + r.Fields = append(r.Fields, + sa.Name, + strconv.Itoa(len(sa.Secrets)), + toAge(sa.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(s.ObjectMeta), fields - return nil } diff --git a/internal/render/secret.go b/internal/render/secret.go index e56796c9..0280d0ba 100644 --- a/internal/render/secret.go +++ b/internal/render/secret.go @@ -34,29 +34,28 @@ func (Secret) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (Secret) Render(o interface{}, ns string, r *Row) error { +func (s Secret) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected Secret, but got %T", o) } - var s v1.Secret - err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &s) + var sec v1.Secret + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sec) if err != nil { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(sec.ObjectMeta) + r.Fields = make(Fields, 0, len(s.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, s.Namespace) + r.Fields = append(r.Fields, sec.Namespace) } - fields = append(fields, - s.Name, - string(s.Type), - strconv.Itoa(len(s.Data)), - toAge(s.ObjectMeta.CreationTimestamp), + r.Fields = append(r.Fields, + sec.Name, + string(sec.Type), + strconv.Itoa(len(sec.Data)), + toAge(sec.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(s.ObjectMeta), fields - return nil } diff --git a/internal/render/subject.go b/internal/render/subject.go index 9c5d701e..a11e4dc2 100644 --- a/internal/render/subject.go +++ b/internal/render/subject.go @@ -25,6 +25,5 @@ func (Subject) Header(ns string) HeaderRow { // Render renders a K8s resource to screen. func (Subject) Render(o interface{}, gvr string, r *Row) error { - panic("NYI") return nil } diff --git a/internal/render/svc.go b/internal/render/svc.go index 26d232c9..e41935a5 100644 --- a/internal/render/svc.go +++ b/internal/render/svc.go @@ -38,7 +38,7 @@ func (Service) Header(ns string) HeaderRow { } // Render renders a K8s resource to screen. -func (Service) Render(o interface{}, ns string, r *Row) error { +func (s Service) Render(o interface{}, ns string, r *Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("Expected Service, but got %T", o) @@ -49,11 +49,12 @@ func (Service) Render(o interface{}, ns string, r *Row) error { return err } - fields := make(Fields, 0, len(r.Fields)) + r.ID = MetaFQN(svc.ObjectMeta) + r.Fields = make(Fields, 0, len(s.Header(ns))) if isAllNamespace(ns) { - fields = append(fields, svc.Namespace) + r.Fields = append(r.Fields, svc.Namespace) } - fields = append(fields, + r.Fields = append(r.Fields, svc.ObjectMeta.Name, string(svc.Spec.Type), svc.Spec.ClusterIP, @@ -63,8 +64,6 @@ func (Service) Render(o interface{}, ns string, r *Row) error { toAge(svc.ObjectMeta.CreationTimestamp), ) - r.ID, r.Fields = MetaFQN(svc.ObjectMeta), fields - return nil } diff --git a/internal/ui/colorer_test.go b/internal/ui/colorer_test.go deleted file mode 100644 index 095582ff..00000000 --- a/internal/ui/colorer_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package ui_test - -// BOZO!! -// import ( -// "testing" - -// "github.com/derailed/k9s/internal/render" -// "github.com/derailed/k9s/internal/ui" -// "github.com/gdamore/tcell" -// "github.com/stretchr/testify/assert" -// ) - -// func TestDefaultColorer(t *testing.T) { -// uu := map[string]struct { -// re render.RowEvent -// e tcell.Color -// }{ -// "default": {render.RowEvent{}, ui.StdColor}, -// "add": {render.RowEvent{Kind: render.EventAdd}, ui.AddColor}, -// "delete": {render.RowEvent{Kind: render.EventDelete}, ui.KillColor}, -// "update": {render.RowEvent{Kind: render.EventUpdate}, ui.ModColor}, -// } - -// for k := range uu { -// u := uu[k] -// t.Run(k, func(t *testing.T) { -// assert.Equal(t, u.e, ui.DefaultColorer("", u.re)) -// }) -// } -// } diff --git a/internal/ui/sorter_test.go b/internal/ui/sorter_test.go deleted file mode 100644 index 9218576f..00000000 --- a/internal/ui/sorter_test.go +++ /dev/null @@ -1,118 +0,0 @@ -package ui - -// BOZO!! -// func TestGroupSort(t *testing.T) { -// uu := []struct { -// asc bool -// rows []string -// expect []string -// }{ -// {true, []string{"200m", "100m"}, []string{"100m", "200m"}}, -// {true, []string{"200m", "100m"}, []string{"100m", "200m"}}, -// {false, []string{"200m", "100m"}, []string{"200m", "100m"}}, -// {true, []string{"10", "1"}, []string{"1", "10"}}, -// {false, []string{"10", "1"}, []string{"10", "1"}}, -// {true, []string{"100Mi", "10Mi"}, []string{"10Mi", "100Mi"}}, -// {false, []string{"100Mi", "10Mi"}, []string{"100Mi", "10Mi"}}, -// {true, []string{"xyz", "abc"}, []string{"abc", "xyz"}}, -// {false, []string{"xyz", "abc"}, []string{"xyz", "abc"}}, -// {true, []string{"2m30s", "1m10s"}, []string{"1m10s", "2m30s"}}, -// {true, []string{"3d", "1d"}, []string{"1d", "3d"}}, - -// {true, []string{"95h", "93h"}, []string{"93h", "95h"}}, -// {true, []string{"95d", "93d"}, []string{"93d", "95d"}}, -// {true, []string{"1h10m", "59m"}, []string{"59m", "1h10m"}}, -// {true, []string{"95m", "1h30m"}, []string{"1h30m", "95m"}}, -// {true, []string{"b-21", "b-2"}, []string{"b-2", "b-21"}}, -// {false, []string{"b-21", "b-2"}, []string{"b-21", "b-2"}}, -// {true, []string{"4m", "3m2s"}, []string{"3m2s", "4m"}}, -// {true, []string{"3y37d", "2y4d"}, []string{"2y4d", "3y37d"}}, -// } - -// for _, u := range uu { -// g := GroupSorter{rows: u.rows, asc: u.asc} -// sort.Sort(g) -// assert.Equal(t, u.expect, g.rows) -// } -// } - -// func TestRowSort(t *testing.T) { -// uu := []struct { -// asc bool -// rows, expect resource.Rows -// }{ -// { -// true, -// resource.Rows{resource.Row{"200m"}, resource.Row{"100m"}}, -// resource.Rows{resource.Row{"100m"}, resource.Row{"200m"}}, -// }, -// { -// false, -// resource.Rows{resource.Row{"200m"}, resource.Row{"100m"}}, -// resource.Rows{resource.Row{"200m"}, resource.Row{"100m"}}, -// }, -// { -// true, -// resource.Rows{resource.Row{"200Mi"}, resource.Row{"100Mi"}}, -// resource.Rows{resource.Row{"100Mi"}, resource.Row{"200Mi"}}, -// }, -// { -// false, -// resource.Rows{resource.Row{"200Mi"}, resource.Row{"100Mi"}}, -// resource.Rows{resource.Row{"200Mi"}, resource.Row{"100Mi"}}, -// }, -// { -// true, -// resource.Rows{resource.Row{"8m4s"}, resource.Row{"31m"}}, -// resource.Rows{resource.Row{"8m4s"}, resource.Row{"31m"}}, -// }, -// { -// true, -// resource.Rows{resource.Row{"n/a"}, resource.Row{"31m"}}, -// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}}, -// }, -// { -// true, -// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}}, -// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}}, -// }, -// { -// false, -// resource.Rows{resource.Row{"n/a"}, resource.Row{"31m"}}, -// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}}, -// }, -// { -// false, -// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}}, -// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}}, -// }, -// } - -// for _, u := range uu { -// r := RowSorter{index: 0, rows: u.rows, asc: u.asc} -// sort.Sort(r) -// assert.Equal(t, u.expect, r.rows) -// } -// } - -// func TestIsDurationSort(t *testing.T) { -// uu := map[string]struct { -// s1, s2 string -// asc, e bool -// }{ -// "ascLess": {"10h5m", "2h10m", true, false}, -// "descGreater": {"10h5m", "2h10m", false, true}, -// "ascEqual": {"2h10m", "2h10m", true, true}, -// "descEqual": {"2h10m", "2h10m", false, true}, -// "ascGreater": {"10h10m", "2h5m", true, false}, -// } - -// for k := range uu { -// u := uu[k] -// t.Run(k, func(t *testing.T) { -// less, ok := isDurationSort(u.asc, u.s1, u.s2) -// assert.True(t, ok) -// assert.Equal(t, u.e, less) -// }) -// } -// } diff --git a/internal/ui/table.go b/internal/ui/table.go index 38b4cf7e..0da8f270 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -33,7 +33,6 @@ type Table struct { cmdBuff *CmdBuff styles *config.Styles sortCol SortColumn - sortFn SortFn colorerFn render.ColorerFunc decorateFn DecorateFunc } @@ -158,7 +157,6 @@ func (t *Table) doUpdate(data render.TableData) { t.adjustSorter(data) - var row int fg := config.AsColor(t.styles.GetTable().Header.FgColor) bg := config.AsColor(t.styles.GetTable().Header.BgColor) for col, h := range data.Header { @@ -167,8 +165,6 @@ func (t *Table) doUpdate(data render.TableData) { c.SetBackgroundColor(bg) c.SetTextColor(fg) } - row++ - data.RowEvents.Sort(data.Namespace, t.sortCol.index, t.sortCol.asc) pads := make(MaxyPad, len(data.Header)) diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index 17bb9222..167fb9a4 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -29,9 +29,6 @@ const ( ) var ( - cpuRX = regexp.MustCompile(`\A.{0,1}CPU`) - memRX = regexp.MustCompile(`\A.{0,1}MEM`) - // LabelCmd identifies a label query LabelCmd = regexp.MustCompile(`\A\-l`) @@ -88,46 +85,6 @@ func SkinTitle(fmat string, style config.Frame) string { return fmat } -// BOZO!! -// func sortRows(evts resource.RowEvents, sortFn SortFn, sortCol SortColumn, keys []string) { -// rows := make(resource.Rows, 0, len(evts)) -// for k, r := range evts { -// rows = append(rows, append(r.Fields, k)) -// } -// sortFn(rows, sortCol) - -// for i, r := range rows { -// keys[i] = r[len(r)-1] -// } -// } - -// func defaultSort(rows resource.Rows, sortCol SortColumn) { -// t := RowSorter{rows: rows, index: sortCol.index, asc: sortCol.asc} -// sort.Sort(t) -// } - -// BOZO!! -// func sortAllRows(col SortColumn, rows resource.RowEvents, sortFn SortFn) (resource.Row, map[string]resource.Row) { -// keys := make([]string, len(rows)) -// sortRows(rows, sortFn, col, keys) - -// sec := make(map[string]resource.Row, len(rows)) -// for _, k := range keys { -// grp := rows[k].Fields[col.index] -// sec[grp] = append(sec[grp], k) -// } - -// // Performs secondary to sort by name for each groups. -// prim := make(resource.Row, 0, len(sec)) -// for k, v := range sec { -// sort.Strings(v) -// prim = append(prim, k) -// } -// sort.Sort(GroupSorter{prim, col.asc}) - -// return prim, sec -// } - func sortIndicator(col SortColumn, style config.Table, index int, name string) string { if col.index != index { return name @@ -170,10 +127,9 @@ func rxFilter(q string, data render.TableData) (render.TableData, error) { } func fuzzyFilter(q string, index int, data render.TableData) render.TableData { - var ss, kk []string + var ss []string for _, re := range data.RowEvents { ss = append(ss, re.Row.Fields[index]) - kk = append(kk, re.Row.ID) } filtered := render.TableData{ diff --git a/internal/ui/table_helper_test.go b/internal/ui/table_helper_test.go index f8b7f9bd..8f1c18d9 100644 --- a/internal/ui/table_helper_test.go +++ b/internal/ui/table_helper_test.go @@ -40,76 +40,3 @@ func TestTrimLabelSelector(t *testing.T) { }) } } - -// BOZO!! -// func TestTVSortRows(t *testing.T) { -// uu := []struct { -// rows resource.RowEvents -// col int -// asc bool -// first resource.Row -// e []string -// }{ -// { -// resource.RowEvents{ -// "row1": {Fields: resource.Row{"x", "y"}}, -// "row2": {Fields: resource.Row{"a", "b"}}, -// }, -// 0, -// true, -// resource.Row{"a", "b"}, -// []string{"row2", "row1"}, -// }, -// { -// resource.RowEvents{ -// "row1": {Fields: resource.Row{"x", "y"}}, -// "row2": {Fields: resource.Row{"a", "b"}}, -// }, -// 1, -// true, -// resource.Row{"a", "b"}, -// []string{"row2", "row1"}, -// }, -// { -// resource.RowEvents{ -// "row1": {Fields: resource.Row{"x", "y"}}, -// "row2": {Fields: resource.Row{"a", "b"}}, -// }, -// 1, -// false, -// resource.Row{"x", "y"}, -// []string{"row1", "row2"}, -// }, -// { -// resource.RowEvents{ -// "row1": {Fields: resource.Row{"2175h48m0.06015s", "y"}}, -// "row2": {Fields: resource.Row{"403h42m34.060166s", "b"}}, -// }, -// 0, -// true, -// resource.Row{"403h42m34.060166s", "b"}, -// []string{"row2", "row1"}, -// }, -// } - -// for _, u := range uu { -// keys := make([]string, len(u.rows)) -// sortRows(u.rows, defaultSort, SortColumn{index: u.col, colCount: len(u.rows), asc: u.asc}, keys) -// assert.Equal(t, u.e, keys) -// assert.Equal(t, u.first, u.rows[u.e[0]].Fields) -// } -// } - -// func BenchmarkTableSortRows(b *testing.B) { -// evts := resource.RowEvents{ -// "row1": {Fields: resource.Row{"x", "y"}}, -// "row2": {Fields: resource.Row{"a", "b"}}, -// } -// sc := SortColumn{index: 0, colCount: 2, asc: true} -// keys := make([]string, len(evts)) -// b.ResetTimer() -// b.ReportAllocs() -// for i := 0; i < b.N; i++ { -// sortRows(evts, defaultSort, sc, keys) -// } -// } diff --git a/internal/ui/sorter.go b/internal/ui/types.go similarity index 82% rename from internal/ui/sorter.go rename to internal/ui/types.go index dfc0dfbe..1026cb67 100644 --- a/internal/ui/sorter.go +++ b/internal/ui/types.go @@ -1,8 +1,6 @@ package ui -import ( - "github.com/derailed/k9s/internal/render" -) +import "github.com/derailed/k9s/internal/render" type ( // SortFn represent a function that can sort columnar data. diff --git a/internal/view/alias.go b/internal/view/alias.go index 906c1ca3..1f6be173 100644 --- a/internal/view/alias.go +++ b/internal/view/alias.go @@ -12,10 +12,7 @@ import ( "github.com/rs/zerolog/log" ) -const ( - aliasTitle = "Aliases" - aliasTitleFmt = " [mediumseagreen::b]%s([fuchsia::b]%d[fuchsia::-][mediumseagreen::-]) " -) +const aliasTitle = "Aliases" // Alias represents a command alias view. type Alias struct { @@ -32,7 +29,6 @@ func NewAlias(gvr client.GVR) ResourceViewer { a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone) a.SetBindKeysFn(a.bindKeys) a.SetContextFn(a.aliasContext) - // a.GetTable().SetEnterFn(a.gotoCmd) return &a } @@ -45,12 +41,9 @@ func (a *Alias) bindKeys(aa ui.KeyActions) { aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) aa.Add(ui.KeyActions{ tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, true), - // BOZO!! - // tcell.KeyEscape: ui.NewKeyAction("Reset", a.resetCmd, false), - // ui.KeySlash: ui.NewKeyAction("Filter", a.GetTable().activateCmd, false), - ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.GetTable().SortColCmd(0, true), false), - ui.KeyShiftC: ui.NewKeyAction("Sort Command", a.GetTable().SortColCmd(1, true), false), - ui.KeyShiftA: ui.NewKeyAction("Sort ApiGroup", a.GetTable().SortColCmd(2, true), false), + ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.GetTable().SortColCmd(0, true), false), + ui.KeyShiftC: ui.NewKeyAction("Sort Command", a.GetTable().SortColCmd(1, true), false), + ui.KeyShiftA: ui.NewKeyAction("Sort ApiGroup", a.GetTable().SortColCmd(2, true), false), }) } @@ -60,7 +53,9 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { if r != 0 { s := ui.TrimCell(a.GetTable().SelectTable, r, 1) tokens := strings.Split(s, ",") - a.App().gotoResource(tokens[0]) + if err := a.App().gotoResource(tokens[0]); err != nil { + a.App().Flash().Err(err) + } return nil } @@ -69,71 +64,3 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey { } return evt } - -func (a *Alias) resetCmd(evt *tcell.EventKey) *tcell.EventKey { - if !a.GetTable().SearchBuff().Empty() { - a.GetTable().SearchBuff().Reset() - return nil - } - - return a.App().PrevCmd(evt) -} - -func (a *Alias) gotoCmd1(app *App, ns, res, path string) { - log.Debug().Msgf("GOTO %q -- %q -- %q", ns, res, path) - app.gotoResource(client.GVR(path).ToR()) - // r, _ := a.GetTable().GetSelection() - // if r != 0 { - // s := ui.TrimCell(a.GetTable().SelectTable, r, 1) - // tokens := strings.Split(s, ",") - // a.App().Content.Pop() - // if err := a.App().gotoResource(tokens[0]); err != nil { - // a.App().Flash().Err(err) - // } - // return nil - // } - - // if a.GetTable().SearchBuff().IsActive() { - // return a.GetTable().activateCmd(evt) - // } - - // return evt -} - -// BOZO!! -// func (a *Alias) hydrate() render.TableData { -// var re render.Alias - -// data := render.TableData{ -// Header: re.Header(render.AllNamespaces), -// RowEvents: make(render.RowEvents, 0, len(aliases.Alias)), -// Namespace: resource.NotNamespaced, -// } - -// aa := make(config.ShortNames, len(aliases.Alias)) -// for alias, gvr := range aliases.Alias { -// if _, ok := aa[gvr]; ok { -// aa[gvr] = append(aa[gvr], alias) -// } else { -// aa[gvr] = []string{alias} -// } -// } - -// for gvr, aliases := range aa { -// var row render.Row -// if err := re.Render(aliases, gvr, &row); err != nil { -// log.Error().Err(err).Msgf("Alias render failed") -// continue -// } -// data.RowEvents = append(data.RowEvents, render.RowEvent{ -// Kind: render.EventAdd, -// Row: row, -// }) -// } - -// return data -// } - -// func (a *Alias) resetTitle() { -// a.SetTitle(fmt.Sprintf(aliasTitleFmt, aliasTitle, a.GetRowCount()-1)) -// } diff --git a/internal/view/app.go b/internal/view/app.go index 2767e904..3c20e2d7 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -211,20 +211,26 @@ func (a *App) refreshIndicator() { cluster := model.NewCluster(a.Conn(), mx) var cmx client.ClusterMetrics nos, nmx, err := fetchResources(a) - cpu, mem := "0", "0" - if err == nil { - cluster.Metrics(nos, nmx, &cmx) - cpu = render.AsPerc(cmx.PercCPU) - if cpu == "0" { - cpu = render.NAValue - } - mem = render.AsPerc(cmx.PercMEM) - if mem == "0" { - mem = render.NAValue - } + if err != nil { + log.Error().Err(err).Msgf("unable to refresh cluster indicator") + return } - info := fmt.Sprintf( + if err := cluster.Metrics(nos, nmx, &cmx); err != nil { + log.Error().Err(err).Msgf("unable to refresh cluster indicator") + return + } + + cpu := render.AsPerc(cmx.PercCPU) + if cpu == "0" { + cpu = render.NAValue + } + mem := render.AsPerc(cmx.PercMEM) + if mem == "0" { + mem = render.NAValue + } + + a.indicator().SetPermanent(fmt.Sprintf( indicatorFmt, a.version, cluster.ClusterName(), @@ -232,8 +238,7 @@ func (a *App) refreshIndicator() { cluster.Version(), cpu, mem, - ) - a.indicator().SetPermanent(info) + )) } func (a *App) switchNS(ns string) bool { diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index 23d29ff0..1f447c57 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -2,9 +2,7 @@ package view import ( "context" - "fmt" "io/ioutil" - "os" "path/filepath" "strings" @@ -18,10 +16,7 @@ import ( "github.com/rs/zerolog/log" ) -const ( - benchTitle = "Benchmarks" - resultTitle = "Benchmark Results" -) +const resultTitle = "Benchmark Results" // Benchmark represents a service benchmark results view. type Benchmark struct { @@ -50,31 +45,19 @@ func (b *Benchmark) benchContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyDir, benchDir(b.App().Config)) } -// BOZO!! -// // Start runs the refresh loop -// func (b *Bench) Start() { -// log.Debug().Msgf(">>>> Bench START") -// var ctx context.Context - -// ctx, b.cancelFn = context.WithCancel(context.Background()) -// if err := b.watchBenchDir(ctx); err != nil { -// b.App().Flash().Errf("Unable to watch benchmarks directory %s", err) -// } -// } - func (b *Benchmark) viewBench(app *App, ns, res, path string) { log.Debug().Msgf("VIEWBENCH %q -- %q -- %q", ns, res, path) data, err := readBenchFile(app.Config, b.benchFile()) if err != nil { - b.App().Flash().Errf("Unable to load bench file %s", err) + app.Flash().Errf("Unable to load bench file %s", err) return } b.details.SetText(data) b.details.SetSubject(fileToSubject(path)) - b.App().inject(b.details) - - return + if err := app.inject(b.details); err != nil { + app.Flash().Err(err) + } } func fileToSubject(path string) string { @@ -84,60 +67,30 @@ func fileToSubject(path string) string { return ee[0] + "/" + ee[1] } -func (b *Benchmark) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { - if !b.GetTable().RowSelected() { - return nil - } +// BOZO!! +// func (b *Benchmark) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { +// if !b.GetTable().RowSelected() { +// return nil +// } - sel, file := b.GetTable().GetSelectedItem(), b.benchFile() - dir := filepath.Join(perf.K9sBenchDir, b.App().Config.K9s.CurrentCluster) - showModal(b.App().Content.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() { - if err := os.Remove(filepath.Join(dir, file)); err != nil { - b.App().Flash().Errf("Unable to delete file %s", err) - return - } - b.App().Flash().Infof("Benchmark %s deleted!", sel) - }) +// sel, file := b.GetTable().GetSelectedItem(), b.benchFile() +// dir := filepath.Join(perf.K9sBenchDir, b.App().Config.K9s.CurrentCluster) +// showModal(b.App().Content.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() { +// if err := os.Remove(filepath.Join(dir, file)); err != nil { +// b.App().Flash().Errf("Unable to delete file %s", err) +// return +// } +// b.App().Flash().Infof("Benchmark %s deleted!", sel) +// }) - return nil -} +// return nil +// } func (b *Benchmark) benchFile() string { r := b.GetTable().GetSelectedRowIndex() return ui.TrimCell(b.GetTable().SelectTable, r, 7) } -// BOZO!! -// func (b *Benchmark) watchBenchDir(ctx context.Context) error { -// w, err := fsnotify.NewWatcher() -// if err != nil { -// return err -// } - -// go func() { -// for { -// select { -// case evt := <-w.Events: -// log.Debug().Msgf("Bench event %#v", evt) -// b.App().QueueUpdateDraw(func() { -// b.Refresh() -// }) -// case err := <-w.Errors: -// log.Info().Err(err).Msg("Dir Watcher failed") -// return -// case <-ctx.Done(): -// log.Debug().Msg("!!!! FS WATCHER DONE!!") -// if err := w.Close(); err != nil { -// log.Error().Err(err).Msg("Closing bench watched") -// } -// return -// } -// } -// }() - -// return w.Add(benchDir(b.App().Config)) -// } - // ---------------------------------------------------------------------------- // Helpers... @@ -145,10 +98,6 @@ func benchDir(cfg *config.Config) string { return filepath.Join(perf.K9sBenchDir, cfg.K9s.CurrentCluster) } -func loadBenchDir(cfg *config.Config) ([]os.FileInfo, error) { - return ioutil.ReadDir(benchDir(cfg)) -} - func readBenchFile(cfg *config.Config, n string) (string, error) { data, err := ioutil.ReadFile(filepath.Join(benchDir(cfg), n)) if err != nil { diff --git a/internal/view/browser.go b/internal/view/browser.go index 0870017f..7ce292c1 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -60,7 +60,7 @@ func (b *Browser) Init(ctx context.Context) error { return err } - if err := b.Table.Init(ctx); err != nil { + if err = b.Table.Init(ctx); err != nil { return err } if !dao.IsK9sMeta(b.meta) { @@ -79,7 +79,6 @@ func (b *Browser) Init(ctx context.Context) error { log.Debug().Msgf("ACCESSOR FOR %s -- %#v", b.gvr, b.accessor) b.envFn = b.defaultK9sEnv - b.Table.setFilterFn(b.filterBrowser) b.setNamespace(b.App().Config.ActiveNamespace()) b.refresh() row, _ := b.GetSelection() @@ -111,10 +110,7 @@ func (b *Browser) Stop() { } func (b *Browser) Refresh() { - // BOZO!! - // b.app.QueueUpdateDraw(func() { b.refresh() - // }) } // Name returns the component name. @@ -142,12 +138,6 @@ func (b *Browser) GetTable() *Table { return b.Table } -func (b *Browser) filterBrowser(sel string) { - panic("NYI") - // b.list.SetLabelSelector(sel) - b.refresh() -} - func (b *Browser) update(ctx context.Context) { for { select { @@ -263,7 +253,7 @@ func (b *Browser) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (b *Browser) defaultEnter(app *App, ns, _, sel string) { +func (b *Browser) defaultEnter(app *App, _, _, sel string) { log.Debug().Msgf("--------- Resource %q Verbs %v", sel, b.meta.Verbs) ns, n := client.Namespaced(sel) yaml, err := dao.Describe(b.app.Conn(), b.gvr, ns, n) @@ -277,7 +267,6 @@ func (b *Browser) defaultEnter(app *App, ns, _, sel string) { details.SetTextColor(b.app.Styles.FgColor()) details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, yaml)) details.ScrollToBeginning() - if err := b.app.inject(details); err != nil { b.app.Flash().Err(err) } @@ -317,7 +306,9 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey { details.SetTextColor(b.app.Styles.FgColor()) details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, raw)) details.ScrollToBeginning() - b.app.inject(details) + if err := b.app.inject(details); err != nil { + b.App().Flash().Err(err) + } return nil } diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index 0e4c9be0..7dd685e0 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -150,7 +150,9 @@ func (v *clusterInfoView) refreshMetrics(cluster *model.Cluster, row int) { } var cmx client.ClusterMetrics - cluster.Metrics(nos, nmx, &cmx) + if err := cluster.Metrics(nos, nmx, &cmx); err != nil { + log.Error().Err(err).Msgf("failed to retrieve cluster metrics") + } c := v.GetCell(row, 1) cpu := render.AsPerc(cmx.PercCPU) if cpu == "0" { diff --git a/internal/view/command.go b/internal/view/command.go index 811c2a7e..d4fb5f9e 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -140,7 +140,6 @@ func (c *command) exec(gvr string, comp model.Component) error { log.Error().Err(err).Msg("Config save failed!") } c.app.Content.Stack.ClearHistory() - return c.app.inject(comp) - return nil + return c.app.inject(comp) } diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index 418d0308..79759b06 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -49,7 +49,9 @@ func (c *CronJob) showJobs(app *App, ns, res, path string) { v := NewJob(client.GVR("batch/v1/jobs")) v.SetContextFn(jobCtx(path, string(cj.UID))) - app.inject(v) + if err := app.inject(v); err != nil { + app.Flash().Err(err) + } } func jobCtx(path, uid string) ContextFunc { diff --git a/internal/view/help.go b/internal/view/help.go index a910fef3..6c5c6919 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -34,7 +34,7 @@ func NewHelp() *Help { } // Init initializes the component. -func (v *Help) Init(ctx context.Context) (err error) { +func (v *Help) Init(ctx context.Context) error { if err := v.Table.Init(ctx); err != nil { return nil } diff --git a/internal/view/help_test.go b/internal/view/help_test.go index 5baaaa53..5df3dd3d 100644 --- a/internal/view/help_test.go +++ b/internal/view/help_test.go @@ -1,28 +1,27 @@ package view_test -// import ( -// "testing" +import ( + "testing" -// "github.com/derailed/k9s/internal/dao" -// "github.com/derailed/k9s/internal/ui" -// "github.com/derailed/k9s/internal/view" -// "github.com/stretchr/testify/assert" -// ) + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/ui" + "github.com/derailed/k9s/internal/view" + "github.com/stretchr/testify/assert" +) -// BOZO!! -// func TestHelpNew(t *testing.T) { -// ctx := makeCtx() +func TestHelp(t *testing.T) { + ctx := makeCtx() -// app := ctx.Value(ui.KeyApp).(*view.App) -// po := view.NewPod(client.GVR("v1/pods")) -// po.Init(ctx) -// app.Content.Push(po) + app := ctx.Value(ui.KeyApp).(*view.App) + po := view.NewPod(client.GVR("v1/pods")) + po.Init(ctx) + app.Content.Push(po) -// v := view.NewHelp() -// v.Init(ctx) + v := view.NewHelp() -// assert.Equal(t, 32, v.GetRowCount()) -// assert.Equal(t, 10, v.GetColumnCount()) -// assert.Equal(t, "", v.GetCell(1, 0).Text) -// assert.Equal(t, "Erase", v.GetCell(1, 1).Text) -// } + assert.Nil(t, v.Init(ctx)) + assert.Equal(t, 25, v.GetRowCount()) + assert.Equal(t, 10, v.GetColumnCount()) + assert.Equal(t, "", v.GetCell(1, 0).Text) + assert.Equal(t, "Erase", v.GetCell(1, 1).Text) +} diff --git a/internal/view/helpers.go b/internal/view/helpers.go index 6ce93e90..9ea4d0d4 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -13,8 +13,6 @@ import ( "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" - "golang.org/x/text/language" - "golang.org/x/text/message" ) func showPodsWithLabels(app *App, path string, sel map[string]string) { @@ -38,7 +36,9 @@ func showPods(app *App, path, labelSel, fieldSel string) { if err := app.Config.SetActiveNamespace(ns); err != nil { log.Error().Err(err).Msg("Config NS set failed!") } - app.inject(v) + if err := app.inject(v); err != nil { + app.Flash().Err(err) + } } func podCtx(path, labelSel, fieldSel string) ContextFunc { @@ -118,9 +118,3 @@ func fqn(ns, n string) string { } return ns + "/" + n } - -// AsNumb prints a number with thousand separator. -func asNum(n int) string { - p := message.NewPrinter(language.English) - return p.Sprintf("%d", n) -} diff --git a/internal/view/job.go b/internal/view/job.go index 08650d48..7b20ba21 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -23,7 +23,7 @@ func NewJob(gvr client.GVR) ResourceViewer { return &j } -// BOZO!! Change enter signature? +// TODO!! Change enter signature? func (*Job) showPods(app *App, _, res, path string) { o, err := app.factory.Get("batch/v1/jobs", path, labels.Everything()) if err != nil { diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index 8b5e3a7d..655fe8f2 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -55,5 +55,7 @@ func (l *LogsExtender) showLogs(path string, prev bool) { log.Debug().Msgf("CUSTOM CO FUNC") co = l.containerFn() } - l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)) + if err := l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)); err != nil { + l.App().Flash().Err(err) + } } diff --git a/internal/view/node.go b/internal/view/node.go index 63a8add3..5805c5e1 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -8,8 +8,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -const nodeTitle = "Nodes" - // Node represents a node view. type Node struct { ResourceViewer @@ -65,7 +63,9 @@ func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey { details.SetTextColor(n.App().Styles.FgColor()) details.SetText(colorizeYAML(n.App().Styles.Views().Yaml, raw)) details.ScrollToBeginning() - n.App().inject(details) + if err := n.App().inject(details); err != nil { + n.App().Flash().Err(err) + } return nil } diff --git a/internal/view/ns.go b/internal/view/ns.go index 5927f435..0df38d18 100644 --- a/internal/view/ns.go +++ b/internal/view/ns.go @@ -40,7 +40,9 @@ func (n *Namespace) bindKeys(aa ui.KeyActions) { func (n *Namespace) switchNs(app *App, _, res, sel string) { n.useNamespace(sel) - app.gotoResource("po") + if err := app.gotoResource("po"); err != nil { + app.Flash().Err(err) + } } func (n *Namespace) useNsCmd(evt *tcell.EventKey) *tcell.EventKey { diff --git a/internal/view/pod.go b/internal/view/pod.go index c5bbf18d..7929ceb5 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -19,10 +19,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) -const ( - podTitle = "Pods" - shellCheck = "command -v bash >/dev/null && exec bash || exec sh" -) +const shellCheck = "command -v bash >/dev/null && exec bash || exec sh" // Pod represents a pod viewer. type Pod struct { @@ -59,7 +56,9 @@ func (p *Pod) showContainers(app *App, ns, gvr, path string) { log.Debug().Msgf("SHOW CONTAINERS %q -- %q -- %q", gvr, ns, path) co := NewContainer(client.GVR("containers")) co.SetContextFn(p.podContext) - app.inject(co) + if err := app.inject(co); err != nil { + app.Flash().Err(err) + } } func (p *Pod) podContext(ctx context.Context) context.Context { @@ -124,7 +123,9 @@ func (p *Pod) shellCmd(evt *tcell.EventKey) *tcell.EventKey { picker.SetSelectedFunc(func(i int, t, d string, r rune) { p.shellIn(sel, t) }) - p.App().inject(picker) + if err := p.App().inject(picker); err != nil { + p.App().Flash().Err(err) + } return evt } diff --git a/internal/view/policy.go b/internal/view/policy.go index 03dc2d44..9b564741 100644 --- a/internal/view/policy.go +++ b/internal/view/policy.go @@ -10,23 +10,16 @@ import ( ) const ( - policyTitle = "Policy" - group = "Group" - user = "User" - sa = "ServiceAccount" - allVerbs = "*" + group = "Group" + user = "User" + sa = "ServiceAccount" + allVerbs = "*" ) -type ( - namespacedRole struct { - ns, role string - } - - // Policy presents a RBAC policy viewer. - Policy struct { - ResourceViewer - } -) +// Policy presents a RBAC policy viewer. +type Policy struct { + ResourceViewer +} // NewPolicy returns a new viewer. func NewPolicy(gvr client.GVR) *Policy { @@ -44,27 +37,9 @@ func (p *Policy) Name() string { return "policy" } -// func (p *Policy) Start() { -// p.Stop() -// ctx, cancel := context.WithCancel(context.Background()) -// p.cancel = cancel -// go func(ctx context.Context) { -// for { -// select { -// case <-ctx.Done(): -// return -// case <-time.After(time.Duration(p.app.Config.K9s.GetRefreshRate()) * time.Second): -// p.refresh() -// } -// } -// }(ctx) -// } - func (p *Policy) bindKeys(aa ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) aa.Add(ui.KeyActions{ - // tcell.KeyEscape: ui.NewKeyAction("Back", p.resetCmd, false), - // ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false), ui.KeyShiftP: ui.NewKeyAction("Sort Namespace", p.GetTable().SortColCmd(0, true), false), ui.KeyShiftN: ui.NewKeyAction("Sort Name", p.GetTable().SortColCmd(1, true), false), ui.KeyShiftO: ui.NewKeyAction("Sort Group", p.GetTable().SortColCmd(2, true), false), @@ -72,237 +47,6 @@ func (p *Policy) bindKeys(aa ui.KeyActions) { }) } -func (p *Policy) getTitle() string { - // BOZO!! - // return fmt.Sprintf(rbacTitleFmt, policyTitle, p.subjectKind+":"+p.subjectName, p.GetRowCount()) - return "" -} - -// func (p *Policy) refresh() { -// log.Debug().Msgf(">>>>>>>>>>>>>>> Refreshing Policies") -// // BOZO!! -// defer func(t time.Time) { -// log.Debug().Msgf("Policy Refresh elapsed %v", time.Since(t)) -// }(time.Now()) - -// data, err := p.reconcile() -// if err != nil { -// log.Error().Err(err).Msgf("Refresh for %s:%s", p.subjectKind, p.subjectName) -// p.app.Flash().Err(err) -// } -// p.app.QueueUpdateDraw(func() { -// p.Update(data) -// }) -// } - -// func (p *Policy) resetCmd(evt *tcell.EventKey) *tcell.EventKey { -// if !p.GetTable().SearchBuff().Empty() { -// p.GetTable().SearchBuff().Reset() -// return nil -// } - -// return p.backCmd(evt) -// } - -// func (p *Policy) backCmd(evt *tcell.EventKey) *tcell.EventKey { -// if p.cancel != nil { -// p.cancel() -// } - -// if p.SearchBuff().IsActive() { -// p.SearchBuff().Reset() -// return nil -// } - -// return p.app.PrevCmd(evt) -// } - -// func (p *Policy) reconcile() (render.TableData, error) { -// // BOZO!! -// defer func(t time.Time) { -// log.Debug().Msgf("Policy Reconcile elapsed %v", time.Since(t)) -// }(time.Now()) - -// var table render.TableData - -// evts, errs := p.fetchClusterRoleBindings() -// if len(errs) > 0 { -// for _, err := range errs { -// log.Error().Err(err).Msg("Unable to find cluster policies") -// } -// return table, errs[0] -// } - -// nevts, errs := p.namespacedPolicies() -// if len(errs) > 0 { -// for _, err := range errs { -// log.Error().Err(err).Msg("Unable to find cluster policies") -// } -// return table, errs[0] -// } - -// for _, v := range nevts { -// evts = append(evts, v) -// } - -// return buildTable(p, evts), nil -// } - -// Protocol... - -// func (p *Policy) Header() render.HeaderRow { -// return render.Policy{}.Header(render.AllNamespaces) -// } - -// func (p *Policy) GetCache() render.RowEvents { -// return p.cache -// } - -// func (p *Policy) SetCache(evts render.RowEvents) { -// p.cache = evts -// } - -// func (p *Policy) fetchClusterRoleBindings() (render.Rows, []error) { -// var errs []error -// oo, err := p.app.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) -// if err != nil { -// return nil, append(errs, err) -// } - -// roles := make([]string, 0, len(oo)) -// for _, o := range oo { -// var crb rbacv1.ClusterRoleBinding -// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) -// if err != nil { -// errs = append(errs, err) -// continue -// } -// for _, s := range crb.Subjects { -// if s.Kind == p.subjectKind && s.Name == p.subjectName { -// roles = append(roles, crb.RoleRef.Name) -// } -// } -// } - -// rows := make(render.Rows, 0, len(oo)) -// for _, role := range roles { -// o, err := p.app.factory.Get(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterroles", role, labels.Everything()) -// if err != nil { -// return nil, append(errs, err) -// } -// var cr rbacv1.ClusterRole -// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) -// if err != nil { -// errs = append(errs, err) -// continue -// } - -// for _, v := range p.parseRules("*", "CR:"+role, cr.Rules) { -// rows = append(rows, v) -// } -// } - -// return rows, errs -// } - -// func (p *Policy) fetchRoleBindings() ([]namespacedRole, error) { -// oo, err := p.app.factory.List(render.AllNamespaces, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) -// if err != nil { -// return nil, err -// } - -// rr := make([]namespacedRole, 0, len(oo)) -// for _, o := range oo { -// var rb rbacv1.RoleBinding -// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) -// if err != nil { -// return nil, err -// } -// for _, s := range rb.Subjects { -// if s.Kind == p.subjectKind && s.Name == p.subjectName { -// rr = append(rr, namespacedRole{rb.Namespace, rb.RoleRef.Name}) -// } -// } -// } - -// return rr, nil -// } - -// func (p *Policy) fetchClusterRoles(errs []error, rr []namespacedRole) (render.Rows, []error) { -// rows := make(render.Rows, 0, len(rr)) -// for _, r := range rr { -// o, err := p.app.factory.Get(r.ns, "rbac.authorization.k8s.io/v1/clusterroles", r.role, labels.Everything()) -// if err != nil { -// return nil, append(errs, err) -// } - -// var cr rbacv1.ClusterRole -// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) -// if err != nil { -// errs = append(errs, err) -// continue -// } -// rows = append(rows, p.parseRules(r.ns, "RO:"+r.role, cr.Rules)...) -// } - -// return rows, errs -// } - -// func (p *Policy) namespacedPolicies() (render.Rows, []error) { -// var errs []error -// roles, err := p.fetchRoleBindings() -// if err != nil { -// errs = append(errs, err) -// } - -// return p.fetchClusterRoles(errs, roles) -// } - -// func (p *Policy) parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Rows { -// m := make(render.Rows, 0, len(rules)) -// for _, r := range rules { -// for _, grp := range r.APIGroups { -// for _, res := range r.Resources { -// k := res -// if grp != "" { -// k = res + "." + grp -// } -// for _, na := range r.ResourceNames { -// n := fqn(k, na) -// m = append(m, render.Row{ -// ID: fqn(ns, n), -// Fields: append(policyRow(ns, n, grp, binding), asVerbs(r.Verbs)...), -// }) -// } -// m = append(m, render.Row{ -// ID: fqn(ns, k), -// Fields: append(policyRow(ns, k, grp, binding), asVerbs(r.Verbs)...), -// }) -// } -// } -// for _, nres := range r.NonResourceURLs { -// if nres[0] != '/' { -// nres = "/" + nres -// } -// m = append(m, render.Row{ -// ID: fqn(ns, nres), -// Fields: append(policyRow(ns, nres, "", binding), asVerbs(r.Verbs)...), -// }) -// } -// } - -// return m -// } - -func policyRow(ns, res, grp, binding string) render.Fields { - if grp != "" { - grp = toGroup(grp) - } - - r := make(render.Fields, 0, len(render.Policy{}.Header(render.AllNamespaces))) - return append(r, ns, res, grp, binding) -} - func mapSubject(subject string) string { switch subject { case "g": @@ -314,23 +58,6 @@ func mapSubject(subject string) string { } } -// func showSAPolicy(app *App, _, _, selection string) { -// _, n := client.Namespaced(selection) -// subject, err := mapFuSubject("ServiceAccount") -// if err != nil { -// app.Flash().Err(err) -// return -// } -// app.inject(NewPolicy(app, subject, n)) -// } - -func toGroup(g string) string { - if g == "" { - return "v1" - } - return g -} - func hasVerb(verbs []string, verb string) bool { if len(verbs) == 1 && verbs[0] == allVerbs { return true diff --git a/internal/view/port_forward.go b/internal/view/port_forward.go index af8eedb1..e0d1cdc3 100644 --- a/internal/view/port_forward.go +++ b/internal/view/port_forward.go @@ -13,15 +13,11 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/tview" - "github.com/fsnotify/fsnotify" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" ) -const ( - portForwardTitle = "PortForwards" - promptPage = "prompt" -) +const promptPage = "prompt" // PortForward presents active portforward viewer. type PortForward struct { @@ -49,37 +45,6 @@ func (p *PortForward) portForwardContext(ctx context.Context) context.Context { return context.WithValue(ctx, internal.KeyBenchCfg, p.App().Bench) } -// BOZO!! -// // Start runs the refresh loop. -// func (p *PortForward) Start() { -// path := ui.BenchConfig(p.App().Config.K9s.CurrentCluster) -// var ctx context.Context -// ctx, p.cancelFn = context.WithCancel(context.Background()) -// if err := watchFS(ctx, p.App(), config.K9sHome, path, p.reload); err != nil { -// p.App().Flash().Errf("RuRoh! Unable to watch benchmarks directory %s : %s", config.K9sHome, err) -// } -// } - -// // Name returns the component name. -// func (p *PortForward) Name() string { -// return portForwardTitle -// } - -// func (p *PortForward) reload() { -// path := ui.BenchConfig(p.App().Config.K9s.CurrentCluster) -// log.Debug().Msgf("Reloading Config %s", path) -// if err := p.App().Bench.Reload(path); err != nil { -// p.App().Flash().Err(err) -// } -// p.refresh() -// } - -// func (p *PortForward) refresh() { -// p.Update(p.hydrate()) -// p.App().SetFocus(p) -// p.UpdateTitle() -// } - func (p *PortForward) bindKeys(aa ui.KeyActions) { aa.Add(ui.KeyActions{ tcell.KeyEnter: ui.NewKeyAction("Benchmarks", p.showBenchCmd, true), @@ -94,7 +59,9 @@ func (p *PortForward) bindKeys(aa ui.KeyActions) { } func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey { - p.App().inject(NewBenchmark("benchmarks")) + if err := p.App().inject(NewBenchmark("benchmarks")); err != nil { + p.App().Flash().Err(err) + } return nil } @@ -193,62 +160,9 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -// func (p *PortForward) hydrate() render.TableData { -// var re render.Forward - -// data := render.TableData{ -// Header: re.Header(render.AllNamespaces), -// RowEvents: make(render.RowEvents, 0, len(p.App().forwarders)), -// Namespace: render.AllNamespaces, -// } - -// containers := p.App().Bench.Benchmarks.Containers -// for _, f := range p.App().forwarders { -// fqn := containerID(f.Path(), f.Container()) -// cfg := benchCfg{ -// c: p.App().Bench.Benchmarks.Defaults.C, -// n: p.App().Bench.Benchmarks.Defaults.N, -// } -// if config, ok := containers[fqn]; ok { -// cfg.c, cfg.n = config.C, config.N -// cfg.host, cfg.path = config.HTTP.Host, config.HTTP.Path -// } - -// var row render.Row -// fwd := forwarder{ -// Forwarder: f, -// BenchConfigurator: cfg, -// } -// if err := re.Render(fwd, render.AllNamespaces, &row); err != nil { -// log.Error().Err(err).Msgf("PortForward render failed") -// continue -// } -// data.RowEvents = append(data.RowEvents, render.RowEvent{Kind: render.EventAdd, Row: row}) -// } - -// return data -// } - // ---------------------------------------------------------------------------- // Helpers... -// var _ render.PortForwarder = forwarder{} - -// type forwarder struct { -// render.Forwarder -// render.BenchConfigurator -// } - -// type benchCfg struct { -// c, n int -// host, path string -// } - -// func (b benchCfg) C() int { return b.c } -// func (b benchCfg) N() int { return b.n } -// func (b benchCfg) Host() string { return b.host } -// func (b benchCfg) HttpPath() string { return b.path } - func defaultConfig() config.BenchConfig { return config.BenchConfig{ C: config.DefaultC, @@ -279,36 +193,3 @@ func showModal(p *ui.Pages, msg string, ok func()) { func dismissModal(p *ui.Pages) { p.RemovePage(promptPage) } - -func watchFS(ctx context.Context, app *App, dir, file string, cb func()) error { - w, err := fsnotify.NewWatcher() - if err != nil { - return err - } - - go func() { - for { - select { - case evt := <-w.Events: - log.Debug().Msgf("FS %s event %v", file, evt.Name) - if file == "" || evt.Name == file { - log.Debug().Msgf("Capturing Event %#v", evt) - app.QueueUpdateDraw(func() { - cb() - }) - } - case err := <-w.Errors: - log.Info().Err(err).Msgf("FS %s watcher failed", dir) - return - case <-ctx.Done(): - log.Debug().Msgf("<>", dir) - if err := w.Close(); err != nil { - log.Error().Err(err).Msg("Closing portforward watcher") - } - return - } - } - }() - - return w.Add(dir) -} diff --git a/internal/view/rbac.go b/internal/view/rbac.go index d4724aff..12770d8c 100644 --- a/internal/view/rbac.go +++ b/internal/view/rbac.go @@ -9,19 +9,11 @@ import ( "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" - rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/runtime" ) const ( ClusterRole roleKind = iota Role - - clusterWide = "*" - rbacTitle = "Policies" - rbacTitleFmt = " [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%d[fg:bg:-]][fg:bg:-] " ) var ( @@ -62,263 +54,47 @@ func NewRbac(gvr client.GVR) ResourceViewer { return &r } -func (r *Rbac) showPolicies(app *App, ns, resource, selection string) { - log.Debug().Msgf("SHOWING!! %q--%q--%q", ns, resource, selection) -} - -func (r *Rbac) UpdateTitle() { - // BOZO!! - // r.GetTable().SetTitle(ui.SkinTitle(fmt.Sprintf(rbacTitleFmt, rbacTitle, r.path, r.GetRowCount()-1), r.app.Styles.Frame())) -} - func (r *Rbac) bindKeys(aa ui.KeyActions) { aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) aa.Add(ui.KeyActions{ - // BOZO!! - // tcell.KeyEscape: ui.NewKeyAction("Reset", r.resetCmd, false), - // ui.KeySlash: ui.NewKeyAction("Filter", r.activateCmd, false), ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", r.GetTable().SortColCmd(1, true), false), }) } // BOZO!! -// func (r *Rbac) refresh() { -// if r.app.Conn() == nil { +// func showClusterRoleBinding(app *App, ns, gvr, path string) { +// o, err := app.factory.Get("rbac.authorization.k8s.io/v1/clusterrolebindings", path, labels.Everything()) +// if err != nil { +// app.Flash().Err(err) // return // } -// data, err := r.reconcile(r.roleName, r.roleType) + +// var crb rbacv1.ClusterRoleBinding +// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) // if err != nil { -// log.Error().Err(err).Msgf("Refresh for %s:%d", r.roleName, r.roleType) -// r.app.Flash().Err(err) +// app.Flash().Errf("Unable to retrieve clusterrolebindings for %s", path) +// return // } -// r.Update(data) -// r.UpdateTitle() + +// // BOZO!! Must make sure cluster roles are in cache prior to loading rbac view. +// app.factory.ForResource("-", "rbac.authorization.k8s.io/v1/clusterroles") +// app.factory.WaitForCacheSync() + +// // BOZO!! +// // app.inject(NewRbac(crb.RoleRef.Name, ClusterRole, selection)) // } -// func (r *Rbac) resetCmd(evt *tcell.EventKey) *tcell.EventKey { -// if !r.GetTable().SearchBuff().Empty() { -// r.GetTable().SearchBuff().Reset() -// return nil -// } - -// return r.App().PrevCmd(evt) -// } - -// func (r *Rbac) backCmd(evt *tcell.EventKey) *tcell.EventKey { -// if r.cancelFn != nil { -// r.cancelFn() -// } - -// if r.SearchBuff().IsActive() { -// r.SearchBuff().Reset() -// return nil -// } - -// return r.app.PrevCmd(evt) -// } - -// func (r *Rbac) reconcile(name string, kind roleKind) (render.TableData, error) { -// var table render.TableData - -// rows, err := r.fetchRoles(name, kind) -// if err != nil { -// return table, err -// } - -// return buildTable(r, rows), nil -// } - -// func (r *Rbac) Header() render.HeaderRow { -// return render.Rbac{}.Header(render.AllNamespaces) -// } - -// func (r *Rbac) GetCache() render.RowEvents { -// return r.cache -// } - -// func (r *Rbac) SetCache(evts render.RowEvents) { -// r.cache = evts -// } - -// func (r *Rbac) fetchRoles(name string, kind roleKind) (render.Rows, error) { -// switch kind { -// case ClusterRole: -// return r.loadClusterRoles(name) -// case Role: -// return r.loadRoles(name) -// default: -// return nil, fmt.Errorf("Expecting clusterrole/role but found %d", kind) -// } -// } - -// func (r *Rbac) loadClusterRoles(name string) (render.Rows, error) { -// o, err := r.app.factory.Get("-", "rbac.authorization.k8s.io/v1/clusterroles", name, labels.Everything()) -// if err != nil { -// return nil, err -// } - -// var cr rbacv1.ClusterRole -// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) -// if err != nil { -// return nil, err -// } - -// return r.parseRules(cr.Rules), nil -// } - -// func (r *Rbac) loadRoles(path string) (render.Rows, error) { -// ns, n := client.Namespaced(path) -// o, err := r.app.factory.Get(ns, "rbac.authorization.k8s.io/v1/roles", n, labels.Everything()) -// if err != nil { -// return nil, err -// } - -// var ro rbacv1.Role -// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro) -// if err != nil { -// return nil, err -// } - -// return r.parseRules(ro.Rules), nil -// } - -// func (r *Rbac) parseRules(rules []rbacv1.PolicyRule) render.Rows { -// m := make(render.Rows, 0, len(rules)) -// for _, rule := range rules { -// for _, grp := range rule.APIGroups { -// for _, res := range rule.Resources { -// k := res -// if grp != "" { -// k = res + "." + grp -// } -// for _, na := range rule.ResourceNames { -// m = m.Upsert(r.prepRow(fqn(k, na), grp, rule.Verbs)) -// } -// m = m.Upsert(r.prepRow(k, grp, rule.Verbs)) -// } -// } -// for _, nres := range rule.NonResourceURLs { -// if nres[0] != '/' { -// nres = "/" + nres -// } -// m = m.Upsert(r.prepRow(nres, "", rule.Verbs)) -// } -// } - -// return m -// } - -// func (r *Rbac) prepRow(res, grp string, verbs []string) render.Row { -// if grp != "" { -// grp = toGroup(grp) -// } - -// fields := make(render.Fields, 0, len(r.Header())) -// fields = append(fields, res, group) -// return render.Row{ -// ID: res, -// Fields: append(fields, verbs...), -// } -// } - -// func asVerbs(verbs ...string) []string { -// const ( -// verbLen = 4 -// unknownLen = 30 -// ) - -// r := make([]string, 0, len(k8sVerbs)+1) -// for _, v := range k8sVerbs { -// r = append(r, toVerbIcon(hasVerb(verbs, v))) -// } - -// var unknowns []string -// for _, v := range verbs { -// if hv, ok := httpTok8sVerbs[v]; ok { -// v = hv -// } -// if !hasVerb(k8sVerbs, v) && v != clusterWide { -// unknowns = append(unknowns, v) -// } -// } - -// return append(r, resource.Truncate(strings.Join(unknowns, ","), unknownLen)) -// } - -// func toVerbIcon(ok bool) string { -// if ok { -// return "[green::b] ✓ [::]" -// } -// return "[orangered::b] 𐄂 [::]" -// } - -// func hasVerb(verbs []string, verb string) bool { -// if len(verbs) == 1 && verbs[0] == clusterWide { -// return true -// } - -// for _, v := range verbs { -// if hv, ok := httpTok8sVerbs[v]; ok { -// if hv == verb { -// return true -// } -// } -// if v == verb { -// return true -// } -// } - -// return false -// } - -// func toGroup(g string) string { -// if g == "" { -// return "v1" -// } -// return g -// } - -func showRoleBinding(app *App, _, resource, selection string) { - // ns, n := client.Namespaced(selection) - // rb, err := app.Conn().DialOrDie().RbacV1().RoleBindings(ns).Get(n, metav1.GetOptions{}) - // if err != nil { - // app.Flash().Errf("Unable to retrieve rolebindings for %s", selection) - // return - // } - // BOZO!! - // app.inject(NewRbac(fqn(ns, rb.RoleRef.Name), Role, selection)) -} - -func showClusterRoleBinding(app *App, ns, gvr, path string) { - o, err := app.factory.Get("rbac.authorization.k8s.io/v1/clusterrolebindings", path, labels.Everything()) - if err != nil { - app.Flash().Err(err) - return - } - - var crb rbacv1.ClusterRoleBinding - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) - if err != nil { - app.Flash().Errf("Unable to retrieve clusterrolebindings for %s", path) - return - } - - // BOZO!! Must make sure cluster roles are in cache prior to loading rbac view. - app.factory.ForResource("-", "rbac.authorization.k8s.io/v1/clusterroles") - app.factory.WaitForCacheSync() - - // BOZO!! - // app.inject(NewRbac(crb.RoleRef.Name, ClusterRole, selection)) -} - func showRBAC(app *App, _, gvr, path string) { log.Debug().Msgf("Showing RBAC %q--%q", gvr, path) v := NewRbac(client.GVR("rbac")) - v.SetContextFn(rbacCtxt(app, gvr, path)) - app.inject(v) + v.SetContextFn(rbacCtxt(gvr, path)) + + if err := app.inject(v); err != nil { + app.Flash().Err(err) + } } -func rbacCtxt(app *App, gvr, path string) ContextFunc { +func rbacCtxt(gvr, path string) ContextFunc { return func(ctx context.Context) context.Context { ctx = context.WithValue(ctx, internal.KeyPath, path) return context.WithValue(ctx, internal.KeyGVR, gvr) diff --git a/internal/view/registrar.go b/internal/view/registrar.go index 1a4b4a71..944927d6 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -5,7 +5,6 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/dao" - "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) @@ -16,16 +15,6 @@ func ToResource(o *unstructured.Unstructured, obj interface{}) error { return runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &obj) } -func showCRD(app *App, ns, resource, selection string) { - log.Debug().Msgf("Launching CRD %q -- %q -- %q", ns, resource, selection) - tokens := strings.Split(selection, ".") - _ = tokens - panic("NYI") - // if !app.gotoResource(tokens[0]) { - // app.Flash().Errf("Goto %s failed", tokens[0]) - // } -} - func loadAliases() error { if err := aliases.Load(); err != nil { return err @@ -57,8 +46,6 @@ func loadCustomViewers() MetaViewers { miscRes(m) appsRes(m) rbacRes(m) - extRes(m) - netRes(m) batchRes(m) return m @@ -80,30 +67,6 @@ func coreRes(vv MetaViewers) { vv["v1/secrets"] = MetaViewer{ viewerFn: NewSecret, } - - // vv["v1/serviceaccounts"] = MetaViewer{ - // listFn: resource.NewServiceAccountList, - // enterFn: showSAPolicy, - // } - // vv["v1/configmaps"] = MetaViewer{ - // listFn: resource.NewConfigMapList, - // } - // vv["v1/persistentvolumes"] = MetaViewer{ - // listFn: resource.NewPersistentVolumeList, - // } - // vv["v1/persistentvolumeclaims"] = MetaViewer{ - // listFn: resource.NewPersistentVolumeClaimList, - // } - // vv["v1/endpoints"] = MetaViewer{ - // listFn: resource.NewEndpointsList, - // } - // vv["v1/events"] = MetaViewer{ - // listFn: resource.NewEventList, - // } - // vv["v1/replicationcontrollers"] = MetaViewer{ - // viewFn: NewReplicationController, - // listFn: resource.NewReplicationControllerList, - // } } func miscRes(vv MetaViewers) { @@ -119,16 +82,6 @@ func miscRes(vv MetaViewers) { vv["screendumps"] = MetaViewer{ viewerFn: NewScreenDump, } - - // vv["storage.k8s.io/v1/storageclasses"] = MetaViewer{ - // listFn: resource.NewStorageClassList, - // } - // vv["users"] = MetaViewer{ - // viewFn: NewSubject, - // } - // vv["groups"] = MetaViewer{ - // viewFn: NewSubject, - // } vv["benchmarks"] = MetaViewer{ viewerFn: NewBenchmark, } @@ -173,26 +126,6 @@ func rbacRes(vv MetaViewers) { } } -func extRes(vv MetaViewers) { - // vv["apiextensions.k8s.io/v1/customresourcedefinitions"] = MetaViewer{ - // listFn: resource.NewCustomResourceDefinitionList, - // enterFn: showCRD, - // } - // vv["apiextensions.k8s.io/v1beta1/customresourcedefinitions"] = MetaViewer{ - // listFn: resource.NewCustomResourceDefinitionList, - // enterFn: showCRD, - // } -} - -func netRes(vv MetaViewers) { - // vv["networking.k8s.io/v1/networkpolicies"] = MetaViewer{ - // listFn: resource.NewNetworkPolicyList, - // } - // vv["extensions/v1beta1/ingresses"] = MetaViewer{ - // listFn: resource.NewIngressList, - // } -} - func batchRes(vv MetaViewers) { vv["batch/v1beta1/cronjobs"] = MetaViewer{ viewerFn: NewCronJob, @@ -201,16 +134,3 @@ func batchRes(vv MetaViewers) { viewerFn: NewJob, } } - -// BOZO!! -// func policyRes(vv MetaViewers) { -// vv["policy/v1beta1/poddisruptionbudgets"] = MetaViewer{ -// listFn: resource.NewPDBList, -// } -// } - -// func autoscalingRes(vv MetaViewers) { -// vv["autoscaling/v1/horizontalpodautoscalers"] = MetaViewer{ -// listFn: resource.NewHorizontalPodAutoscalerV1List, -// } -// } diff --git a/internal/view/screen_dump.go b/internal/view/screen_dump.go index 76d454b1..50c13cec 100644 --- a/internal/view/screen_dump.go +++ b/internal/view/screen_dump.go @@ -9,13 +9,10 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/render" - "github.com/fsnotify/fsnotify" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" ) -const dumpTitle = "Screen Dumps" - // ScreenDump presents a directory listing viewer. type ScreenDump struct { ResourceViewer @@ -26,7 +23,6 @@ func NewScreenDump(gvr client.GVR) ResourceViewer { s := ScreenDump{ ResourceViewer: NewBrowser(gvr), } - // BOZO!! Rename Table s.GetTable().SetBorderFocusColor(tcell.ColorSteelBlue) s.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorRoyalBlue, tcell.AttrNone) s.GetTable().SetColorerFn(render.ScreenDump{}.ColorerFunc()) @@ -38,22 +34,6 @@ func NewScreenDump(gvr client.GVR) ResourceViewer { return &s } -// BOZO!! -// BOZO !! Need model watcher! -// // Start starts the directory watcher. -// func (s *ScreenDump) Start() { -// s.Stop() - -// s.GetTable().Actions().Delete(tcell.KeyCtrlS) - -// s.GetTable().Start() -// var ctx context.Context -// ctx, s.GetTable().cancelFn = context.WithCancel(context.Background()) -// if err := s.watchDumpDir(ctx); err != nil { -// s.App().Flash().Errf("Unable to watch screen dumps directory %s", err) -// } -// } - func (s *ScreenDump) dirContext(ctx context.Context) context.Context { dir := filepath.Join(config.K9sDumpDir, s.App().Config.K9s.CurrentCluster) return context.WithValue(ctx, internal.KeyDir, dir) @@ -68,31 +48,3 @@ func (s *ScreenDump) edit(app *App, ns, resource, path string) { app.Flash().Err(errors.New("Failed to launch editor")) } } - -func (s *ScreenDump) watchDumpDir(ctx context.Context) error { - w, err := fsnotify.NewWatcher() - if err != nil { - return err - } - - go func() { - for { - select { - case evt := <-w.Events: - log.Debug().Msgf("ScreenDump event detected %#v", evt) - s.Refresh() - case err := <-w.Errors: - log.Error().Err(err).Msg("Dir Watcher failed") - return - case <-ctx.Done(): - log.Debug().Msg("!!!! ScreenDump WATCHER DONE!!") - if err := w.Close(); err != nil { - log.Error().Err(err).Msg("Closing dump watcher") - } - return - } - } - }() - - return w.Add(filepath.Join(config.K9sDumpDir, s.App().Config.K9s.CurrentCluster)) -} diff --git a/internal/view/secret.go b/internal/view/secret.go index 26255455..6dfbaeaf 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -67,7 +67,9 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey { details.SetTextColor(s.App().Styles.FgColor()) details.SetText(colorizeYAML(s.App().Styles.Views().Yaml, string(raw))) details.ScrollToBeginning() - s.App().inject(details) + if err := s.App().inject(details); err != nil { + s.App().Flash().Err(err) + } return nil } diff --git a/internal/view/subject.go b/internal/view/subject.go index 692e6f7b..5fa1c66c 100644 --- a/internal/view/subject.go +++ b/internal/view/subject.go @@ -19,7 +19,6 @@ type ( ResourceViewer subjectKind string - cache render.RowEvents } ) @@ -27,32 +26,13 @@ type ( func NewSubject(gvr client.GVR) ResourceViewer { s := Subject{ResourceViewer: NewBrowser(gvr)} s.GetTable().SetColorerFn(render.Subject{}.ColorerFunc()) + // BOZO!! // s.GetTable().SetSortCol(1, len(s.Header()), true) s.SetBindKeysFn(s.bindKeys) return &s } -// BOZO!! -// // Start runs the refresh loop. -// func (s *Subject) Start() { -// s.Stop() - -// var ctx context.Context -// ctx, s.cancelFn = context.WithCancel(context.Background()) -// go func(ctx context.Context) { -// for { -// select { -// case <-ctx.Done(): -// log.Debug().Msgf("Subject:%s Watch bailing out!", s.subjectKind) -// return -// case <-time.After(time.Duration(s.App().Config.K9s.GetRefreshRate()) * time.Second): -// s.refresh() -// } -// } -// }(ctx) -// } - // Name returns the component name func (s *Subject) Name() string { return "subjects" @@ -62,10 +42,7 @@ func (s *Subject) bindKeys(aa ui.KeyActions) { aa.Delete(ui.KeyShiftA, ui.KeyShiftP, tcell.KeyCtrlSpace, ui.KeySpace) aa.Add(ui.KeyActions{ tcell.KeyEnter: ui.NewKeyAction("Policies", s.policyCmd, true), - // BOZO!! - // tcell.KeyEscape: ui.NewKeyAction("Back", s.resetCmd, false), - // ui.KeySlash: ui.NewKeyAction("Filter", s.activateCmd, false), - ui.KeyShiftK: ui.NewKeyAction("Sort Kind", s.GetTable().SortColCmd(1, true), false), + ui.KeyShiftK: ui.NewKeyAction("Sort Kind", s.GetTable().SortColCmd(1, true), false), }) } @@ -74,24 +51,12 @@ func (s *Subject) SetSubject(n string) { s.subjectKind = mapSubject(n) } -// BOZO!! -// func (s *Subject) refresh() { -// log.Debug().Msgf("Refreshing Subject...") -// data, err := s.reconcile() -// if err != nil { -// log.Error().Err(err).Msgf("Refresh for %s", s.subjectKind) -// s.App().Flash().Err(err) -// } -// s.App().QueueUpdateDraw(func() { -// s.GetTable().Update(data) -// }) -// } - func (s *Subject) policyCmd(evt *tcell.EventKey) *tcell.EventKey { if !s.GetTable().RowSelected() { return evt } + // BOZO!! // _, n := client.Namespaced(s.GetSelectedItem()) // subject, err := mapFuSubject(s.subjectKind) // if err != nil { @@ -103,184 +68,3 @@ func (s *Subject) policyCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - -// func (s *Subject) resetCmd(evt *tcell.EventKey) *tcell.EventKey { -// if !s.SearchBuff().Empty() { -// s.SearchBuff().Reset() -// return nil -// } - -// return s.backCmd(evt) -// } - -// func (s *Subject) backCmd(evt *tcell.EventKey) *tcell.EventKey { -// if s.SearchBuff().IsActive() { -// s.SearchBuff().Reset() -// return nil -// } - -// return s.App().PrevCmd(evt) -// } - -// func (s *Subject) reconcile() (render.TableData, error) { -// var table render.TableData -// if s.App().Conn() == nil { -// return table, nil -// } - -// rows, err := s.fetchClusterRoleBindings() -// if err != nil { -// return table, err -// } - -// nrows, err := s.fetchRoleBindings() -// if err != nil { -// return table, err -// } -// for k, v := range nrows { -// rows[k] = v -// } - -// return buildTable(s, rows), nil -// } - -// func (s *Subject) Header() render.HeaderRow { -// return render.Subject{}.Header(render.AllNamespaces) -// } - -// func (s *Subject) GetCache() render.RowEvents { -// return s.cache -// } - -// func (s *Subject) SetCache(rows render.RowEvents) { -// s.cache = rows -// } - -// func buildTable(c TableInfo, rows render.Rows) render.TableData { -// table := render.TableData{ -// Header: c.Header(), -// Namespace: "*", -// } - -// cache := c.GetCache() -// if len(cache) == 0 { -// cache := make(render.RowEvents, 0, len(rows)) -// for _, row := range rows { -// cache = append(cache, render.RowEvent{Kind: render.EventAdd, Row: row}) -// } -// table.RowEvents = cache -// return table -// } - -// for _, row := range rows { -// idx, ok := cache.FindIndex(row.ID) -// if !ok { -// cache = append(cache, render.RowEvent{Kind: render.EventAdd, Row: row}) -// continue -// } - -// old := cache[idx].Row -// deltas := make(render.DeltaRow, len(row.Fields)) -// if reflect.DeepEqual(old, row) { -// cache[idx].Kind = render.EventUnchanged -// cache[idx].Deltas = deltas -// continue -// } - -// cache[idx].Kind = render.EventUpdate -// for i, field := range old.Fields { -// if field != row.Fields[i] { -// deltas[i] = field -// } -// } -// cache[idx].Deltas = deltas -// } - -// for _, row := range rows { -// if _, ok := cache.FindIndex(row.ID); !ok { -// cache.Delete(row.ID) -// } -// } -// table.RowEvents = cache - -// return table -// } - -// func (s *Subject) fetchClusterRoleBindings() (render.Rows, error) { -// s.App().factory.Preload(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterroles") -// oo, err := s.App().factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) -// if err != nil { -// return nil, err -// } - -// rows := make(render.Rows, 0, len(oo)) -// for _, o := range oo { -// var crb rbacv1.ClusterRoleBinding -// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) -// if err != nil { -// return nil, err -// } -// for _, subject := range crb.Subjects { -// if subject.Kind != s.subjectKind { -// continue -// } -// rows = append(rows, render.Row{ -// ID: subject.Name, -// Fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name}, -// }) -// } -// } - -// return rows, nil -// } - -// func (s *Subject) fetchRoleBindings() (render.Rows, error) { -// s.App().factory.Preload(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterroles") -// oo, err := s.App().factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) -// if err != nil { -// return nil, err -// } - -// rows := make(render.Rows, 0, len(oo)) -// for _, o := range oo { -// var rb rbacv1.RoleBinding -// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) -// if err != nil { -// return nil, err -// } -// for _, subject := range rb.Subjects { -// if subject.Kind == s.subjectKind { -// rows = append(rows, render.Row{ -// ID: subject.Name, -// Fields: render.Fields{subject.Name, "RoleBinding", rb.Name}, -// }) -// } -// } -// } - -// return rows, nil -// } - -// func mapCmdSubject(subject string) string { -// switch subject { -// case "groups": -// return group -// case "sas": -// return sa -// default: -// return user -// } -// } - -// func mapFuSubject(subject string) (string, error) { -// switch subject { -// case group: -// return "g", nil -// case sa: -// return "s", nil -// case user: -// return "u", nil -// default: -// return "", fmt.Errorf("Unknown subject %q should be one of user, group, serviceaccount", subject) -// } -// } diff --git a/internal/view/table.go b/internal/view/table.go index e38d72f1..a23b47ef 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -93,15 +93,6 @@ func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -func (t *Table) setFilterFn(fn func(string)) { - t.filterFn = fn - - cmd := t.SearchBuff().String() - if ui.IsLabelSelector(cmd) && t.filterFn != nil { - t.filterFn(ui.TrimLabelSelector(cmd)) - } -} - func (t *Table) bindKeys() { t.Actions().Add(ui.KeyActions{ ui.KeySpace: ui.NewKeyAction("Mark", t.markCmd, true), diff --git a/internal/view/table_helper.go b/internal/view/table_helper.go index 5d60f70a..86993479 100644 --- a/internal/view/table_helper.go +++ b/internal/view/table_helper.go @@ -65,12 +65,7 @@ func saveTable(cluster, title, path string, data render.TableData) (string, erro }() w := csv.NewWriter(out) - // BOZO!! Method on header - headers := make([]string, len(data.Header)) - for i, h := range data.Header { - headers[i] = h.Name - } - if err := w.Write([]string(headers)); err != nil { + if err := w.Write(data.Header.Columns()); err != nil { return "", err } diff --git a/internal/watch/factory.go b/internal/watch/factory.go index de0f780c..06ed861f 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -13,7 +13,6 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" di "k8s.io/client-go/dynamic/dynamicinformer" "k8s.io/client-go/informers" - "k8s.io/client-go/informers/internalinterfaces" ) const ( @@ -22,46 +21,13 @@ const ( clusterScope = "-" ) -// BOZO!! -// // Authorizer checks what a user can or cannot do to a resource. -// type Authorizer interface { -// // CanI returns true if the user can use these actions for a given resource. -// CanI(ns, gvr string, verbs []string) (bool, error) -// } - -// type Connection interface { -// Authorizer - -// // DialOrDie dials client api. -// DialOrDie() kubernetes.Interface - -// // MXDial dials metrics api. -// MXDial() (*versioned.Clientset, error) - -// // DynDialOrDie dials dynamic client api. -// DynDialOrDie() dynamic.Interface - -// // RestConfigOrDie return a client configuration. -// RestConfigOrDie() *restclient.Config - -// // Config returns the current kubeconfig. -// Config() *k8s.Config - -// // CachedDiscovery returns a cached client. -// CachedDiscovery() (*disk.CachedDiscoveryClient, error) - -// // SwithContextOrDie switch to a new kube context. -// SwitchContextOrDie(ctx string) -// } - // Factory tracks various resource informers. type Factory struct { - factories map[string]di.DynamicSharedInformerFactory - client client.Connection - stopChan chan struct{} - tweakListOptions internalinterfaces.TweakListOptionsFunc - activeNS string - forwarders Forwarders + factories map[string]di.DynamicSharedInformerFactory + client client.Connection + stopChan chan struct{} + activeNS string + forwarders Forwarders } // NewFactory returns a new informers factory. @@ -259,14 +225,6 @@ func (f *Factory) ensureFactory(ns string) di.DynamicSharedInformerFactory { return f.factories[ns] } -func (f *Factory) register(gvr, ns string, stopChan <-chan struct{}) error { - log.Debug().Msgf("Registering GVR %q - %s", ns, gvr) - f.factories[ns].ForResource(toGVR(gvr)) - f.factories[ns].Start(stopChan) - - return nil -} - func toGVR(gvr string) schema.GroupVersionResource { log.Debug().Msgf("GVR -- %q", gvr) tokens := strings.Split(gvr, "/")