diff --git a/internal/client/client.go b/internal/client/client.go index fd45e380..2d71f9a2 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -48,7 +48,6 @@ type Connection interface { ValidNamespaces() ([]v1.Namespace, error) SupportsRes(grp string, versions []string) (string, bool, error) ServerVersion() (*version.Info, error) - FetchNodes() (*v1.NodeList, error) CurrentNamespaceName() (string, error) } @@ -74,13 +73,18 @@ func InitConnectionOrDie(config *Config) *APIClient { } func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview { - res := GVR(gvr).AsGVR() + if ns == "-" { + ns = "" + } + spec := NewGVR(gvr) + res := spec.AsGVR() return &authorizationv1.SelfSubjectAccessReview{ Spec: authorizationv1.SelfSubjectAccessReviewSpec{ ResourceAttributes: &authorizationv1.ResourceAttributes{ - Namespace: ns, - Group: res.Group, - Resource: res.Resource, + Namespace: ns, + Group: res.Group, + Resource: res.Resource, + Subresource: spec.SubResource(), }, }, } @@ -88,19 +92,22 @@ func makeSAR(ns, gvr string) *authorizationv1.SelfSubjectAccessReview { // CanI checks if user has access to a certain resource. func (a *APIClient) CanI(ns, gvr string, verbs []string) (bool, error) { + log.Debug().Msgf("AUTH %q:%q -- %v", ns, gvr, verbs) sar := makeSAR(ns, gvr) dial := a.DialOrDie().AuthorizationV1().SelfSubjectAccessReviews() for _, v := range verbs { sar.Spec.ResourceAttributes.Verb = v resp, err := dial.Create(sar) if err != nil { - log.Error().Err(err).Msgf("CanI") + log.Warn().Err(err).Msgf(" Dial Failed!") return false, err } if !resp.Status.Allowed { - return false, fmt.Errorf("%s access denied for current user on %q:%s", v, ns, gvr) + log.Debug().Msgf(" NO %q ;(", v) + return false, fmt.Errorf("`%s access denied for user on %q:%s", v, ns, gvr) } } + log.Debug().Msgf(" YES!") return true, nil } @@ -119,11 +126,6 @@ func (a *APIClient) ServerVersion() (*version.Info, error) { return discovery.ServerVersion() } -// FetchNodes returns all available nodes. -func (a *APIClient) FetchNodes() (*v1.NodeList, error) { - return a.DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{}) -} - // ValidNamespaces returns all available namespaces. func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) { nn, err := a.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{}) diff --git a/internal/client/gvr.go b/internal/client/gvr.go index a2aa7651..689db250 100644 --- a/internal/client/gvr.go +++ b/internal/client/gvr.go @@ -6,39 +6,75 @@ import ( "strings" "github.com/rs/zerolog/log" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" "vbom.ml/util/sortorder" ) // GVR represents a kubernetes resource schema as a string. // Format is group/version/resources -type GVR string +type GVR struct { + raw, g, v, r, sr string +} // NewGVR builds a new gvr from a group, version, resource. -func NewGVR(g, v, r string) GVR { - return GVR(path.Join(g, v, r)) +func NewGVR(gvr string) GVR { + var g, v, r, sr string + + tokens := strings.Split(gvr, ":") + raw := gvr + if len(tokens) == 2 { + raw, sr = tokens[0], tokens[1] + } + tokens = strings.Split(raw, "/") + switch len(tokens) { + case 3: + g, v, r = tokens[0], tokens[1], tokens[2] + case 2: + v, r = tokens[0], tokens[1] + case 1: + r = tokens[0] + default: + panic(fmt.Sprintf("can't parse GVR %q", gvr)) + } + + return GVR{raw: gvr, g: g, v: v, r: r, sr: sr} +} + +func NewGVRFromMeta(a metav1.APIResource) GVR { + return GVR{ + raw: path.Join(a.Group, a.Version, a.Name), + g: a.Group, + v: a.Version, + r: a.Name, + } } // FromGVAndR builds a gvr from a group/version and resource. func FromGVAndR(gv, r string) GVR { - return GVR(path.Join(gv, r)) + return NewGVR(path.Join(gv, r)) } -// ResName returns a resource . separated descriptor in the shape of kind.version.group. -func (g GVR) ResName() string { - return g.ToR() + "." + g.ToV() + "." + g.ToG() +// AsResourceName returns a resource . separated descriptor in the shape of kind.version.group. +func (g GVR) AsResourceName() string { + return g.r + "." + g.v + "." + g.g +} + +// SubResource returns a sub resource if available. +func (g GVR) SubResource() string { + return g.sr } // String returns gvr as string. func (g GVR) String() string { - return string(g) + return g.raw } // AsGV returns the group version scheme representation. func (g GVR) AsGV() schema.GroupVersion { return schema.GroupVersion{ - Group: g.ToG(), - Version: g.ToV(), + Group: g.g, + Version: g.v, } } @@ -53,41 +89,22 @@ func (g GVR) AsGVR() schema.GroupVersionResource { // ToV returns the resource version. func (g GVR) ToV() string { - tokens := strings.Split(string(g), "/") - if len(tokens) < 2 { - return "" - } - return tokens[len(tokens)-2] + return g.v } // ToRAndG returns the resource and group. func (g GVR) ToRAndG() (string, string) { - tokens := strings.Split(string(g), "/") - switch len(tokens) { - case 3: - return tokens[2], tokens[0] - case 2: - return tokens[1], "core" - default: - return tokens[0], "core" - } + return g.r, g.g } // ToR returns the resource name. func (g GVR) ToR() string { - tokens := strings.Split(string(g), "/") - return tokens[len(tokens)-1] + return g.r } // ToG returns the resource group name. func (g GVR) ToG() string { - tokens := strings.Split(string(g), "/") - switch len(tokens) { - case 3: - return tokens[0] - default: - return "" - } + return g.g } // GVRs represents a collection of gvr. diff --git a/internal/client/gvr_test.go b/internal/client/gvr_test.go index 471f3181..a5815809 100644 --- a/internal/client/gvr_test.go +++ b/internal/client/gvr_test.go @@ -1,6 +1,7 @@ package client_test import ( + "path" "sort" "testing" @@ -10,9 +11,17 @@ import ( ) func TestGVRSort(t *testing.T) { - gg := client.GVRs{"v1/pods", "v1/services", "apps/v1/deployments"} + gg := client.GVRs{ + client.NewGVR("v1/pods"), + client.NewGVR("v1/services"), + client.NewGVR("apps/v1/deployments"), + } sort.Sort(gg) - assert.Equal(t, client.GVRs{"v1/pods", "v1/services", "apps/v1/deployments"}, gg) + assert.Equal(t, client.GVRs{ + client.NewGVR("v1/pods"), + client.NewGVR("v1/services"), + client.NewGVR("apps/v1/deployments"), + }, gg) } func TestGVRCan(t *testing.T) { @@ -50,7 +59,7 @@ func TestAsGVR(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, client.GVR(u.gvr).AsGVR()) + assert.Equal(t, u.e, client.NewGVR(u.gvr).AsGVR()) }) } } @@ -68,7 +77,7 @@ func TestAsGV(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, client.GVR(u.gvr).AsGV()) + assert.Equal(t, u.e, client.NewGVR(u.gvr).AsGV()) }) } } @@ -85,12 +94,12 @@ func TestNewGVR(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, client.NewGVR(u.g, u.v, u.r).String()) + assert.Equal(t, u.e, client.NewGVR(path.Join(u.g, u.v, u.r)).String()) }) } } -func TestResName(t *testing.T) { +func TestGVRAsResourceName(t *testing.T) { uu := map[string]struct { gvr string e string @@ -104,7 +113,7 @@ func TestResName(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, client.GVR(u.gvr).ResName()) + assert.Equal(t, u.e, client.NewGVR(u.gvr).AsResourceName()) }) } } @@ -123,7 +132,7 @@ func TestToR(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, client.GVR(u.gvr).ToR()) + assert.Equal(t, u.e, client.NewGVR(u.gvr).ToR()) }) } } @@ -142,7 +151,7 @@ func TestToG(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, client.GVR(u.gvr).ToG()) + assert.Equal(t, u.e, client.NewGVR(u.gvr).ToG()) }) } } @@ -161,7 +170,7 @@ func TestToV(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.e, client.GVR(u.gvr).ToV()) + assert.Equal(t, u.e, client.NewGVR(u.gvr).ToV()) }) } } @@ -179,7 +188,7 @@ func TestToString(t *testing.T) { for k := range uu { u := uu[k] t.Run(k, func(t *testing.T) { - assert.Equal(t, u.gvr, client.GVR(u.gvr).String()) + assert.Equal(t, u.gvr, client.NewGVR(u.gvr).String()) }) } } diff --git a/internal/client/metrics.go b/internal/client/metrics.go index 14a57f57..e4f5061f 100644 --- a/internal/client/metrics.go +++ b/internal/client/metrics.go @@ -101,6 +101,11 @@ func (m *MetricsServer) ClusterLoad(nos *v1.NodeList, nmx *mv1beta1.NodeMetricsL // FetchNodesMetrics return all metrics for pods in a given namespace. func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) { + auth, err := m.CanI("", "metrics.k8s.io/v1beta1/nodes", []string{"list"}) + if !auth || err != nil { + return nil, err + } + client, err := m.MXDial() if err != nil { return nil, err @@ -111,6 +116,11 @@ func (m *MetricsServer) FetchNodesMetrics() (*mv1beta1.NodeMetricsList, error) { // FetchPodsMetrics return all metrics for pods in a given namespace. func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, error) { + auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", []string{"list"}) + if !auth || err != nil { + return &mv1beta1.PodMetricsList{}, err + } + client, err := m.MXDial() if err != nil { return nil, err @@ -121,6 +131,11 @@ func (m *MetricsServer) FetchPodsMetrics(ns string) (*mv1beta1.PodMetricsList, e // FetchPodMetrics return all metrics for pods in a given namespace. func (m *MetricsServer) FetchPodMetrics(ns, sel string) (*mv1beta1.PodMetrics, error) { + auth, err := m.CanI(ns, "metrics.k8s.io/v1beta1/pods", []string{"get"}) + if !auth || err != nil { + return nil, err + } + client, err := m.MXDial() if err != nil { return nil, err diff --git a/internal/config/mock_connection_test.go b/internal/config/mock_connection_test.go index 45572f04..e988afa5 100644 --- a/internal/config/mock_connection_test.go +++ b/internal/config/mock_connection_test.go @@ -135,25 +135,6 @@ func (mock *MockConnection) DynDialOrDie() dynamic.Interface { return ret0 } -func (mock *MockConnection) FetchNodes() (*v1.NodeList, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodes", params, []reflect.Type{reflect.TypeOf((**v1.NodeList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *v1.NodeList - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*v1.NodeList) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - func (mock *MockConnection) HasMetrics() bool { if mock == nil { panic("mock must not be nil. Use myMock := NewMockConnection().") @@ -478,23 +459,6 @@ func (c *MockConnection_DynDialOrDie_OngoingVerification) GetCapturedArguments() func (c *MockConnection_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierMockConnection) FetchNodes() *MockConnection_FetchNodes_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodes", params, verifier.timeout) - return &MockConnection_FetchNodes_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_FetchNodes_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_FetchNodes_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_FetchNodes_OngoingVerification) GetAllCapturedArguments() { -} - func (verifier *VerifierMockConnection) HasMetrics() *MockConnection_HasMetrics_OngoingVerification { params := []pegomock.Param{} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) diff --git a/internal/dao/alias.go b/internal/dao/alias.go index 6ecda108..a8a67cd9 100644 --- a/internal/dao/alias.go +++ b/internal/dao/alias.go @@ -29,9 +29,6 @@ func (a *Alias) Clear() { // Ensure makes sure alias are loaded. func (a *Alias) Ensure() (config.Alias, error) { - // if len(a.Alias) > 0 { - // return a.Alias, nil - // } if err := LoadResources(a.factory); err != nil { return config.Alias{}, err } @@ -51,12 +48,12 @@ func (a *Alias) load() error { if _, ok := a.Alias[meta.Kind]; ok { continue } - a.Define(string(gvr), strings.ToLower(meta.Kind), meta.Name) + a.Define(gvr.String(), strings.ToLower(meta.Kind), meta.Name) if meta.SingularName != "" { - a.Define(string(gvr), meta.SingularName) + a.Define(gvr.String(), meta.SingularName) } if meta.ShortNames != nil { - a.Define(string(gvr), meta.ShortNames...) + a.Define(gvr.String(), meta.ShortNames...) } } diff --git a/internal/dao/container.go b/internal/dao/container.go index 32a41f8b..43c2e1d6 100644 --- a/internal/dao/container.go +++ b/internal/dao/container.go @@ -6,7 +6,6 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" - "github.com/derailed/k9s/internal/watch" v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" @@ -24,11 +23,11 @@ var _ Loggable = &Container{} // TailLogs tails a given container logs func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error { - fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) + fac, ok := ctx.Value(internal.KeyFactory).(Factory) if !ok { return errors.New("Expecting an informer") } - o, err := fac.Get("v1/pods", opts.Path, labels.Everything()) + o, err := fac.Get("v1/pods", opts.Path, true, labels.Everything()) if err != nil { return err } @@ -42,7 +41,13 @@ 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 { +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"}) + if !auth || err != nil { + return nil, err + } + ns, n := client.Namespaced(path) - return c.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts) + return c.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts), nil } diff --git a/internal/dao/cronjob.go b/internal/dao/cronjob.go index 42a1a972..dc4bc04b 100644 --- a/internal/dao/cronjob.go +++ b/internal/dao/cronjob.go @@ -20,6 +20,11 @@ var _ Runnable = &CronJob{} // 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"}) + if !auth || err != nil { + return err + } + cj, err := c.Client().DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{}) if err != nil { return err diff --git a/internal/dao/describe.go b/internal/dao/describe.go index c59615f2..6fc54165 100644 --- a/internal/dao/describe.go +++ b/internal/dao/describe.go @@ -16,14 +16,13 @@ func Describe(c client.Connection, gvr client.GVR, ns, n string) (string, error) return "", err } - GVR := client.GVR(gvr) - gvk, err := m.KindFor(GVR.AsGVR()) + gvk, err := m.KindFor(gvr.AsGVR()) if err != nil { log.Error().Err(err).Msgf("No GVK for resource %s", gvr) return "", err } - mapping, err := mapper.ResourceFor(GVR.ResName(), gvk.Kind) + mapping, err := mapper.ResourceFor(gvr.AsResourceName(), gvk.Kind) if err != nil { log.Error().Err(err).Msgf("Unable to find mapper for %s %s", gvr, n) return "", err @@ -34,6 +33,5 @@ func Describe(c client.Connection, gvr client.GVR, ns, n string) (string, error) return "", err } - log.Debug().Msgf("DESCRIBE FOR %q -- %q:%q", gvr, ns, n) return d.Describe(ns, n, describe.DescriberSettings{ShowEvents: true}) } diff --git a/internal/dao/dp.go b/internal/dao/dp.go index 32753f95..eee7a911 100644 --- a/internal/dao/dp.go +++ b/internal/dao/dp.go @@ -6,7 +6,6 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" - "github.com/rs/zerolog/log" appsv1 "k8s.io/api/apps/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -28,8 +27,12 @@ var _ Scalable = &Deployment{} // Scale a Deployment. func (d *Deployment) Scale(path string, replicas int32) error { - log.Debug().Msgf("SCALING DEPLOYMENT!! %q:%d", path, replicas) ns, n := client.Namespaced(path) + auth, err := d.Client().CanI(ns, "apps/v1/deployments:scale", []string{"get", "update"}) + if !auth || err != nil { + return err + } + scale, err := d.Client().DialOrDie().AppsV1().Deployments(ns).GetScale(n, metav1.GetOptions{}) if err != nil { return err @@ -42,29 +45,33 @@ func (d *Deployment) Scale(path string, replicas int32) error { // Restart a Deployment rollout. func (d *Deployment) Restart(path string) error { - o, err := d.Get(string(d.gvr), path, labels.Everything()) + o, err := d.Get(d.gvr.String(), path, true, labels.Everything()) if err != nil { return err } - var ds appsv1.Deployment err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds) if err != nil { return err } + ns, _ := client.Namespaced(path) + auth, err := d.Client().CanI(ns, "apps/v1/deployments", []string{"patch"}) + if !auth || err != nil { + return err + } update, err := polymorphichelpers.ObjectRestarterFn(&ds) if err != nil { return err } - _, err = d.Client().DialOrDie().AppsV1().Deployments(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update) + return err } // TailLogs tail logs for all pods represented by this Deployment. func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := d.Get(string(d.gvr), opts.Path, labels.Everything()) + o, err := d.Get(d.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } @@ -74,7 +81,6 @@ func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOpti if err != nil { return errors.New("expecting Deployment resource") } - if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 { return fmt.Errorf("No valid selector found on Deployment %s", opts.Path) } diff --git a/internal/dao/ds.go b/internal/dao/ds.go index 198c674d..b74b58d4 100644 --- a/internal/dao/ds.go +++ b/internal/dao/ds.go @@ -31,33 +31,35 @@ var _ Restartable = &DaemonSet{} // Restart a DaemonSet rollout. func (d *DaemonSet) Restart(path string) error { - o, err := d.Get(string(d.gvr), path, labels.Everything()) + o, err := d.Get(d.gvr.String(), path, true, labels.Everything()) if err != nil { return err } - var ds appsv1.DaemonSet err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds) if err != nil { return err } + auth, err := d.Client().CanI(ds.Namespace, "apps/v1/daemonsets", []string{"patch"}) + if !auth || err != nil { + return err + } update, err := polymorphichelpers.ObjectRestarterFn(&ds) if err != nil { return err } - _, err = d.Client().DialOrDie().AppsV1().DaemonSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update) + return err } // TailLogs tail logs for all pods represented by this DaemonSet. func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := d.Get("apps/v1/daemonsets", opts.Path, labels.Everything()) + o, err := d.Get(d.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } - var ds appsv1.DaemonSet err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds) if err != nil { @@ -86,7 +88,7 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L } ns, _ := client.Namespaced(opts.Path) - oo, err := f.List("v1/pods", ns, lsel) + oo, err := f.List("v1/pods", ns, true, lsel) if err != nil { return err } @@ -96,7 +98,7 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L } po := Pod{} - po.Init(f, "v1/pods") + po.Init(f, client.NewGVR("v1/pods")) for _, o := range oo { var pod v1.Pod err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &pod) diff --git a/internal/dao/generic.go b/internal/dao/generic.go index 075828ec..d822bbad 100644 --- a/internal/dao/generic.go +++ b/internal/dao/generic.go @@ -20,12 +20,16 @@ func (g *Generic) Init(f Factory, gvr client.GVR) { // Delete a Generic. 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"}) + if !auth || err != nil { + return err + } + p := metav1.DeletePropagationOrphan if cascade { p = metav1.DeletePropagationBackground } - - ns, n := client.Namespaced(path) opts := metav1.DeleteOptions{PropagationPolicy: &p} if ns != "-" { return g.dynClient().Namespace(ns).Delete(n, &opts) diff --git a/internal/dao/helpers.go b/internal/dao/helpers.go index 6aa0c968..534a1de3 100644 --- a/internal/dao/helpers.go +++ b/internal/dao/helpers.go @@ -5,6 +5,8 @@ import ( "github.com/derailed/tview" runewidth "github.com/mattn/go-runewidth" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func toPerc(v1, v2 float64) float64 { @@ -18,3 +20,13 @@ func toPerc(v1, v2 float64) float64 { func Truncate(str string, width int) string { return runewidth.Truncate(str, width, string(tview.SemigraphicsHorizontalEllipsis)) } + +// FetchNodes returns a collection of nodes. +func FetchNodes(f Factory) (*v1.NodeList, error) { + auth, err := f.Client().CanI("", "v1/nodes", []string{"list"}) + if !auth || err != nil { + return nil, err + } + + return f.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{}) +} diff --git a/internal/dao/job.go b/internal/dao/job.go index 59ae23ea..e3f8a834 100644 --- a/internal/dao/job.go +++ b/internal/dao/job.go @@ -21,7 +21,7 @@ var _ Loggable = &Job{} // TailLogs tail logs for all pods represented by this Job. func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := j.Get(string(j.gvr), opts.Path, labels.Everything()) + o, err := j.Get(j.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } diff --git a/internal/dao/pod.go b/internal/dao/pod.go index 12bf38b7..1b3be251 100644 --- a/internal/dao/pod.go +++ b/internal/dao/pod.go @@ -32,14 +32,20 @@ var _ Accessor = &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 { +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"}) + if !auth || err != nil { + return nil, err + } + ns, n := client.Namespaced(path) - return p.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts) + return p.Client().DialOrDie().CoreV1().Pods(ns).GetLogs(n, opts), nil } // Containers returns all container names on pod func (p *Pod) Containers(path string, includeInit bool) ([]string, error) { - o, err := p.Get("v1/pod", path, labels.Everything()) + o, err := p.Get(p.gvr.String(), path, true, labels.Everything()) if err != nil { return nil, err } @@ -77,7 +83,7 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error if !ok { return errors.New("Expecting an informer") } - o, err := fac.Get("v1/pods", opts.Path, labels.Everything()) + o, err := fac.Get(p.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } @@ -119,7 +125,10 @@ func tailLogs(ctx context.Context, logger Logger, c chan<- string, opts LogOptio TailLines: &opts.Lines, Previous: opts.Previous, } - req := logger.Logs(opts.Path, &o) + req, err := logger.Logs(opts.Path, &o) + if err != nil { + return err + } ctxt, cancelFunc := context.WithCancel(ctx) req.Context(ctxt) diff --git a/internal/dao/port_forwarder.go b/internal/dao/port_forwarder.go index c6acf075..f166110a 100644 --- a/internal/dao/port_forwarder.go +++ b/internal/dao/port_forwarder.go @@ -92,6 +92,10 @@ 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"}) + if !auth || err != nil { + return nil, err + } pod, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{}) if err != nil { return nil, err @@ -100,6 +104,11 @@ 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"}) + if !auth || err != nil { + return nil, err + } + rcfg := p.RestConfigOrDie() rcfg.GroupVersion = &schema.GroupVersion{Group: "", Version: "v1"} rcfg.APIPath = "/api" diff --git a/internal/dao/portforward.go b/internal/dao/portforward.go index 64a3202b..da302f4c 100644 --- a/internal/dao/portforward.go +++ b/internal/dao/portforward.go @@ -1,5 +1,9 @@ package dao +import ( + "github.com/derailed/k9s/internal/client" +) + // PortForward represents a port forward dao. type PortForward struct { Generic @@ -10,6 +14,12 @@ var _ Nuker = &PortForward{} // 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"}) + if !auth || err != nil { + return err + } p.Factory.DeleteForwarder(path) + return nil } diff --git a/internal/dao/registry.go b/internal/dao/registry.go index a3ec4776..cb439465 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -25,19 +25,19 @@ var resMetas = ResourceMetas{} // Customize here for non resource types or types with metrics or logs. func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { m := Accessors{ - "contexts": &Context{}, - "containers": &Container{}, - "screendumps": &ScreenDump{}, - "benchmarks": &Benchmark{}, - "portforwards": &PortForward{}, - "v1/services": &Service{}, - "v1/pods": &Pod{}, - "apps/v1/deployments": &Deployment{}, - "apps/v1/daemonsets": &DaemonSet{}, - "extensions/v1beta1/daemonsets": &DaemonSet{}, - "apps/v1/statefulsets": &StatefulSet{}, - "batch/v1beta1/cronjobs": &CronJob{}, - "batch/v1/jobs": &Job{}, + client.NewGVR("contexts"): &Context{}, + client.NewGVR("containers"): &Container{}, + client.NewGVR("screendumps"): &ScreenDump{}, + client.NewGVR("benchmarks"): &Benchmark{}, + client.NewGVR("portforwards"): &PortForward{}, + client.NewGVR("v1/services"): &Service{}, + client.NewGVR("v1/pods"): &Pod{}, + client.NewGVR("apps/v1/deployments"): &Deployment{}, + client.NewGVR("apps/v1/daemonsets"): &DaemonSet{}, + client.NewGVR("extensions/v1beta1/daemonsets"): &DaemonSet{}, + client.NewGVR("apps/v1/statefulsets"): &StatefulSet{}, + client.NewGVR("batch/v1beta1/cronjobs"): &CronJob{}, + client.NewGVR("batch/v1/jobs"): &Job{}, } r, ok := m[gvr] @@ -52,7 +52,7 @@ func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { // RegisterMeta registers a new resource meta object. func RegisterMeta(gvr string, res metav1.APIResource) { - resMetas[client.GVR(gvr)] = res + resMetas[client.NewGVR(gvr)] = res } // AllGVRs returns all cluster resources. @@ -93,41 +93,39 @@ func LoadResources(f Factory) error { return err } loadNonResource(resMetas) + loadCRDs(f, resMetas) - if err := loadCRDs(f, resMetas); err != nil { - log.Warn().Err(err).Msgf("CRDs load failed!") - } return nil } // BOZO!! Need contermeasure for direct commands! func loadNonResource(m ResourceMetas) { - m["aliases"] = metav1.APIResource{ + m[client.NewGVR("aliases")] = metav1.APIResource{ Name: "aliases", Kind: "Aliases", Categories: []string{"k9s"}, } - m["contexts"] = metav1.APIResource{ + m[client.NewGVR("contexts")] = metav1.APIResource{ Name: "contexts", Kind: "Contexts", ShortNames: []string{"ctx"}, Categories: []string{"k9s"}, } - m["screendumps"] = metav1.APIResource{ + m[client.NewGVR("screendumps")] = metav1.APIResource{ Name: "screendumps", Kind: "ScreenDumps", ShortNames: []string{"sd"}, Verbs: []string{"delete"}, Categories: []string{"k9s"}, } - m["benchmarks"] = metav1.APIResource{ + m[client.NewGVR("benchmarks")] = metav1.APIResource{ Name: "benchmarks", Kind: "Benchmarks", ShortNames: []string{"be"}, Verbs: []string{"delete"}, Categories: []string{"k9s"}, } - m["portforwards"] = metav1.APIResource{ + m[client.NewGVR("portforwards")] = metav1.APIResource{ Name: "portforwards", Namespaced: true, Kind: "PortForwards", @@ -135,7 +133,7 @@ func loadNonResource(m ResourceMetas) { Verbs: []string{"delete"}, Categories: []string{"k9s"}, } - m["containers"] = metav1.APIResource{ + m[client.NewGVR("containers")] = metav1.APIResource{ Name: "containers", Kind: "Containers", Categories: []string{"k9s"}, @@ -145,23 +143,23 @@ func loadNonResource(m ResourceMetas) { } func loadRBAC(m ResourceMetas) { - m["rbac"] = metav1.APIResource{ + m[client.NewGVR("rbac")] = metav1.APIResource{ Name: "rbacs", Kind: "Rules", Categories: []string{"k9s"}, } - m["policy"] = metav1.APIResource{ + m[client.NewGVR("policy")] = metav1.APIResource{ Name: "policies", Kind: "Rules", Namespaced: true, Categories: []string{"k9s"}, } - m["users"] = metav1.APIResource{ + m[client.NewGVR("users")] = metav1.APIResource{ Name: "users", Kind: "User", Categories: []string{"k9s"}, } - m["groups"] = metav1.APIResource{ + m[client.NewGVR("groups")] = metav1.APIResource{ Name: "groups", Kind: "Group", Categories: []string{"k9s"}, @@ -188,17 +186,13 @@ func loadPreferred(f Factory, m ResourceMetas) error { return nil } -func loadCRDs(f Factory, m ResourceMetas) error { +func loadCRDs(f Factory, m ResourceMetas) { log.Debug().Msgf("Loading CRDs...") const crdGVR = "apiextensions.k8s.io/v1beta1/customresourcedefinitions" - _, err := f.CanForResource("", crdGVR, "list") - if err != nil { - return err - } - oo, err := f.List(crdGVR, "", labels.Everything()) + oo, err := f.List(crdGVR, "", true, labels.Everything()) if err != nil { log.Warn().Err(err).Msgf("Fail CRDs load") - return nil + return } log.Debug().Msgf(">>> CRDS count %d", len(oo)) @@ -208,11 +202,9 @@ func loadCRDs(f Factory, m ResourceMetas) error { log.Error().Err(errs[0]).Msgf("Fail to extract CRD meta (%d) errors", len(errs)) continue } - gvr := client.NewGVR(meta.Group, meta.Version, meta.Name) + gvr := client.NewGVRFromMeta(meta) m[gvr] = meta } - - return nil } func extractMeta(o runtime.Object) (metav1.APIResource, []error) { diff --git a/internal/dao/sts.go b/internal/dao/sts.go index 007e0c4e..490bd864 100644 --- a/internal/dao/sts.go +++ b/internal/dao/sts.go @@ -28,6 +28,10 @@ var _ Scalable = &StatefulSet{} // 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"}) + if !auth || err != nil { + return err + } scale, err := s.Client().DialOrDie().AppsV1().StatefulSets(ns).GetScale(n, metav1.GetOptions{}) if err != nil { return err @@ -40,29 +44,33 @@ func (s *StatefulSet) Scale(path string, replicas int32) error { // Restart a StatefulSet rollout. func (s *StatefulSet) Restart(path string) error { - o, err := s.Get(string(s.gvr), path, labels.Everything()) + o, err := s.Get(s.gvr.String(), path, true, labels.Everything()) if err != nil { return err } - var ds appsv1.StatefulSet err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ds) if err != nil { return err } + ns, _ := client.Namespaced(path) + auth, err := s.Client().CanI(ns, "apps/v1/statefulsets", []string{"patch"}) + if !auth || err != nil { + return err + } update, err := polymorphichelpers.ObjectRestarterFn(&ds) if err != nil { return err } - _, err = s.Client().DialOrDie().AppsV1().StatefulSets(ds.Namespace).Patch(ds.Name, types.StrategicMergePatchType, update) + return err } // TailLogs tail logs for all pods represented by this StatefulSet. func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := s.Get(string(s.gvr), opts.Path, labels.Everything()) + o, err := s.Get(s.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } @@ -72,7 +80,6 @@ func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOpt if err != nil { return errors.New("expecting StatefulSet resource") } - if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 { return fmt.Errorf("No valid selector found on StatefulSet %s", opts.Path) } diff --git a/internal/dao/svc.go b/internal/dao/svc.go index a4df9895..5eb7d134 100644 --- a/internal/dao/svc.go +++ b/internal/dao/svc.go @@ -21,7 +21,7 @@ var _ Loggable = &Service{} // TailLogs tail logs for all pods represented by this Service. func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { - o, err := s.Get(string(s.gvr), opts.Path, labels.Everything()) + o, err := s.Get(s.gvr.String(), opts.Path, true, labels.Everything()) if err != nil { return err } diff --git a/internal/dao/types.go b/internal/dao/types.go index 80a0db6c..fb41099d 100644 --- a/internal/dao/types.go +++ b/internal/dao/types.go @@ -18,16 +18,16 @@ type Factory interface { Client() client.Connection // Get fetch a given resource. - Get(gvr, path string, sel labels.Selector) (runtime.Object, error) + Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) // List fetch a collection of resources. - List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) + List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) // ForResource fetch an informer for a given resource. ForResource(ns, gvr string) informers.GenericInformer // CanForResource fetch an informer for a given resource if authorized - CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error) + CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) // WaitForCacheSync synchronize the cache. WaitForCacheSync() @@ -86,5 +86,5 @@ type Runnable interface { // Logger represents a resource that exposes logs. type Logger interface { // Logs tails a resource logs. - Logs(path string, opts *v1.PodLogOptions) *restclient.Request + Logs(path string, opts *v1.PodLogOptions) (*restclient.Request, error) } diff --git a/internal/model/alias_test.go b/internal/model/alias_test.go index aa886515..1c76f45b 100644 --- a/internal/model/alias_test.go +++ b/internal/model/alias_test.go @@ -60,28 +60,29 @@ func makeAliases() *dao.Alias { type testFactory struct{} -var _ model.Factory = testFactory{} +var _ dao.Factory = testFactory{} func (f testFactory) Client() client.Connection { return nil } -func (f testFactory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) { +func (f testFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) { return nil, nil } -func (f testFactory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) { +func (f testFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) { return nil, nil } func (f testFactory) ForResource(ns, gvr string) informers.GenericInformer { return nil } -func (f testFactory) CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error) { +func (f testFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) { return nil, nil } func (f testFactory) WaitForCacheSync() {} func (f testFactory) Forwarders() watch.Forwarders { return nil } +func (f testFactory) DeleteForwarder(string) {} -func makeFactory() model.Factory { +func makeFactory() dao.Factory { return testFactory{} } diff --git a/internal/model/container.go b/internal/model/container.go index 899955a7..5e10f306 100644 --- a/internal/model/container.go +++ b/internal/model/container.go @@ -31,7 +31,7 @@ func (c *Container) List(ctx context.Context) ([]runtime.Object, error) { } ns, _ := render.Namespaced(path) c.namespace = ns - o, err := c.factory.Get("v1/pods", path, labels.Everything()) + o, err := c.factory.Get("v1/pods", path, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/model/container_test.go b/internal/model/container_test.go index 018a5733..5474a07e 100644 --- a/internal/model/container_test.go +++ b/internal/model/container_test.go @@ -6,6 +6,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/watch" @@ -47,29 +48,30 @@ func TestContainerHydrate(t *testing.T) { type podFactory struct{} -var _ model.Factory = testFactory{} +var _ dao.Factory = testFactory{} func (f podFactory) Client() client.Connection { return nil } -func (f podFactory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) { +func (f podFactory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) { var m map[string]interface{} if err := yaml.Unmarshal([]byte(poYaml()), &m); err != nil { return nil, err } return &unstructured.Unstructured{Object: m}, nil } -func (f podFactory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) { +func (f podFactory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) { return nil, nil } func (f podFactory) ForResource(ns, gvr string) informers.GenericInformer { return nil } -func (f podFactory) CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error) { +func (f podFactory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) { return nil, nil } func (f podFactory) WaitForCacheSync() {} func (f podFactory) Forwarders() watch.Forwarders { return nil } +func (f podFactory) DeleteForwarder(string) {} -func makePodFactory() model.Factory { +func makePodFactory() dao.Factory { return podFactory{} } diff --git a/internal/model/crd.go b/internal/model/crd.go index bae45d55..7493c9b2 100644 --- a/internal/model/crd.go +++ b/internal/model/crd.go @@ -21,8 +21,8 @@ func (c *CustomResourceDefinition) List(ctx context.Context) ([]runtime.Object, lsel = sel.AsSelector() } - gvr := "apiextensions.k8s.io/v1beta1/customresourcedefinitions" - oo, err := c.factory.List(gvr, "-", lsel) + const gvr = "apiextensions.k8s.io/v1beta1/customresourcedefinitions" + oo, err := c.factory.List(gvr, "-", true, lsel) if err != nil { return nil, err } diff --git a/internal/model/generic.go b/internal/model/generic.go index 6c837134..5070a9da 100644 --- a/internal/model/generic.go +++ b/internal/model/generic.go @@ -26,12 +26,12 @@ type Generic struct { // List returns a collection of node resources. func (g *Generic) List(ctx context.Context) ([]runtime.Object, error) { // Ensures the factory is tracking this resource - _, err := g.factory.CanForResource(g.namespace, g.gvr) + _, err := g.factory.CanForResource(g.namespace, g.gvr, []string{"list"}) if err != nil { return nil, err } - gvr := client.GVR(g.gvr) + gvr := client.NewGVR(g.gvr) fcodec, codec := g.codec(gvr.AsGV()) c, err := g.client(fcodec, gvr) diff --git a/internal/model/hpa.go b/internal/model/hpa.go index c0389398..31b8e3ed 100644 --- a/internal/model/hpa.go +++ b/internal/model/hpa.go @@ -41,7 +41,7 @@ func (h *HorizontalPodAutoscaler) List(ctx context.Context) ([]runtime.Object, e } func (h *HorizontalPodAutoscaler) list(gvr string, sel labels.Selector) ([]runtime.Object, error) { - oo, err := h.factory.List(gvr, h.namespace, sel) + oo, err := h.factory.List(gvr, h.namespace, true, sel) if err != nil { return nil, err } diff --git a/internal/model/mock_clustermeta_test.go b/internal/model/mock_clustermeta_test.go index 4ce0a3cf..cf5426c2 100644 --- a/internal/model/mock_clustermeta_test.go +++ b/internal/model/mock_clustermeta_test.go @@ -173,25 +173,6 @@ func (mock *MockClusterMeta) DynDialOrDie() dynamic.Interface { return ret0 } -func (mock *MockClusterMeta) FetchNodes() (*v1.NodeList, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodes", params, []reflect.Type{reflect.TypeOf((**v1.NodeList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *v1.NodeList - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*v1.NodeList) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - func (mock *MockClusterMeta) GetNodes() (*v1.NodeList, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockClusterMeta().") @@ -607,23 +588,6 @@ func (c *MockClusterMeta_DynDialOrDie_OngoingVerification) GetCapturedArguments( func (c *MockClusterMeta_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierMockClusterMeta) FetchNodes() *MockClusterMeta_FetchNodes_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodes", params, verifier.timeout) - return &MockClusterMeta_FetchNodes_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_FetchNodes_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_FetchNodes_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_FetchNodes_OngoingVerification) GetAllCapturedArguments() { -} - func (verifier *VerifierMockClusterMeta) GetNodes() *MockClusterMeta_GetNodes_OngoingVerification { params := []pegomock.Param{} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "GetNodes", params, verifier.timeout) diff --git a/internal/model/mock_connection_test.go b/internal/model/mock_connection_test.go index d4d4dec6..f60e861f 100644 --- a/internal/model/mock_connection_test.go +++ b/internal/model/mock_connection_test.go @@ -135,25 +135,6 @@ func (mock *MockConnection) DynDialOrDie() dynamic.Interface { return ret0 } -func (mock *MockConnection) FetchNodes() (*v1.NodeList, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodes", params, []reflect.Type{reflect.TypeOf((**v1.NodeList)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 *v1.NodeList - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(*v1.NodeList) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - func (mock *MockConnection) HasMetrics() bool { if mock == nil { panic("mock must not be nil. Use myMock := NewMockConnection().") @@ -478,23 +459,6 @@ func (c *MockConnection_DynDialOrDie_OngoingVerification) GetCapturedArguments() func (c *MockConnection_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierMockConnection) FetchNodes() *MockConnection_FetchNodes_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodes", params, verifier.timeout) - return &MockConnection_FetchNodes_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_FetchNodes_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_FetchNodes_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_FetchNodes_OngoingVerification) GetAllCapturedArguments() { -} - func (verifier *VerifierMockConnection) HasMetrics() *MockConnection_HasMetrics_OngoingVerification { params := []pegomock.Param{} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) diff --git a/internal/model/node.go b/internal/model/node.go index 2df9ae80..68a2eb23 100644 --- a/internal/model/node.go +++ b/internal/model/node.go @@ -5,10 +5,10 @@ import ( "fmt" "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -24,7 +24,7 @@ type Node struct { // List returns a collection of node resources. func (n *Node) List(_ context.Context) ([]runtime.Object, error) { - nn, err := n.factory.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{}) + nn, err := dao.FetchNodes(n.factory) if err != nil { return nil, err } @@ -37,6 +37,7 @@ func (n *Node) List(_ context.Context) ([]runtime.Object, error) { } oo[i] = &unstructured.Unstructured{Object: o} } + return oo, nil } @@ -99,8 +100,8 @@ func nodeMetricsFor(o runtime.Object, mmx *mv1beta1.NodeMetricsList) *mv1beta1.N return nil } -func (n *Node) nodePods(f Factory, node string) ([]*v1.Pod, error) { - pp, err := f.List("v1/pods", render.AllNamespaces, labels.Everything()) +func (n *Node) nodePods(f dao.Factory, node string) ([]*v1.Pod, error) { + pp, err := f.List("v1/pods", render.AllNamespaces, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/model/policy.go b/internal/model/policy.go index 41145d9d..e0b450d3 100644 --- a/internal/model/policy.go +++ b/internal/model/policy.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" rbacv1 "k8s.io/api/rbac/v1" @@ -117,8 +118,8 @@ func (p *Policy) loadRoleBinding(kind, name string) (render.Policies, error) { return rows, nil } -func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) { - oo, err := f.List("rbac.authorization.k8s.io/v1/clusterrolebindings", render.ClusterScope, labels.Everything()) +func fetchClusterRoleBindings(f dao.Factory) ([]rbacv1.ClusterRoleBinding, error) { + oo, err := f.List(crbGVR, render.ClusterScope, true, labels.Everything()) if err != nil { return nil, err } @@ -135,8 +136,8 @@ func fetchClusterRoleBindings(f Factory) ([]rbacv1.ClusterRoleBinding, error) { return crbs, nil } -func fetchRoleBindings(f Factory) ([]rbacv1.RoleBinding, error) { - oo, err := f.List("rbac.authorization.k8s.io/v1/rolebindings", render.ClusterScope, labels.Everything()) +func fetchRoleBindings(f dao.Factory) ([]rbacv1.RoleBinding, error) { + oo, err := f.List(rbGVR, render.ClusterScope, true, labels.Everything()) if err != nil { return nil, err } @@ -173,7 +174,7 @@ func (p *Policy) fetchRoleBindingSubjects(kind, name string) ([]string, error) { func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) { const gvr = "rbac.authorization.k8s.io/v1/clusterroles" - oo, err := p.factory.List(gvr, render.ClusterScope, labels.Everything()) + oo, err := p.factory.List(gvr, render.ClusterScope, true, labels.Everything()) if err != nil { return nil, err } @@ -193,7 +194,7 @@ func (p *Policy) fetchClusterRoles() ([]rbacv1.ClusterRole, error) { func (p *Policy) fetchRoles() ([]rbacv1.Role, error) { const gvr = "rbac.authorization.k8s.io/v1/roles" - oo, err := p.factory.List(gvr, render.AllNamespaces, labels.Everything()) + oo, err := p.factory.List(gvr, render.AllNamespaces, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/model/rbac.go b/internal/model/rbac.go index 9bf89b8c..b29f673c 100644 --- a/internal/model/rbac.go +++ b/internal/model/rbac.go @@ -37,7 +37,8 @@ func (r *Rbac) List(ctx context.Context) ([]runtime.Object, error) { return r.Resource.List(ctx) } - switch client.GVR(r.gvr).ToR() { + res := client.NewGVR(r.gvr) + switch res.ToR() { case "clusterrolebindings": return r.loadClusterRoleBinding(path) case "rolebindings": @@ -47,12 +48,12 @@ func (r *Rbac) List(ctx context.Context) ([]runtime.Object, error) { case "roles": return r.loadRole(path) default: - return nil, fmt.Errorf("expecting clusterrole/role but found %s", client.GVR(r.gvr).ToR()) + return nil, fmt.Errorf("expecting clusterrole/role but found %s", res.ToR()) } } func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { - o, err := r.factory.Get(crbGVR, path, labels.Everything()) + o, err := r.factory.Get(crbGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -63,7 +64,7 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { return nil, err } - crbo, err := r.factory.Get(crGVR, client.FQN("-", crb.RoleRef.Name), labels.Everything()) + crbo, err := r.factory.Get(crGVR, client.FQN("-", crb.RoleRef.Name), true, labels.Everything()) if err != nil { return nil, err } @@ -77,7 +78,7 @@ func (r *Rbac) loadClusterRoleBinding(path string) ([]runtime.Object, error) { } func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { - o, err := r.factory.Get(rbGVR, path, labels.Everything()) + o, err := r.factory.Get(rbGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -88,7 +89,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { } if rb.RoleRef.Kind == "ClusterRole" { - o, e := r.factory.Get(crGVR, client.FQN("-", rb.RoleRef.Name), labels.Everything()) + o, e := r.factory.Get(crGVR, client.FQN("-", rb.RoleRef.Name), true, labels.Everything()) if e != nil { return nil, e } @@ -100,7 +101,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { return asRuntimeObjects(parseRules(render.ClusterScope, "-", cr.Rules)), nil } - ro, err := r.factory.Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), labels.Everything()) + ro, err := r.factory.Get(rGVR, client.FQN(rb.Namespace, rb.RoleRef.Name), true, labels.Everything()) if err != nil { return nil, err } @@ -114,7 +115,7 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) { } func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { - o, err := r.factory.Get(crGVR, path, labels.Everything()) + o, err := r.factory.Get(crGVR, path, true, labels.Everything()) if err != nil { return nil, err } @@ -129,7 +130,7 @@ func (r *Rbac) loadClusterRole(path string) ([]runtime.Object, error) { } func (r *Rbac) loadRole(path string) ([]runtime.Object, error) { - o, err := r.factory.Get(rGVR, path, labels.Everything()) + o, err := r.factory.Get(rGVR, path, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/model/resource.go b/internal/model/resource.go index af485171..7bc21aa9 100644 --- a/internal/model/resource.go +++ b/internal/model/resource.go @@ -4,6 +4,7 @@ import ( "context" "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -12,11 +13,11 @@ import ( // Resource represents a generic resource model. type Resource struct { namespace, gvr string - factory Factory + factory dao.Factory } // Init initializes the model. -func (r *Resource) Init(ns, gvr string, f Factory) { +func (r *Resource) Init(ns, gvr string, f dao.Factory) { r.namespace, r.gvr, r.factory = ns, gvr, f } @@ -28,7 +29,7 @@ func (r *Resource) List(ctx context.Context) ([]runtime.Object, error) { lsel = sel.AsSelector() } - return r.factory.List(r.gvr, r.namespace, lsel) + return r.factory.List(r.gvr, r.namespace, true, lsel) } // Hydrate renders all rows. diff --git a/internal/model/table.go b/internal/model/table.go index 98e6b26f..2e6a2316 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -7,13 +7,14 @@ import ( "time" "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/runtime" ) const ( - refreshRate = 1 * time.Second + refreshRate = 2 * time.Second noDataCount = 2 ) @@ -159,17 +160,17 @@ func (t *Table) fireTableLoadFailed(err error) { } func (t *Table) list(ctx context.Context, l Lister) ([]runtime.Object, error) { - factory, ok := ctx.Value(internal.KeyFactory).(Factory) + factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory) if !ok { return nil, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory)) } - l.Init(t.namespace, string(t.gvr), factory) + l.Init(t.namespace, t.gvr, factory) return l.List(ctx) } func (t *Table) reconcile(ctx context.Context) error { - meta, ok := Registry[string(t.gvr)] + meta, ok := Registry[t.gvr] if !ok { log.Debug().Msgf("Resource %s not found in registry. Going generic!", t.gvr) meta = ResourceMeta{ diff --git a/internal/model/types.go b/internal/model/types.go index 3bbd594b..e7576bde 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -3,13 +3,10 @@ package model import ( "context" - "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/render" - "github.com/derailed/k9s/internal/watch" "github.com/derailed/tview" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/informers" ) // Igniter represents a runnable view. @@ -60,7 +57,7 @@ type Renderer interface { // Lister represents a resource lister. type Lister interface { // Init initializes a resource. - Init(ns, gvr string, f Factory) + Init(ns, gvr string, f dao.Factory) // List returns a collection of resources. List(context.Context) ([]runtime.Object, error) @@ -69,30 +66,6 @@ type Lister interface { Hydrate(oo []runtime.Object, rr render.Rows, r Renderer) error } -// Factory represents a K8s resource factory. -type Factory interface { - // Client retrieves an api client. - Client() client.Connection - - // Get fetch a given resource. - Get(gvr, path string, sel labels.Selector) (runtime.Object, error) - - // List fetch a collection of resources. - List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) - - // ForResource fetch an informer for a given resource. - ForResource(ns, gvr string) informers.GenericInformer - - // CanForResource fetch an informer for a given resource. - CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error) - - // WaitForCacheSync synchronize the cache. - WaitForCacheSync() - - // Forwards returns all portforwards. - Forwarders() watch.Forwarders -} - // ResourceMeta represents model info about a resource. type ResourceMeta struct { Model Lister diff --git a/internal/render/alias.go b/internal/render/alias.go index dce1edc0..54960111 100644 --- a/internal/render/alias.go +++ b/internal/render/alias.go @@ -38,7 +38,7 @@ func (Alias) Render(o interface{}, ns string, r *Row) error { } r.ID = a.GVR - gvr := client.GVR(a.GVR) + gvr := client.NewGVR(a.GVR) res, grp := gvr.ToRAndG() r.Fields = append(r.Fields, res, diff --git a/internal/ui/table.go b/internal/ui/table.go index c7cf8131..a4f1dd8c 100644 --- a/internal/ui/table.go +++ b/internal/ui/table.go @@ -283,6 +283,7 @@ func (t *Table) ClearMarks() { // Refresh update the table data. func (t *Table) Refresh() { + // BOZO!! Really want to tell model reload now. Refactor! t.Update(t.model.Peek()) } diff --git a/internal/view/alias_test.go b/internal/view/alias_test.go index 9a44ff4b..31e64c15 100644 --- a/internal/view/alias_test.go +++ b/internal/view/alias_test.go @@ -18,7 +18,7 @@ import ( ) func TestAliasNew(t *testing.T) { - v := view.NewAlias(client.GVR("aliases")) + v := view.NewAlias(client.NewGVR("aliases")) assert.Nil(t, v.Init(makeContext())) assert.Equal(t, "Aliases", v.Name()) @@ -26,7 +26,7 @@ func TestAliasNew(t *testing.T) { } func TestAliasSearch(t *testing.T) { - v := view.NewAlias(client.GVR("aliases")) + v := view.NewAlias(client.NewGVR("aliases")) assert.Nil(t, v.Init(makeContext())) v.GetTable().SetModel(&testModel{}) v.GetTable().SearchBuff().SetActive(true) @@ -39,7 +39,7 @@ func TestAliasSearch(t *testing.T) { } func TestAliasGoto(t *testing.T) { - v := view.NewAlias(client.GVR("aliases")) + v := view.NewAlias(client.NewGVR("aliases")) assert.Nil(t, v.Init(makeContext())) v.GetTable().Select(0, 0) diff --git a/internal/view/app.go b/internal/view/app.go index fef20bda..ee01bfda 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -404,7 +404,7 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - if err := a.inject(NewAlias("aliases")); err != nil { + if err := a.inject(NewAlias(client.NewGVR("aliases"))); err != nil { a.Flash().Err(err) } diff --git a/internal/view/browser.go b/internal/view/browser.go index eccf6e06..ab3be1d4 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -50,7 +50,8 @@ func (b *Browser) Init(ctx context.Context) error { return err } if !dao.IsK9sMeta(b.meta) { - if _, e := b.app.factory.CanForResource(b.app.Config.ActiveNamespace(), b.GVR()); e != nil { + log.Debug().Msgf("BROWSER ACTIVE_NS %q", b.app.Config.ActiveNamespace()) + if _, e := b.app.factory.CanForResource(b.app.Config.ActiveNamespace(), b.GVR(), []string{"list", "watch"}); e != nil { return e } } @@ -196,7 +197,7 @@ func (b *Browser) enterCmd(evt *tcell.EventKey) *tcell.EventKey { if b.enterFn != nil { f = b.enterFn } - f(b.app, b.GetModel().GetNamespace(), string(b.gvr), path) + f(b.app, b.GetModel().GetNamespace(), b.gvr.String(), path) return nil } @@ -236,7 +237,7 @@ func (b *Browser) describeCmd(evt *tcell.EventKey) *tcell.EventKey { if path == "" { return evt } - describeResource(b.app, b.GetModel().GetNamespace(), string(b.gvr), path) + describeResource(b.app, b.GetModel().GetNamespace(), b.gvr.String(), path) return nil } @@ -319,7 +320,7 @@ func (b *Browser) defaultContext() context.Context { ctx := context.Background() ctx = context.WithValue(ctx, internal.KeyFactory, b.app.factory) - ctx = context.WithValue(ctx, internal.KeyGVR, string(b.gvr)) + ctx = context.WithValue(ctx, internal.KeyGVR, b.gvr.String()) ctx = context.WithValue(ctx, internal.KeyPath, b.Path) ctx = context.WithValue(ctx, internal.KeyLabels, "") diff --git a/internal/view/cluster_info.go b/internal/view/cluster_info.go index dbf116c8..1b6d0ab6 100644 --- a/internal/view/cluster_info.go +++ b/internal/view/cluster_info.go @@ -5,6 +5,7 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" @@ -12,7 +13,6 @@ import ( "github.com/gdamore/tcell" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) @@ -142,7 +142,7 @@ func (c *ClusterInfo) updateStyle() { } func fetchResources(app *App) (*v1.NodeList, *mv1beta1.NodeMetricsList, error) { - nos, err := app.factory.Client().DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{}) + nn, err := dao.FetchNodes(app.factory) if err != nil { return nil, nil, err } @@ -153,7 +153,7 @@ func fetchResources(app *App) (*v1.NodeList, *mv1beta1.NodeMetricsList, error) { return nil, nil, err } - return nos, nmx, nil + return nn, nmx, nil } func (c *ClusterInfo) refreshMetrics(cluster *model.Cluster, row int) { diff --git a/internal/view/command.go b/internal/view/command.go index e2c1a663..b9b6345f 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -121,7 +121,7 @@ func (c *Command) viewMetaFor(cmd string) (string, *MetaViewer, error) { return "", nil, fmt.Errorf("Huh? `%s` Command not found", cmd) } - v, ok := customViewers[client.GVR(gvr)] + v, ok := customViewers[client.NewGVR(gvr)] if !ok { return gvr, &MetaViewer{viewerFn: NewBrowser}, nil } @@ -133,10 +133,10 @@ func (c *Command) componentFor(gvr string, v *MetaViewer) ResourceViewer { var view ResourceViewer if v.viewerFn != nil { log.Debug().Msgf("Custom viewer for %s", gvr) - view = v.viewerFn(client.GVR(gvr)) + view = v.viewerFn(client.NewGVR(gvr)) } else { log.Debug().Msgf("Generic viewer for %s", gvr) - view = NewBrowser(client.GVR(gvr)) + view = NewBrowser(client.NewGVR(gvr)) } if v.enterFn != nil { @@ -152,7 +152,7 @@ func (c *Command) exec(gvr string, comp model.Component, clearStack bool) error return fmt.Errorf("No component given for %s", gvr) } - g := client.GVR(gvr) + g := client.NewGVR(gvr) c.app.Flash().Infof("Viewing %s resource...", g.ToR()) log.Debug().Msgf("Running Command %s", gvr) c.app.Config.SetActiveView(g.ToR()) diff --git a/internal/view/container_test.go b/internal/view/container_test.go index 25469471..80f8de8b 100644 --- a/internal/view/container_test.go +++ b/internal/view/container_test.go @@ -9,7 +9,7 @@ import ( ) func TestContainerNew(t *testing.T) { - c := view.NewContainer(client.GVR("containers")) + c := view.NewContainer(client.NewGVR("containers")) assert.Nil(t, c.Init(makeCtx())) assert.Equal(t, "Containers", c.Name()) diff --git a/internal/view/context.go b/internal/view/context.go index eb2409c2..d62b36d7 100644 --- a/internal/view/context.go +++ b/internal/view/context.go @@ -44,7 +44,7 @@ func (c *Context) useCtx(app *App, _, res, path string) { } func (c *Context) useContext(name string) error { - res, err := dao.AccessorFor(c.App().factory, client.GVR(c.GVR())) + res, err := dao.AccessorFor(c.App().factory, client.NewGVR(c.GVR())) if err != nil { return nil } diff --git a/internal/view/context_test.go b/internal/view/context_test.go index 37960059..ebefd197 100644 --- a/internal/view/context_test.go +++ b/internal/view/context_test.go @@ -9,7 +9,7 @@ import ( ) func TestContext(t *testing.T) { - ctx := view.NewContext(client.GVR("contexts")) + ctx := view.NewContext(client.NewGVR("contexts")) assert.Nil(t, ctx.Init(makeCtx())) assert.Equal(t, "Contexts", ctx.Name()) diff --git a/internal/view/cronjob.go b/internal/view/cronjob.go index ca6d9ae9..56ac377f 100644 --- a/internal/view/cronjob.go +++ b/internal/view/cronjob.go @@ -34,7 +34,7 @@ func NewCronJob(gvr client.GVR) ResourceViewer { func (c *CronJob) showJobs(app *App, ns, gvr, path string) { log.Debug().Msgf("Showing Jobs %q:%q -- %q", ns, gvr, path) - o, err := app.factory.Get(gvr, path, labels.Everything()) + o, err := app.factory.Get(gvr, path, true, labels.Everything()) if err != nil { app.Flash().Err(err) return @@ -47,7 +47,7 @@ func (c *CronJob) showJobs(app *App, ns, gvr, path string) { return } - v := NewJob(client.GVR("batch/v1/jobs")) + v := NewJob(client.NewGVR("batch/v1/jobs")) v.SetContextFn(jobCtx(path, string(cj.UID))) if err := app.inject(v); err != nil { app.Flash().Err(err) @@ -73,7 +73,7 @@ func (c *CronJob) trigger(evt *tcell.EventKey) *tcell.EventKey { return evt } - res, err := dao.AccessorFor(c.App().factory, client.GVR(c.GVR())) + res, err := dao.AccessorFor(c.App().factory, client.NewGVR(c.GVR())) if err != nil { return nil } diff --git a/internal/view/dp.go b/internal/view/dp.go index 8f523474..7b70f9f2 100644 --- a/internal/view/dp.go +++ b/internal/view/dp.go @@ -43,7 +43,7 @@ func (d *Deploy) bindKeys(aa ui.KeyActions) { } func (d *Deploy) showPods(app *App, _, _, path string) { - o, err := app.factory.Get(d.GVR(), path, labels.Everything()) + o, err := app.factory.Get(d.GVR(), path, true, labels.Everything()) if err != nil { app.Flash().Err(err) return diff --git a/internal/view/dp_test.go b/internal/view/dp_test.go index 9f766734..6adde7f7 100644 --- a/internal/view/dp_test.go +++ b/internal/view/dp_test.go @@ -9,7 +9,7 @@ import ( ) func TestDeploy(t *testing.T) { - v := view.NewDeploy(client.GVR("apps/v1/deployments")) + v := view.NewDeploy(client.NewGVR("apps/v1/deployments")) assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "Deployments", v.Name()) diff --git a/internal/view/ds.go b/internal/view/ds.go index f807cb9a..a75b8998 100644 --- a/internal/view/ds.go +++ b/internal/view/ds.go @@ -42,7 +42,7 @@ func (d *DaemonSet) bindKeys(aa ui.KeyActions) { } func (d *DaemonSet) showPods(app *App, _, _, path string) { - o, err := app.factory.Get(d.GVR(), path, labels.Everything()) + o, err := app.factory.Get(d.GVR(), path, true, labels.Everything()) if err != nil { d.App().Flash().Err(err) return diff --git a/internal/view/ds_test.go b/internal/view/ds_test.go index fe46b3c2..762ad2b1 100644 --- a/internal/view/ds_test.go +++ b/internal/view/ds_test.go @@ -9,7 +9,7 @@ import ( ) func TestDaemonSet(t *testing.T) { - v := view.NewDaemonSet(client.GVR("apps/v1/daemonsets")) + v := view.NewDaemonSet(client.NewGVR("apps/v1/daemonsets")) assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "DaemonSets", v.Name()) diff --git a/internal/view/help.go b/internal/view/help.go index 1ca13e12..33aad1ec 100644 --- a/internal/view/help.go +++ b/internal/view/help.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" @@ -34,7 +35,7 @@ type Help struct { // NewHelp returns a new help viewer. func NewHelp() *Help { return &Help{ - Table: NewTable(helpTitle), + Table: NewTable(client.NewGVR("help")), } } diff --git a/internal/view/help_test.go b/internal/view/help_test.go index e65adb8f..03e13962 100644 --- a/internal/view/help_test.go +++ b/internal/view/help_test.go @@ -14,7 +14,7 @@ func TestHelp(t *testing.T) { ctx := makeCtx() app := ctx.Value(internal.KeyApp).(*view.App) - po := view.NewPod(client.GVR("v1/pods")) + po := view.NewPod(client.NewGVR("v1/pods")) po.Init(ctx) app.Content.Push(po) diff --git a/internal/view/helpers.go b/internal/view/helpers.go index c79ee771..cee13f83 100644 --- a/internal/view/helpers.go +++ b/internal/view/helpers.go @@ -62,7 +62,7 @@ func defaultK9sEnv(app *App, sel string, row render.Row) K9sEnv { func describeResource(app *App, _, gvr, path string) { ns, n := client.Namespaced(path) - yaml, err := dao.Describe(app.Conn(), client.GVR(gvr), ns, n) + yaml, err := dao.Describe(app.Conn(), client.NewGVR(gvr), ns, n) if err != nil { app.Flash().Errf("Describe command failed: %s", err) return @@ -100,7 +100,7 @@ func showPods(app *App, path, labelSel, fieldSel string) { log.Debug().Msgf("SHOW PODS %q -- %q -- %q", path, labelSel, fieldSel) app.switchNS("") - v := NewPod(client.GVR("v1/pods")) + v := NewPod(client.NewGVR("v1/pods")) v.SetContextFn(podCtx(path, labelSel, fieldSel)) v.GetTable().SetColorerFn(render.Pod{}.ColorerFunc()) diff --git a/internal/view/job.go b/internal/view/job.go index ce4b2bff..4789db38 100644 --- a/internal/view/job.go +++ b/internal/view/job.go @@ -24,7 +24,7 @@ func NewJob(gvr client.GVR) ResourceViewer { } func (*Job) showPods(app *App, _, gvr, path string) { - o, err := app.factory.Get(gvr, path, labels.Everything()) + o, err := app.factory.Get(gvr, path, true, labels.Everything()) if err != nil { app.Flash().Err(err) return diff --git a/internal/view/log_test.go b/internal/view/log_test.go index f8fe5a77..502e2dc3 100644 --- a/internal/view/log_test.go +++ b/internal/view/log_test.go @@ -28,7 +28,7 @@ func TestLogAnsi(t *testing.T) { } func TestLogFlush(t *testing.T) { - v := NewLog(client.GVR("v1/pods"), "fred/p1", "blee", false) + v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false) v.Init(makeContext()) v.Flush(2, []string{"blee", "bozo"}) @@ -41,7 +41,7 @@ func TestLogFlush(t *testing.T) { } func TestLogViewSave(t *testing.T) { - v := NewLog(client.GVR("v1/pods"), "fred/p1", "blee", false) + v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false) v.Init(makeContext()) app := makeApp() @@ -55,7 +55,7 @@ func TestLogViewSave(t *testing.T) { } func TestLogViewNav(t *testing.T) { - v := NewLog(client.GVR("v1/pods"), "fred/p1", "blee", false) + v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false) v.Init(makeContext()) var buff []string @@ -70,7 +70,7 @@ func TestLogViewNav(t *testing.T) { } func TestLogViewClear(t *testing.T) { - v := NewLog(client.GVR("v1/pods"), "fred/p1", "blee", false) + v := NewLog(client.NewGVR("v1/pods"), "fred/p1", "blee", false) v.Init(makeContext()) v.Flush(2, []string{"blee", "bozo"}) diff --git a/internal/view/logs_extender.go b/internal/view/logs_extender.go index 719222f4..c2afc73d 100644 --- a/internal/view/logs_extender.go +++ b/internal/view/logs_extender.go @@ -59,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", watch.ReadVerbs) if err != nil { l.App().Flash().Err(err) return @@ -70,7 +70,7 @@ func (l *LogsExtender) showLogs(path string, prev bool) { if l.containerFn != nil { co = l.containerFn() } - if err := l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)); err != nil { + if err := l.App().inject(NewLog(client.NewGVR(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 67ef5955..77137937 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -45,7 +45,7 @@ func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey { } sel := n.GetTable().GetSelectedItem() - gvr := client.GVR(n.GVR()).AsGVR() + gvr := client.NewGVR(n.GVR()).AsGVR() o, err := n.App().factory.Client().DynDialOrDie().Resource(gvr).Get(sel, metav1.GetOptions{}) if err != nil { n.App().Flash().Errf("Unable to get resource %q -- %s", n.GVR(), err) diff --git a/internal/view/ns_test.go b/internal/view/ns_test.go index 49eb2bcc..6a98ac0a 100644 --- a/internal/view/ns_test.go +++ b/internal/view/ns_test.go @@ -9,7 +9,7 @@ import ( ) func TestNSCleanser(t *testing.T) { - ns := view.NewNamespace(client.GVR("v1/namespaces")) + ns := view.NewNamespace(client.NewGVR("v1/namespaces")) assert.Nil(t, ns.Init(makeCtx())) assert.Equal(t, "Namespaces", ns.Name()) diff --git a/internal/view/pod.go b/internal/view/pod.go index 7929ceb5..dff202ca 100644 --- a/internal/view/pod.go +++ b/internal/view/pod.go @@ -54,7 +54,7 @@ func (p *Pod) bindKeys(aa ui.KeyActions) { 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 := NewContainer(client.NewGVR("containers")) co.SetContextFn(p.podContext) if err := app.inject(co); err != nil { app.Flash().Err(err) @@ -73,7 +73,7 @@ func (p *Pod) killCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - res, err := dao.AccessorFor(p.App().factory, client.GVR(p.GVR())) + res, err := dao.AccessorFor(p.App().factory, client.NewGVR(p.GVR())) if err != nil { p.App().Flash().Err(err) return nil @@ -140,7 +140,7 @@ func (p *Pod) shellIn(path, co string) { // Helpers... func fetchContainers(f *watch.Factory, path string, includeInit bool) ([]string, error) { - o, err := f.Get("v1/pods", path, labels.Everything()) + o, err := f.Get("v1/pods", path, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/view/pod_test.go b/internal/view/pod_test.go index 273848bd..16095f15 100644 --- a/internal/view/pod_test.go +++ b/internal/view/pod_test.go @@ -12,7 +12,7 @@ import ( ) func TestPodNew(t *testing.T) { - po := view.NewPod(client.GVR("v1/pods")) + po := view.NewPod(client.NewGVR("v1/pods")) assert.Nil(t, po.Init(makeCtx())) assert.Equal(t, "Pods", po.Name()) diff --git a/internal/view/policy.go b/internal/view/policy.go index cb2c22d8..2245fa08 100644 --- a/internal/view/policy.go +++ b/internal/view/policy.go @@ -26,7 +26,7 @@ type Policy struct { // NewPolicy returns a new viewer. func NewPolicy(app *App, subject, name string) *Policy { p := Policy{ - ResourceViewer: NewBrowser(client.GVR("policy")), + ResourceViewer: NewBrowser(client.NewGVR("policy")), subjectKind: subject, subjectName: name, } diff --git a/internal/view/port_forward.go b/internal/view/port_forward.go index 609d02fc..07fdf4bf 100644 --- a/internal/view/port_forward.go +++ b/internal/view/port_forward.go @@ -9,6 +9,7 @@ import ( "github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/perf" "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" @@ -57,7 +58,7 @@ func (p *PortForward) bindKeys(aa ui.KeyActions) { } func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey { - if err := p.App().inject(NewBenchmark("benchmarks")); err != nil { + if err := p.App().inject(NewBenchmark(client.NewGVR("benchmarks"))); err != nil { p.App().Flash().Err(err) } @@ -133,15 +134,20 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - sel := p.GetTable().GetSelectedItem() - if sel == "" { + path := p.GetTable().GetSelectedItem() + if path == "" { return nil } - log.Debug().Msgf("PF DELETE %q", sel) + log.Debug().Msgf("PF DELETE %q", path) - showModal(p.App().Content.Pages, fmt.Sprintf("Delete PortForward `%s?", sel), func() { - p.App().factory.DeleteForwarder(sel) - p.App().Flash().Infof("PortForward %s deleted!", sel) + showModal(p.App().Content.Pages, fmt.Sprintf("Delete PortForward `%s?", path), func() { + var pf dao.PortForward + pf.Init(p.App().factory, client.NewGVR("portforwards")) + if err := pf.Delete(path, true, true); err != nil { + p.App().Flash().Err(err) + return + } + p.App().Flash().Infof("PortForward %s deleted!", path) p.GetTable().Refresh() }) diff --git a/internal/view/port_forward_test.go b/internal/view/port_forward_test.go index 69a0648c..ad787bc5 100644 --- a/internal/view/port_forward_test.go +++ b/internal/view/port_forward_test.go @@ -9,7 +9,7 @@ import ( ) func TestPortForwardNew(t *testing.T) { - pf := view.NewPortForward(client.GVR("portforwards")) + pf := view.NewPortForward(client.NewGVR("portforwards")) assert.Nil(t, pf.Init(makeCtx())) assert.Equal(t, "PortForwards", pf.Name()) diff --git a/internal/view/rbac.go b/internal/view/rbac.go index e72002ea..e1068a55 100644 --- a/internal/view/rbac.go +++ b/internal/view/rbac.go @@ -36,7 +36,7 @@ func (r *Rbac) bindKeys(aa ui.KeyActions) { } func showRules(app *App, _, gvr, path string) { - v := NewRbac(client.GVR("rbac")) + v := NewRbac(client.NewGVR("rbac")) v.SetContextFn(rbacCtxt(gvr, path)) if err := app.inject(v); err != nil { diff --git a/internal/view/rbac_test.go b/internal/view/rbac_test.go index 3b26fdd4..63cfad8b 100644 --- a/internal/view/rbac_test.go +++ b/internal/view/rbac_test.go @@ -9,7 +9,7 @@ import ( ) func TestRbacNew(t *testing.T) { - v := view.NewRbac(client.GVR("rbac")) + v := view.NewRbac(client.NewGVR("rbac")) assert.Nil(t, v.Init(makeCtx())) assert.Equal(t, "Rbac", v.Name()) diff --git a/internal/view/registrar.go b/internal/view/registrar.go index a554ab20..bf57df4b 100644 --- a/internal/view/registrar.go +++ b/internal/view/registrar.go @@ -19,103 +19,103 @@ func loadCustomViewers() MetaViewers { } func coreRes(vv MetaViewers) { - vv["v1/namespaces"] = MetaViewer{ + vv[client.NewGVR("v1/namespaces")] = MetaViewer{ viewerFn: NewNamespace, } - vv["v1/events"] = MetaViewer{ + vv[client.NewGVR("v1/events")] = MetaViewer{ viewerFn: NewEvent, } - vv["v1/pods"] = MetaViewer{ + vv[client.NewGVR("v1/pods")] = MetaViewer{ viewerFn: NewPod, } - vv["v1/services"] = MetaViewer{ + vv[client.NewGVR("v1/services")] = MetaViewer{ viewerFn: NewService, } - vv["v1/nodes"] = MetaViewer{ + vv[client.NewGVR("v1/nodes")] = MetaViewer{ viewerFn: NewNode, } - vv["v1/secrets"] = MetaViewer{ + vv[client.NewGVR("v1/secrets")] = MetaViewer{ viewerFn: NewSecret, } } func miscRes(vv MetaViewers) { - vv["contexts"] = MetaViewer{ + vv[client.NewGVR("contexts")] = MetaViewer{ viewerFn: NewContext, } - vv["containers"] = MetaViewer{ + vv[client.NewGVR("containers")] = MetaViewer{ viewerFn: NewContainer, } - vv["portforwards"] = MetaViewer{ + vv[client.NewGVR("portforwards")] = MetaViewer{ viewerFn: NewPortForward, } - vv["screendumps"] = MetaViewer{ + vv[client.NewGVR("screendumps")] = MetaViewer{ viewerFn: NewScreenDump, } - vv["benchmarks"] = MetaViewer{ + vv[client.NewGVR("benchmarks")] = MetaViewer{ viewerFn: NewBenchmark, } - vv["aliases"] = MetaViewer{ + vv[client.NewGVR("aliases")] = MetaViewer{ viewerFn: NewAlias, } } func appsRes(vv MetaViewers) { - vv["apps/v1/deployments"] = MetaViewer{ + vv[client.NewGVR("apps/v1/deployments")] = MetaViewer{ viewerFn: NewDeploy, } - vv["apps/v1/replicasets"] = MetaViewer{ + vv[client.NewGVR("apps/v1/replicasets")] = MetaViewer{ viewerFn: NewReplicaSet, } - vv["apps/v1/statefulsets"] = MetaViewer{ + vv[client.NewGVR("apps/v1/statefulsets")] = MetaViewer{ viewerFn: NewStatefulSet, } - vv["apps/v1/daemonsets"] = MetaViewer{ + vv[client.NewGVR("apps/v1/daemonsets")] = MetaViewer{ viewerFn: NewDaemonSet, } - vv["extensions/v1beta1/daemonsets"] = MetaViewer{ + vv[client.NewGVR("extensions/v1beta1/daemonsets")] = MetaViewer{ viewerFn: NewDaemonSet, } } func rbacRes(vv MetaViewers) { - vv["rbac"] = MetaViewer{ + vv[client.NewGVR("rbac")] = MetaViewer{ enterFn: showRules, } - vv["users"] = MetaViewer{ + vv[client.NewGVR("users")] = MetaViewer{ viewerFn: NewUser, } - vv["groups"] = MetaViewer{ + vv[client.NewGVR("groups")] = MetaViewer{ viewerFn: NewGroup, } - vv["rbac.authorization.k8s.io/v1/clusterroles"] = MetaViewer{ + vv[client.NewGVR("rbac.authorization.k8s.io/v1/clusterroles")] = MetaViewer{ enterFn: showRules, } - vv["rbac.authorization.k8s.io/v1/roles"] = MetaViewer{ + vv[client.NewGVR("rbac.authorization.k8s.io/v1/roles")] = MetaViewer{ enterFn: showRules, } - vv["rbac.authorization.k8s.io/v1/clusterrolebindings"] = MetaViewer{ + vv[client.NewGVR("rbac.authorization.k8s.io/v1/clusterrolebindings")] = MetaViewer{ enterFn: showRules, } - vv["rbac.authorization.k8s.io/v1/rolebindings"] = MetaViewer{ + vv[client.NewGVR("rbac.authorization.k8s.io/v1/rolebindings")] = MetaViewer{ enterFn: showRules, } } func batchRes(vv MetaViewers) { - vv["batch/v1beta1/cronjobs"] = MetaViewer{ + vv[client.NewGVR("batch/v1beta1/cronjobs")] = MetaViewer{ viewerFn: NewCronJob, } - vv["batch/v1/jobs"] = MetaViewer{ + vv[client.NewGVR("batch/v1/jobs")] = MetaViewer{ viewerFn: NewJob, } } func extRes(vv MetaViewers) { - vv["apiextensions.k8s.io/v1/customresourcedefinitions"] = MetaViewer{ + vv[client.NewGVR("apiextensions.k8s.io/v1/customresourcedefinitions")] = MetaViewer{ enterFn: showCRD, } - vv["apiextensions.k8s.io/v1beta1/customresourcedefinitions"] = MetaViewer{ + vv[client.NewGVR("apiextensions.k8s.io/v1beta1/customresourcedefinitions")] = MetaViewer{ enterFn: showCRD, } } diff --git a/internal/view/restart_extender.go b/internal/view/restart_extender.go index 8914372c..5d8695c9 100644 --- a/internal/view/restart_extender.go +++ b/internal/view/restart_extender.go @@ -51,7 +51,7 @@ func (r *RestartExtender) restartCmd(evt *tcell.EventKey) *tcell.EventKey { } func (r *RestartExtender) restartRollout(path string) error { - res, err := dao.AccessorFor(r.App().factory, client.GVR(r.GVR())) + res, err := dao.AccessorFor(r.App().factory, client.NewGVR(r.GVR())) if err != nil { return nil } diff --git a/internal/view/rs.go b/internal/view/rs.go index 87011522..ec28519c 100644 --- a/internal/view/rs.go +++ b/internal/view/rs.go @@ -49,7 +49,7 @@ func (r *ReplicaSet) bindKeys(aa ui.KeyActions) { } func (r *ReplicaSet) showPods(app *App, _, gvr, path string) { - o, err := app.factory.Get(r.GVR(), path, labels.Everything()) + o, err := app.factory.Get(r.GVR(), path, true, labels.Everything()) if err != nil { app.Flash().Err(err) return @@ -104,7 +104,7 @@ func (r *ReplicaSet) showModal(msg string, done func(int, string)) { // Helpers... func findRS(f *watch.Factory, path string) (*v1.ReplicaSet, error) { - o, err := f.Get("apps/v1/replicasets", path, labels.Everything()) + o, err := f.Get("apps/v1/replicasets", path, true, labels.Everything()) if err != nil { return nil, err } @@ -119,7 +119,7 @@ func findRS(f *watch.Factory, path string) (*v1.ReplicaSet, error) { } func findDP(f *watch.Factory, path string) (*appsv1.Deployment, error) { - o, err := f.Get("apps/v1/deployments", path, labels.Everything()) + o, err := f.Get("apps/v1/deployments", path, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/view/scale_extender.go b/internal/view/scale_extender.go index e38ad100..48098939 100644 --- a/internal/view/scale_extender.go +++ b/internal/view/scale_extender.go @@ -106,7 +106,7 @@ func (s *ScaleExtender) makeStyledForm() *tview.Form { } func (s *ScaleExtender) scale(path string, replicas int) error { - res, err := dao.AccessorFor(s.App().factory, client.GVR(s.GVR())) + res, err := dao.AccessorFor(s.App().factory, client.NewGVR(s.GVR())) if err != nil { return nil } diff --git a/internal/view/screen_dump_test.go b/internal/view/screen_dump_test.go index d81fb180..59b1756f 100644 --- a/internal/view/screen_dump_test.go +++ b/internal/view/screen_dump_test.go @@ -9,7 +9,7 @@ import ( ) func TestScreenDumpNew(t *testing.T) { - po := view.NewScreenDump(client.GVR("screendumps")) + po := view.NewScreenDump(client.NewGVR("screendumps")) assert.Nil(t, po.Init(makeCtx())) assert.Equal(t, "ScreenDumps", po.Name()) diff --git a/internal/view/secret.go b/internal/view/secret.go index 603a7c5f..a8ea94f0 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -39,7 +39,7 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - o, err := s.App().factory.Get("v1/secrets", path, labels.Everything()) + o, err := s.App().factory.Get(s.GVR(), path, true, labels.Everything()) if err != nil { s.App().Flash().Err(err) return nil diff --git a/internal/view/secret_test.go b/internal/view/secret_test.go index 8520aab8..8823ab3c 100644 --- a/internal/view/secret_test.go +++ b/internal/view/secret_test.go @@ -9,7 +9,7 @@ import ( ) func TestSecretNew(t *testing.T) { - s := view.NewSecret(client.GVR("v1/secrets")) + s := view.NewSecret(client.NewGVR("v1/secrets")) assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "Secrets", s.Name()) diff --git a/internal/view/sts.go b/internal/view/sts.go index 40c0a97e..bf6cc99b 100644 --- a/internal/view/sts.go +++ b/internal/view/sts.go @@ -50,7 +50,7 @@ func (s *StatefulSet) showPods(app *App, _, gvr, path string) { } func (s *StatefulSet) sts(path string) (*appsv1.StatefulSet, error) { - o, err := s.App().factory.Get(s.GVR(), path, labels.Everything()) + o, err := s.App().factory.Get(s.GVR(), path, true, labels.Everything()) if err != nil { return nil, err } diff --git a/internal/view/sts_test.go b/internal/view/sts_test.go index 50a9a759..bf32d2d7 100644 --- a/internal/view/sts_test.go +++ b/internal/view/sts_test.go @@ -9,7 +9,7 @@ import ( ) func TestStatefulSetNew(t *testing.T) { - s := view.NewStatefulSet(client.GVR("apps/v1/statefulsets")) + s := view.NewStatefulSet(client.NewGVR("apps/v1/statefulsets")) assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "StatefulSets", s.Name()) diff --git a/internal/view/svc.go b/internal/view/svc.go index 5a8ba70c..bd971945 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -47,7 +47,7 @@ func (s *Service) bindKeys(aa ui.KeyActions) { } func (s *Service) showPods(app *App, ns, gvr, path string) { - o, err := app.factory.Get(gvr, path, labels.Everything()) + o, err := app.factory.Get(gvr, path, true, labels.Everything()) if err != nil { app.Flash().Err(err) return diff --git a/internal/view/svc_test.go b/internal/view/svc_test.go index 37189d1d..a6400b54 100644 --- a/internal/view/svc_test.go +++ b/internal/view/svc_test.go @@ -128,7 +128,7 @@ func init() { } func TestServiceNew(t *testing.T) { - s := view.NewService(client.GVR("v1/services")) + s := view.NewService(client.NewGVR("v1/services")) assert.Nil(t, s.Init(makeCtx())) assert.Equal(t, "Services", s.Name()) diff --git a/internal/view/table.go b/internal/view/table.go index c298bca6..dc320d65 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -27,7 +27,7 @@ type Table struct { // NewTable returns a new viewer. func NewTable(gvr client.GVR) *Table { return &Table{ - Table: ui.NewTable(string(gvr)), + Table: ui.NewTable(gvr.String()), gvr: gvr, } } @@ -50,7 +50,7 @@ func (t *Table) Init(ctx context.Context) (err error) { func (t *Table) Name() string { return t.BaseTitle } // GVR returns a resource descriptor. -func (t *Table) GVR() string { return string(t.gvr) } +func (t *Table) GVR() string { return t.gvr.String() } // SetBindKeysFn adds additional key bindings. func (t *Table) SetBindKeysFn(f BindKeysFunc) { t.bindKeysFn = f } @@ -134,7 +134,7 @@ func (t *Table) viewCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - o, err := t.app.factory.Get(string(t.gvr), path, labels.Everything()) + o, err := t.app.factory.Get(t.GVR(), path, true, labels.Everything()) if err != nil { t.app.Flash().Errf("Unable to get resource %q -- %s", t.gvr, err) return nil diff --git a/internal/view/table_int_test.go b/internal/view/table_int_test.go index ffdfa0e6..da645171 100644 --- a/internal/view/table_int_test.go +++ b/internal/view/table_int_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/model" "github.com/derailed/k9s/internal/render" @@ -18,7 +19,7 @@ import ( ) func TestTableSave(t *testing.T) { - v := NewTable("test") + v := NewTable(client.NewGVR("test")) v.Init(makeContext()) v.SetTitle("k9s-test") @@ -31,7 +32,7 @@ func TestTableSave(t *testing.T) { } func TestTableNew(t *testing.T) { - v := NewTable("test") + v := NewTable(client.NewGVR("test")) v.Init(makeContext()) data := render.NewTableData() @@ -60,7 +61,7 @@ func TestTableNew(t *testing.T) { } func TestTableViewFilter(t *testing.T) { - v := NewTable("test") + v := NewTable(client.NewGVR("test")) v.Init(makeContext()) v.SetModel(&testTableModel{}) v.SearchBuff().SetActive(true) @@ -70,7 +71,7 @@ func TestTableViewFilter(t *testing.T) { } func TestTableViewSort(t *testing.T) { - v := NewTable("test") + v := NewTable(client.NewGVR("test")) v.Init(makeContext()) v.SetModel(&testTableModel{}) v.SortColCmd(1, true)(nil) diff --git a/internal/watch/factory.go b/internal/watch/factory.go index 9da09081..e5b9061c 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -40,7 +40,7 @@ func NewFactory(client client.Connection) *Factory { // Start initializes the informers until caller cancels the context. func (f *Factory) Start(ns string) { - log.Debug().Msgf("Starting factory in ns `%q", ns) + log.Debug().Msgf("Factory START with ns `%q", ns) f.stopChan = make(chan struct{}) for ns, fac := range f.factories { log.Debug().Msgf("Starting factory in ns %q", ns) @@ -61,38 +61,60 @@ func (f *Factory) Terminate() { } // List returns a resource collection. -func (f *Factory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) { - inf, err := f.CanForResource(ns, gvr, "list") +func (f *Factory) List(gvr, ns string, wait bool, sel labels.Selector) ([]runtime.Object, error) { + defer func(t time.Time) { + log.Debug().Msgf("LIST time %v", time.Since(t)) + }(time.Now()) + + Dump(f) + log.Debug().Msgf("List %q:%q", ns, gvr) + inf, err := f.CanForResource(ns, gvr, []string{"list", "watch"}) if err != nil { return nil, err } if ns == clusterScope { - return inf.Lister().List(sel) + ns = allNamespaces } + if wait { + f.waitForCacheSync(ns) + } return inf.Lister().ByNamespace(ns).List(sel) } // Get retrieves a given resource. -func (f *Factory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) { +func (f *Factory) Get(gvr, path string, wait bool, sel labels.Selector) (runtime.Object, error) { + defer func(t time.Time) { + log.Debug().Msgf("GET time %v", time.Since(t)) + }(time.Now()) + ns, n := namespaced(path) - inf, err := f.CanForResource(ns, gvr, "get") + inf, err := f.CanForResource(ns, gvr, []string{"get"}) if err != nil { return nil, err } if ns == clusterScope { - return inf.Lister().Get(n) + ns = allNamespaces } + if wait { + f.waitForCacheSync(ns) + } return inf.Lister().ByNamespace(ns).Get(n) } +func (f *Factory) waitForCacheSync(ns string) { + if fac, ok := f.factories[ns]; ok { + fac.WaitForCacheSync(f.stopChan) + } +} + // WaitForCacheSync waits for all factories to update their cache. func (f *Factory) WaitForCacheSync() { - for _, fac := range f.factories { + for ns, fac := range f.factories { m := fac.WaitForCacheSync(f.stopChan) for k, v := range m { - log.Debug().Msgf("CACHE -- Loaded %q:%v", k, v) + log.Debug().Msgf("CACHE `%q Loaded %t:%s", ns, v, k) } } } @@ -120,16 +142,15 @@ func (f *Factory) isClusterWide() bool { return ok } -func (f *Factory) preload(_ string) { - _, _ = f.CanForResource("", "apiextensions.k8s.io/v1beta1/customresourcedefinitions", ReadVerbs...) - // BOZO!! - // _, _ = f.CanForResource(ns, "v1/pods", verbs...) - // _, _ = f.CanForResource(clusterScope, "rbac.authorization.k8s.io/v1/clusterroles", verbs...) - // _, _ = f.CanForResource(allNamespaces, "rbac.authorization.k8s.io/v1/roles", verbs...) -} - // CanForResource return an informer is user has access. -func (f *Factory) CanForResource(ns, gvr string, verbs ...string) (informers.GenericInformer, error) { +func (f *Factory) CanForResource(ns, gvr string, verbs []string) (informers.GenericInformer, error) { + // If user can access resource cluster wide, prefer cluster wide factory. + if ns != allNamespaces { + auth, err := f.Client().CanI(allNamespaces, gvr, verbs) + if auth && err == nil { + return f.ForResource(allNamespaces, gvr), nil + } + } auth, err := f.Client().CanI(ns, gvr, verbs) if err != nil { return nil, err @@ -145,23 +166,31 @@ func (f *Factory) CanForResource(ns, gvr string, verbs ...string) (informers.Gen func (f *Factory) ForResource(ns, gvr string) informers.GenericInformer { fact := f.ensureFactory(ns) inf := fact.ForResource(toGVR(gvr)) + if inf == nil { + log.Error().Err(fmt.Errorf("MEOW! No informer for %q:%q", ns, gvr)) + return inf + } + log.Debug().Msgf("FOR_RESOURCE %q:%q", ns, gvr) fact.Start(f.stopChan) return inf } func (f *Factory) ensureFactory(ns string) di.DynamicSharedInformerFactory { + if ns == clusterScope { + ns = allNamespaces + } if fac, ok := f.factories[ns]; ok { return fac } + log.Debug().Msgf("FACTORY_NEW for ns %q", ns) f.factories[ns] = di.NewFilteredDynamicSharedInformerFactory( f.client.DynDialOrDie(), defaultResync, ns, nil, ) - f.preload(ns) return f.factories[ns] } @@ -173,6 +202,7 @@ 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 1bdca839..59254fc9 100644 --- a/internal/watch/helper.go +++ b/internal/watch/helper.go @@ -37,19 +37,10 @@ func Dump(f *Factory) { } // Debug for debug. -func Debug(f *Factory, gvr string) { +func Debug(f *Factory, ns string, gvr string) { log.Debug().Msgf("----------- DEBUG FACTORY (%s) -------------", gvr) - inf := f.factories[allNamespaces].ForResource(toGVR(gvr)) + inf := f.factories[ns].ForResource(toGVR(gvr)) for i, k := range inf.Informer().GetStore().ListKeys() { log.Debug().Msgf("%d -- %s", i, k) } } - -// Show for debug. -func Show(f *Factory, ns, gvr string) { - log.Debug().Msgf("----------- SHOW FACTORIES %q -------------", ns) - inf := f.ForResource(ns, gvr) - for _, k := range inf.Informer().GetStore().ListKeys() { - log.Debug().Msgf(" Key: %s", k) - } -}