diff --git a/internal/client/client.go b/internal/client/client.go index ffa77896..1e9fbd25 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -77,45 +77,42 @@ func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview { } } -func makeKey(ns, gvr string, vv []string) string { +func makeCacheKey(ns, gvr string, vv []string) string { return ns + ":" + gvr + "::" + strings.Join(vv, ",") } // CanI checks if user has access to a certain resource. -func (a *APIClient) CanI(ns, gvr string, verbs []string) (bool, error) { - defer func(t time.Time) { - log.Debug().Msgf("AUTH elapsed %v", time.Since(t)) +func (a *APIClient) CanI(ns, gvr string, verbs []string) (auth bool, err error) { + key := makeCacheKey(ns, gvr, verbs) + defer func(t time.Time) string { + log.Debug().Msgf("AUTH elapsed %t--%q %v", auth, key, time.Since(t)) + return "s" }(time.Now()) - log.Debug().Msgf("AUTH %q:%q -- %v", ns, gvr, verbs) - sar := makeSAR(ns, gvr) - - key := makeKey(ns, gvr, verbs) if v, ok := a.cache.Get(key); ok { - if auth, ok := v.(bool); ok { + if auth, ok = v.(bool); ok { return auth, nil } } - - dial := a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews() + dial, sar := a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews(), makeSAR(ns, gvr) for _, v := range verbs { sar.Spec.ResourceAttributes.Verb = v resp, err := dial.Create(sar) if err != nil { log.Warn().Err(err).Msgf(" Dial Failed!") a.cache.Add(key, false, cacheExpiry) - return false, err + return auth, err } if !resp.Status.Allowed { log.Debug().Msgf(" NO %q ;(", v) a.cache.Add(key, false, cacheExpiry) - return false, fmt.Errorf("`%s access denied for user on %q:%s", v, ns, gvr) + return auth, fmt.Errorf("`%s access denied for user on %q:%s", v, ns, gvr) } } - log.Debug().Msgf(" YES!") + auth = true a.cache.Add(key, true, cacheExpiry) - return true, nil + return } // CurrentNamespaceName return namespace name set via either cli arg or cluster config. diff --git a/internal/client/metrics.go b/internal/client/metrics.go index 05947d99..d6a94f79 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -76,7 +76,7 @@ func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) { return &mx, fmt.Errorf("No metrics-server detected on cluster") } - auth, err := m.CanI("", "metrics.k8s.io/v1beta1/nodes", []string{"list"}) + auth, err := m.CanI("", "metrics.k8s.io/v1beta1/nodes", ListAccess) if !auth || err != nil { return &mx, err } @@ -98,7 +98,7 @@ func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, e ns = AllNamespaces } - auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", []string{"list"}) + auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", ListAccess) if !auth || err != nil { return &mx, err } @@ -121,7 +121,7 @@ func (m *MetricsServer) FetchPodMetrics(ns, sel string) (*mv1beta1.PodMetrics, e if ns == NamespaceAll { ns = AllNamespaces } - auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", []string{"get"}) + auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", GetAccess) if !auth || err != nil { return &mx, err } diff --git a/internal/client/types.go b/internal/client/types.go index 0d18bf30..1a37ab5e 100644 --- a/internal/client/types.go +++ b/internal/client/types.go @@ -24,6 +24,34 @@ const ( ClusterScope = "-" ) +const ( + // CreateVerb represents create access on a resource. + CreateVerb = "create" + // UpdateVerb represents an update access on a resource. + UpdateVerb = "update" + // UpdateVerb represents a patch access on a resource. + PatchVerb = "patch" + // DeleteVerb represents a delete access on a resource. + DeleteVerb = "delete" + // GetVerb represents a get access on a resource. + GetVerb = "get" + // ListVerb represents a list access on a resource. + ListVerb = "list" + // WatchVerb represents a watch access on a resource. + WatchVerb = "watch" +) + +var ( + // GetAccess reads a resource. + GetAccess = []string{GetVerb} + // ListAccess list resources. + ListAccess = []string{ListVerb} + // MonitorAcces monitors a collection of resources. + MonitorAccess = []string{ListVerb, WatchVerb} + // ReadAllAccess represents an all read access to a resource. + ReadAllAccess = []string{GetVerb, ListVerb, WatchVerb} +) + // Connection represents a Kubenetes apiserver connection. // BOZO!! Refactor! type Connection interface { diff --git a/internal/dao/container.go b/internal/dao/container.go index 294ae22a..b2cddea4 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "time" "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" @@ -29,32 +30,29 @@ type Container struct { // List returns a collection of containers. func (c *Container) List(ctx context.Context, _ string) ([]runtime.Object, error) { - path, ok := ctx.Value(internal.KeyPath).(string) + fqn, ok := ctx.Value(internal.KeyPath).(string) if !ok { return nil, fmt.Errorf("no context path for %q", c.gvr) } - ns, _ := render.Namespaced(path) - o, err := c.Factory.Get("v1/pods", path, true, labels.Everything()) + po, err := c.fetchPod(fqn) if err != nil { return nil, err } - var po v1.Pod - err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po) - if err != nil { - return nil, err - } - res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)) - mx := client.NewMetricsServer(c.Client()) + ns, _ := render.Namespaced(fqn) var pmx *mv1beta1.PodMetrics - if c.Client() != nil { - var err error - pmx, err = mx.FetchPodMetrics(ns, po.Name) - if err != nil { - log.Warn().Err(err).Msgf("No metrics found for pod %q:%q", ns, po.Name) + if c.Client().HasMetrics() { + mx := client.NewMetricsServer(c.Client()) + if c.Client() != nil { + var err error + pmx, err = mx.FetchPodMetrics(ns, po.Name) + if err != nil { + log.Warn().Err(err).Msgf("No metrics found for pod %q:%q", ns, po.Name) + } } } + res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)) for _, co := range po.Spec.InitContainers { res = append(res, makeContainerRes(co, po, pmx, true)) } @@ -87,7 +85,7 @@ func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts Lo // Logs fetch container logs for a given pod and container. func (c *Container) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) { ns, _ := client.Namespaced(path) - auth, err := c.Client().CanI(ns, "v1/pods:log", []string{"get"}) + auth, err := c.Client().CanI(ns, "v1/pods:log", client.GetAccess) if !auth || err != nil { return nil, err } @@ -99,14 +97,18 @@ func (c *Container) Logs(path string, opts *v1.PodLogOptions) (*restclient.Reque // ---------------------------------------------------------------------------- // Helpers... -func makeContainerRes(co v1.Container, po v1.Pod, pmx *mv1beta1.PodMetrics, isInit bool) render.ContainerRes { +func makeContainerRes(co v1.Container, po *v1.Pod, pmx *mv1beta1.PodMetrics, isInit bool) render.ContainerRes { + defer func(t time.Time) { + log.Debug().Msgf("MAKE-CO %s -- %v", co.Name, time.Since(t)) + }(time.Now()) + cmx, err := containerMetrics(co.Name, pmx) if err != nil { - log.Warn().Err(err).Msgf("Container metrics for %s", co.Name) + log.Warn().Err(err).Msgf("Fail container metrics for %s", co.Name) } return render.ContainerRes{ - Container: co, + Container: &co, Status: getContainerStatus(co.Name, po.Status), Metrics: cmx, IsInit: isInit, @@ -114,11 +116,7 @@ func makeContainerRes(co v1.Container, po v1.Pod, pmx *mv1beta1.PodMetrics, isIn } } -func containerMetrics(n string, mx runtime.Object) (*mv1beta1.ContainerMetrics, error) { - pmx, ok := mx.(*mv1beta1.PodMetrics) - if !ok { - return nil, fmt.Errorf("expecting podmetrics but got `%T", mx) - } +func containerMetrics(n string, pmx *mv1beta1.PodMetrics) (*mv1beta1.ContainerMetrics, error) { if pmx == nil { return nil, fmt.Errorf("no metrics for container %s", n) } @@ -136,7 +134,6 @@ func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { return &c } } - for _, c := range status.InitContainerStatuses { if c.Name == co { return &c @@ -145,3 +142,17 @@ func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { return nil } + +func (c *Container) fetchPod(fqn string) (*v1.Pod, error) { + o, err := c.Factory.Get("v1/pods", fqn, true, labels.Everything()) + if err != nil { + return nil, err + } + var po v1.Pod + err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po) + if err != nil { + return nil, err + } + + return &po, nil +} diff --git a/internal/dao/container_test.go b/internal/dao/container_test.go index b9b281de..e8f7c356 100644 --- a/internal/dao/container_test.go +++ b/internal/dao/container_test.go @@ -10,10 +10,17 @@ import ( "github.com/derailed/k9s/internal/watch" "github.com/ghodss/yaml" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/version" + "k8s.io/client-go/discovery/cached/disk" + "k8s.io/client-go/dynamic" "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes" + restclient "k8s.io/client-go/rest" + versioned "k8s.io/metrics/pkg/client/clientset/versioned" ) func TestContainerList(t *testing.T) { @@ -29,12 +36,36 @@ func TestContainerList(t *testing.T) { // ---------------------------------------------------------------------------- // Helpers... +type conn struct{} + +func makeConn() *conn { + return &conn{} +} + +func (c *conn) Config() *client.Config { return nil } +func (c *conn) DialOrDie() kubernetes.Interface { return nil } +func (c *conn) SwitchContextOrDie(ctx string) {} +func (c *conn) CachedDiscoveryOrDie() *disk.CachedDiscoveryClient { return nil } +func (c *conn) RestConfigOrDie() *restclient.Config { return nil } +func (c *conn) MXDial() (*versioned.Clientset, error) { return nil, nil } +func (c *conn) DynDialOrDie() dynamic.Interface { return nil } +func (c *conn) HasMetrics() bool { return false } +func (c *conn) IsNamespaced(n string) bool { return false } +func (c *conn) SupportsResource(group string) bool { return false } +func (c *conn) ValidNamespaces() ([]v1.Namespace, error) { return nil, nil } +func (c *conn) SupportsRes(grp string, versions []string) (string, bool, error) { + return "", false, nil +} +func (c *conn) ServerVersion() (*version.Info, error) { return nil, nil } +func (c *conn) CurrentNamespaceName() (string, error) { return "", nil } +func (c *conn) CanI(ns, gvr string, verbs []string) (bool, error) { return true, nil } + type podFactory struct{} var _ dao.Factory = testFactory{} func (f podFactory) Client() client.Connection { - return nil + return makeConn() } func (f podFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) { var m map[string]interface{} diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index a4b1c370..3881906a 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -22,7 +22,7 @@ type CronJob struct { // Run a CronJob. func (c *CronJob) Run(path string) error { ns, n := client.Namespaced(path) - auth, err := c.Client().CanI(ns, "batch/v1beta1/cronjobs", []string{"get", "create"}) + auth, err := c.Client().CanI(ns, "batch/v1beta1/cronjobs", []string{client.GetVerb, client.CreateVerb}) if !auth || err != nil { return err } diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 96389132..92669433 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -31,7 +31,7 @@ type Deployment struct { // Scale a Deployment. func (d *Deployment) Scale(path string, replicas int32) error { ns, n := client.Namespaced(path) - auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", []string{"get", "update"}) + auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", []string{client.GetVerb, client.UpdateVerb}) if !auth || err != nil { return err } @@ -59,7 +59,7 @@ func (d *Deployment) Restart(path string) error { } ns, _ := client.Namespaced(path) - auth, err := d.Client().CanI(ns, "apps/v1/deployments", []string{"patch"}) + auth, err := d.Client().CanI(ns, "apps/v1/deployments", []string{client.PatchVerb}) if !auth || err != nil { return err } diff --git a/internal/dao/ds.go b/internal/dao/ds.go index fb78f9f3..d073f976 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -44,7 +44,7 @@ func (d *DaemonSet) Restart(path string) error { return err } - auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", []string{"patch"}) + auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", []string{client.PatchVerb}) if !auth || err != nil { return err } diff --git a/internal/dao/generic.go b/internal/dao/generic.go index bb7aea4e..bb5cb8ec 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -29,7 +29,6 @@ func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) if !ok { log.Warn().Msgf("No label selector found in context. Listing all resources") } - if client.IsAllNamespace(ns) { ns = client.AllNamespaces } @@ -90,7 +89,7 @@ func (g *Generic) ToYAML(path string) (string, error) { // Delete deletes a resource. func (g *Generic) Delete(path string, cascade, force bool) error { ns, n := client.Namespaced(path) - auth, err := g.Client().CanI(ns, g.gvr.String(), []string{"delete"}) + auth, err := g.Client().CanI(ns, g.gvr.String(), []string{client.DeleteVerb}) if !auth || err != nil { return err } diff --git a/internal/dao/node.go b/internal/dao/node.go index 7964256e..98501468 100644 --- a/internal/dao/node.go +++ b/internal/dao/node.go @@ -4,6 +4,7 @@ import ( "context" "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" @@ -59,7 +60,7 @@ func (n *Node) List(ctx context.Context, ns string) ([]runtime.Object, error) { // FetchNodes retrieves all nodes. func FetchNodes(f Factory) (*v1.NodeList, error) { - auth, err := f.Client().CanI("", "v1/nodes", []string{"list"}) + auth, err := f.Client().CanI("", "v1/nodes", []string{client.ListVerb}) if !auth || err != nil { return nil, err } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index da219fd9..cdb7ad43 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -85,7 +85,7 @@ func (p *Pod) List(ctx context.Context, ns string) ([]runtime.Object, error) { // Logs fetch container logs for a given pod and container. func (p *Pod) Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) { ns, _ := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pods:log", []string{"get"}) + auth, err := p.Client().CanI(ns, "v1/pods:log", []string{client.GetVerb}) if !auth || err != nil { return nil, err } diff --git a/internal/dao/port_forward.go b/internal/dao/port_forward.go index 815ee91a..befdb3e8 100644 --- a/internal/dao/port_forward.go +++ b/internal/dao/port_forward.go @@ -25,7 +25,7 @@ type PortForward struct { // Delete a portforward. func (p *PortForward) Delete(path string, cascade, force bool) error { ns, _ := client.Namespaced(path) - auth, err := p.Client().CanI(ns, "v1/pods:portforward", []string{"delete"}) + auth, err := p.Client().CanI(ns, "v1/pods:portforward", []string{client.DeleteVerb}) if !auth || err != nil { return err } diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index f166110a..a2f77cc8 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -92,7 +92,7 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo p.path, p.container, p.ports, p.age = path, co, ports, time.Now() ns, n := client.Namespaced(path) - auth, err := p.CanI(ns, "v1/pods", []string{"get"}) + auth, err := p.CanI(ns, "v1/pods", []string{client.GetVerb}) if !auth || err != nil { return nil, err } @@ -104,7 +104,7 @@ func (p *PortForwarder) Start(path, co, address string, ports []string) (*portfo return nil, fmt.Errorf("unable to forward port because pod is not running. Current status=%v", pod.Status.Phase) } - auth, err = p.CanI(ns, "v1/pods:portforward", []string{"update"}) + auth, err = p.CanI(ns, "v1/pods:portforward", []string{client.UpdateVerb}) if !auth || err != nil { return nil, err } diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 88552a8b..b3870d0f 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -31,7 +31,7 @@ type StatefulSet struct { // Scale a StatefulSet. func (s *StatefulSet) Scale(path string, replicas int32) error { ns, n := client.Namespaced(path) - auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", []string{"get", "update"}) + auth, err := s.Client().CanI(ns, "apps/v1/statefulsets:scale", []string{client.GetVerb, client.UpdateVerb}) if !auth || err != nil { return err } @@ -58,7 +58,7 @@ func (s *StatefulSet) Restart(path string) error { } ns, _ := client.Namespaced(path) - auth, err := s.Client().CanI(ns, "apps/v1/statefulsets", []string{"patch"}) + auth, err := s.Client().CanI(ns, "apps/v1/statefulsets", []string{client.PatchVerb}) if !auth || err != nil { return err } diff --git a/internal/dao/table.go b/internal/dao/table.go index ce4c8e11..4fe6a14c 100644 --- a/internal/dao/table.go +++ b/internal/dao/table.go @@ -27,7 +27,6 @@ func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) { if err != nil { return nil, err } - o, err := c.Get(). SetHeader("Accept", a). Namespace(ns). diff --git a/internal/model/table.go b/internal/model/table.go index 1b5767ef..0c207b46 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -15,6 +15,8 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +const iniRefreshRate = 300 * time.Millisecond + // TableListener represents a table model listener. type TableListener interface { // TableDataChanged notifies the model data changed. @@ -167,11 +169,14 @@ func (t *Table) Peek() render.TableData { func (t *Table) updater(ctx context.Context) { defer log.Debug().Msgf("Model canceled -- %q", t.gvr) + + rate := iniRefreshRate for { select { case <-ctx.Done(): return - case <-time.After(t.refreshRate): + case <-time.After(rate): + rate = t.refreshRate t.refresh(ctx) } } diff --git a/internal/render/container.go b/internal/render/container.go index fb26f311..393ad1db 100644 --- a/internal/render/container.go +++ b/internal/render/container.go @@ -132,7 +132,7 @@ func gatherMetrics(co ContainerRes) (c, p metric) { mem: ToMi(mem), } - rcpu, rmem := containerResources(co.Container) + rcpu, rmem := containerResources(*co.Container) if rcpu != nil { p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue()))) } @@ -187,7 +187,7 @@ func probe(p *v1.Probe) string { // ContainerRes represents a container and its metrics. type ContainerRes struct { - Container v1.Container + Container *v1.Container Status *v1.ContainerStatus Metrics *mv1beta1.ContainerMetrics IsInit bool diff --git a/internal/render/container_test.go b/internal/render/container_test.go index bf01314a..8183eccc 100644 --- a/internal/render/container_test.go +++ b/internal/render/container_test.go @@ -67,8 +67,8 @@ func makeAge() metav1.Time { return metav1.Time{Time: testTime()} } -func makeContainer() v1.Container { - return v1.Container{ +func makeContainer() *v1.Container { + return &v1.Container{ Name: "fred", Image: "img", Resources: v1.ResourceRequirements{ diff --git a/internal/view/browser.go b/internal/view/browser.go index a94a1e17..1027c9e9 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -14,7 +14,6 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui/dialog" - "github.com/derailed/k9s/internal/watch" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -50,8 +49,9 @@ func (b *Browser) Init(ctx context.Context) error { if err = b.Table.Init(ctx); err != nil { return err } + ns := b.app.Config.ActiveNamespace() if dao.IsK8sMeta(b.meta) { - if _, e := b.app.factory.CanForResource(b.app.Config.ActiveNamespace(), b.GVR(), []string{"list", "watch"}); e != nil { + if _, e := b.app.factory.CanForResource(ns, b.GVR(), client.MonitorAccess); e != nil { return e } } @@ -65,7 +65,7 @@ func (b *Browser) Init(ctx context.Context) error { return err } - b.setNamespace(b.App().Config.ActiveNamespace()) + b.setNamespace(ns) row, _ := b.GetSelection() if row == 0 && b.GetRowCount() > 0 { b.Select(1, 0) @@ -296,7 +296,7 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { ns = client.NamespaceAll } - auth, err := b.App().factory.Client().CanI(ns, b.GVR(), watch.ReadVerbs) + auth, err := b.App().factory.Client().CanI(ns, b.GVR(), client.MonitorAccess) if !auth { b.App().Flash().Err(err) return nil diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index c5a46425..1477a398 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -4,7 +4,6 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" - "github.com/derailed/k9s/internal/watch" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" ) @@ -60,7 +59,7 @@ func (l *LogsExtender) showLogs(path string, prev bool) { log.Debug().Msgf("SHOWING LOGS path %q", path) // Need to load and wait for pods ns, _ := render.Namespaced(path) - _, err := l.App().factory.CanForResource(ns, "v1/pods", watch.ReadVerbs) + _, err := l.App().factory.CanForResource(ns, "v1/pods", client.MonitorAccess) if err != nil { l.App().Flash().Err(err) return diff --git a/internal/watch/factory.go b/internal/watch/factory.go index f007dd17..a814adc7 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -2,6 +2,7 @@ package watch import ( "fmt" + "sync" "time" "github.com/derailed/k9s/internal/client" @@ -18,15 +19,13 @@ const ( clusterScope = "-" ) -// ReadVerbs lists out RO verbs. -var ReadVerbs = []string{"get", "list", "watch"} - // Factory tracks various resource informers. type Factory struct { factories map[string]di.DynamicSharedInformerFactory client client.Connection stopChan chan struct{} forwarders Forwarders + mx sync.RWMutex } // NewFactory returns a new informers factory. @@ -54,6 +53,9 @@ func (f *Factory) Terminate() { close(f.stopChan) f.stopChan = nil } + + f.mx.Lock() + defer f.mx.Unlock() for k := range f.factories { delete(f.factories, k) } @@ -70,7 +72,7 @@ func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]run ns = allNamespaces } log.Debug().Msgf("List %q:%q", ns, gvr) - inf, err := f.CanForResource(ns, gvr, []string{"list", "watch"}) + inf, err := f.CanForResource(ns, gvr, client.MonitorAccess) if err != nil { return nil, err } @@ -86,13 +88,18 @@ func (f *Factory) List(gvr, ns string, wait bool, labels labels.Selector) ([]run // Get retrieves a given resource. func (f *Factory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) { + defer func(t time.Time) { + log.Debug().Msgf("FACTORY-GET %q--%q elapsed %v", gvr, path, time.Since(t)) + }(time.Now()) + ns, n := namespaced(path) log.Debug().Msgf("GET %q:%q::%q", ns, gvr, n) - Debug(f, "", gvr) - inf, err := f.CanForResource(ns, gvr, []string{"get"}) + inf, err := f.CanForResource(ns, gvr, []string{client.GetVerb}) if err != nil { return nil, err } + + DumpFactory(f) if wait { f.waitForCacheSync(ns) } @@ -193,6 +200,8 @@ func (f *Factory) ensureFactory(ns string) di.DynamicSharedInformerFactory { } log.Debug().Msgf("FACTORY_NEW for ns %q", ns) + f.mx.Lock() + defer f.mx.Unlock() f.factories[ns] = di.NewFilteredDynamicSharedInformerFactory( f.client.DynDialOrDie(), defaultResync, @@ -210,8 +219,6 @@ func (f *Factory) AddForwarder(pf Forwarder) { // DeleteForwarder deletes portforward for a given container. func (f *Factory) DeleteForwarder(path string) { - - f.forwarders.Dump() count := f.forwarders.Kill(path) log.Warn().Msgf("Deleted (%d) portforward for %q", count, path) } diff --git a/internal/watch/helper.go b/internal/watch/helper.go index abd988d1..5e018b30 100644 --- a/internal/watch/helper.go +++ b/internal/watch/helper.go @@ -28,7 +28,7 @@ func namespaced(n string) (string, string) { } // Dump for debug. -func Dump(f *Factory) { +func DumpFactory(f *Factory) { log.Debug().Msgf("----------- FACTORIES -------------") for ns := range f.factories { log.Debug().Msgf(" Factory for NS %q", ns) @@ -37,7 +37,7 @@ func Dump(f *Factory) { } // Debug for debug. -func Debug(f *Factory, ns string, gvr string) { +func DebugFactory(f *Factory, ns string, gvr string) { log.Debug().Msgf("----------- DEBUG FACTORY (%s) -------------", gvr) fac, ok := f.factories[ns] if !ok {