diff --git a/.golangci.yml b/.golangci.yml index 5f004fc9..ab82fa80 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -114,7 +114,7 @@ linters-settings: local-prefixes: github.com/org/project gocyclo: # minimal code complexity to report, 30 by default (but we recommend 10-20) - min-complexity: 10 + min-complexity: 15 gocognit: # minimal code complexity to report, 30 by default (but we recommend 10-20) min-complexity: 20 diff --git a/internal/client/client.go b/internal/client/client.go index 2b5d2caa..a41acf8a 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -9,7 +9,6 @@ import ( authorizationv1 "k8s.io/api/authorization/v1" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/dynamic" @@ -38,7 +37,6 @@ type Connection interface { Config() *Config DialOrDie() kubernetes.Interface SwitchContextOrDie(ctx string) - NSDialOrDie() dynamic.NamespaceableResourceInterface CachedDiscovery() (*disk.CachedDiscoveryClient, error) RestConfigOrDie() *restclient.Config MXDial() (*versioned.Clientset, error) @@ -235,23 +233,6 @@ func (a *APIClient) DynDialOrDie() dynamic.Interface { return a.dClient } -// NSDialOrDie returns a handle to a namespaced resource. -func (a *APIClient) NSDialOrDie() dynamic.NamespaceableResourceInterface { - a.mx.Lock() - defer a.mx.Unlock() - - if a.nsClient != nil { - return a.nsClient - } - a.nsClient = a.DynDialOrDie().Resource(schema.GroupVersionResource{ - Group: "apiextensions.k8s.io", - Version: "v1beta1", - Resource: "customresourcedefinitions", - }) - - return a.nsClient -} - // MXDial returns a handle to the metrics server. func (a *APIClient) MXDial() (*versioned.Clientset, error) { a.mx.Lock() diff --git a/internal/config/mock_connection_test.go b/internal/config/mock_connection_test.go index 5f2c4271..45572f04 100644 --- a/internal/config/mock_connection_test.go +++ b/internal/config/mock_connection_test.go @@ -4,6 +4,9 @@ package config_test import ( + "reflect" + "time" + client "github.com/derailed/k9s/internal/client" pegomock "github.com/petergtz/pegomock" v1 "k8s.io/api/core/v1" @@ -13,8 +16,6 @@ import ( kubernetes "k8s.io/client-go/kubernetes" rest "k8s.io/client-go/rest" versioned "k8s.io/metrics/pkg/client/clientset/versioned" - "reflect" - "time" ) type MockConnection struct { @@ -202,21 +203,6 @@ func (mock *MockConnection) MXDial() (*versioned.Clientset, error) { return ret0, ret1 } -func (mock *MockConnection) NSDialOrDie() dynamic.NamespaceableResourceInterface { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("NSDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.NamespaceableResourceInterface)(nil)).Elem()}) - var ret0 dynamic.NamespaceableResourceInterface - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(dynamic.NamespaceableResourceInterface) - } - } - return ret0 -} - func (mock *MockConnection) NodePods(_param0 string) (*v1.PodList, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockConnection().") @@ -570,23 +556,6 @@ func (c *MockConnection_MXDial_OngoingVerification) GetCapturedArguments() { func (c *MockConnection_MXDial_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierMockConnection) NSDialOrDie() *MockConnection_NSDialOrDie_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NSDialOrDie", params, verifier.timeout) - return &MockConnection_NSDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_NSDialOrDie_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_NSDialOrDie_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_NSDialOrDie_OngoingVerification) GetAllCapturedArguments() { -} - func (verifier *VerifierMockConnection) NodePods(_param0 string) *MockConnection_NodePods_OngoingVerification { params := []pegomock.Param{_param0} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NodePods", params, verifier.timeout) diff --git a/internal/dao/registry.go b/internal/dao/registry.go index e10313bb..66d2b72a 100644 --- a/internal/dao/registry.go +++ b/internal/dao/registry.go @@ -176,7 +176,6 @@ func loadPreferred(f Factory, m ResourceMetas) error { for _, r := range rr { for _, res := range r.APIResources { gvr := client.FromGVAndR(r.GroupVersion, res.Name) - log.Debug().Msgf("GVR %s", gvr) res.Group, res.Version = gvr.ToG(), gvr.ToV() m[gvr] = res } @@ -186,12 +185,14 @@ func loadPreferred(f Factory, m ResourceMetas) error { } func loadCRDs(f Factory, m ResourceMetas) error { + log.Debug().Msgf("Loading CRDs...") oo, err := f.List("apiextensions.k8s.io/v1beta1/customresourcedefinitions", "", labels.Everything()) if err != nil { log.Error().Err(err).Msgf("Fail CRDs load") return nil } f.WaitForCacheSync() + log.Debug().Msgf("CRDS count %d", len(oo)) for _, o := range oo { meta, errs := extractMeta(o) @@ -200,6 +201,7 @@ func loadCRDs(f Factory, m ResourceMetas) error { continue } gvr := client.NewGVR(meta.Group, meta.Version, meta.Name) + log.Debug().Msgf("CRD %q", gvr) m[gvr] = meta } diff --git a/internal/dao/types.go b/internal/dao/types.go index 553d8857..54292c88 100644 --- a/internal/dao/types.go +++ b/internal/dao/types.go @@ -20,7 +20,7 @@ type Factory interface { Get(gvr, path string, sel labels.Selector) (runtime.Object, error) // List fetch a collection of resources. - List(ns, gvr string, sel labels.Selector) ([]runtime.Object, error) + List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) // ForResource fetch an informer for a given resource. ForResource(ns, gvr string) informers.GenericInformer diff --git a/internal/model/crd.go b/internal/model/crd.go new file mode 100644 index 00000000..af8a4edc --- /dev/null +++ b/internal/model/crd.go @@ -0,0 +1,31 @@ +package model + +import ( + "context" + + "github.com/derailed/k9s/internal" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +// CustomResourceDefinition represents a CRD resource model. +type CustomResourceDefinition struct { + Resource +} + +// List returns a collection of nodes. +func (c *CustomResourceDefinition) List(ctx context.Context) ([]runtime.Object, error) { + strLabel, ok := ctx.Value(internal.KeyLabels).(string) + lsel := labels.Everything() + if sel, err := labels.ConvertSelectorToLabelsMap(strLabel); ok && err == nil { + lsel = sel.AsSelector() + } + + gvr := "apiextensions.k8s.io/v1beta1/customresourcedefinitions" + oo, err := c.factory.List(gvr, "-", lsel) + if err != nil { + return nil, err + } + + return oo, nil +} diff --git a/internal/model/generic.go b/internal/model/generic.go index c049c038..6c837134 100644 --- a/internal/model/generic.go +++ b/internal/model/generic.go @@ -3,11 +3,9 @@ package model import ( "context" "fmt" - "time" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/render" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" @@ -61,16 +59,11 @@ func (g *Generic) List(ctx context.Context) ([]runtime.Object, error) { res[i] = RowRes{&g.table.Rows[i]} } - log.Debug().Msgf("!!!!GENERIC lister returns %d", len(res)) return res, err } // Hydrate returns nodes as rows. func (g *Generic) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { - defer func(t time.Time) { - log.Debug().Msgf("HYDRATE elapsed: %v", time.Since(t)) - }(time.Now()) - gr, ok := re.(*render.Generic) if !ok { return fmt.Errorf("expecting generic renderer for %s but got %T", g.gvr, re) diff --git a/internal/model/hpa.go b/internal/model/hpa.go new file mode 100644 index 00000000..d40cda71 --- /dev/null +++ b/internal/model/hpa.go @@ -0,0 +1,48 @@ +package model + +import ( + "context" + + "github.com/derailed/k9s/internal" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +// HorizontalPodAutoscaler represents a HPA resource model. +type HorizontalPodAutoscaler struct { + Resource +} + +// List returns a collection of nodes. +func (c *HorizontalPodAutoscaler) List(ctx context.Context) ([]runtime.Object, error) { + strLabel, ok := ctx.Value(internal.KeyLabels).(string) + lsel := labels.Everything() + if sel, err := labels.ConvertSelectorToLabelsMap(strLabel); ok && err == nil { + lsel = sel.AsSelector() + } + + gvr := "autoscaling/v2beta2/horizontalpodautoscalers" + ooV2b2, err := c.factory.List(gvr, c.namespace, lsel) + if err != nil { + return nil, err + } + if len(ooV2b2) > 0 { + return ooV2b2, nil + } + + gvr = "autoscaling/v2beta1/horizontalpodautoscalers" + ooV2b1, err := c.factory.List(gvr, c.namespace, lsel) + if err != nil { + return nil, err + } + if len(ooV2b1) > 0 { + return ooV2b1, nil + } + + gvr = "autoscaling/v1/horizontalpodautoscalers" + oo, err := c.factory.List(gvr, c.namespace, lsel) + if err != nil { + return nil, err + } + return oo, nil +} diff --git a/internal/model/mock_clustermeta_test.go b/internal/model/mock_clustermeta_test.go index 87109ebe..4ce0a3cf 100644 --- a/internal/model/mock_clustermeta_test.go +++ b/internal/model/mock_clustermeta_test.go @@ -4,6 +4,9 @@ package model_test import ( + "reflect" + "time" + client "github.com/derailed/k9s/internal/client" pegomock "github.com/petergtz/pegomock" v1 "k8s.io/api/core/v1" @@ -13,8 +16,6 @@ import ( kubernetes "k8s.io/client-go/kubernetes" rest "k8s.io/client-go/rest" versioned "k8s.io/metrics/pkg/client/clientset/versioned" - "reflect" - "time" ) type MockClusterMeta struct { @@ -259,21 +260,6 @@ func (mock *MockClusterMeta) MXDial() (*versioned.Clientset, error) { return ret0, ret1 } -func (mock *MockClusterMeta) NSDialOrDie() dynamic.NamespaceableResourceInterface { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterMeta().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("NSDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.NamespaceableResourceInterface)(nil)).Elem()}) - var ret0 dynamic.NamespaceableResourceInterface - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(dynamic.NamespaceableResourceInterface) - } - } - return ret0 -} - func (mock *MockClusterMeta) NodePods(_param0 string) (*v1.PodList, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockClusterMeta().") @@ -716,23 +702,6 @@ func (c *MockClusterMeta_MXDial_OngoingVerification) GetCapturedArguments() { func (c *MockClusterMeta_MXDial_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierMockClusterMeta) NSDialOrDie() *MockClusterMeta_NSDialOrDie_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NSDialOrDie", params, verifier.timeout) - return &MockClusterMeta_NSDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockClusterMeta_NSDialOrDie_OngoingVerification struct { - mock *MockClusterMeta - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockClusterMeta_NSDialOrDie_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockClusterMeta_NSDialOrDie_OngoingVerification) GetAllCapturedArguments() { -} - func (verifier *VerifierMockClusterMeta) NodePods(_param0 string) *MockClusterMeta_NodePods_OngoingVerification { params := []pegomock.Param{_param0} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NodePods", params, verifier.timeout) diff --git a/internal/model/mock_connection_test.go b/internal/model/mock_connection_test.go index 26d86571..d4d4dec6 100644 --- a/internal/model/mock_connection_test.go +++ b/internal/model/mock_connection_test.go @@ -4,6 +4,9 @@ package model_test import ( + "reflect" + "time" + client "github.com/derailed/k9s/internal/client" pegomock "github.com/petergtz/pegomock" v1 "k8s.io/api/core/v1" @@ -13,8 +16,6 @@ import ( kubernetes "k8s.io/client-go/kubernetes" rest "k8s.io/client-go/rest" versioned "k8s.io/metrics/pkg/client/clientset/versioned" - "reflect" - "time" ) type MockConnection struct { @@ -202,21 +203,6 @@ func (mock *MockConnection) MXDial() (*versioned.Clientset, error) { return ret0, ret1 } -func (mock *MockConnection) NSDialOrDie() dynamic.NamespaceableResourceInterface { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockConnection().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("NSDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.NamespaceableResourceInterface)(nil)).Elem()}) - var ret0 dynamic.NamespaceableResourceInterface - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(dynamic.NamespaceableResourceInterface) - } - } - return ret0 -} - func (mock *MockConnection) NodePods(_param0 string) (*v1.PodList, error) { if mock == nil { panic("mock must not be nil. Use myMock := NewMockConnection().") @@ -570,23 +556,6 @@ func (c *MockConnection_MXDial_OngoingVerification) GetCapturedArguments() { func (c *MockConnection_MXDial_OngoingVerification) GetAllCapturedArguments() { } -func (verifier *VerifierMockConnection) NSDialOrDie() *MockConnection_NSDialOrDie_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NSDialOrDie", params, verifier.timeout) - return &MockConnection_NSDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MockConnection_NSDialOrDie_OngoingVerification struct { - mock *MockConnection - methodInvocations []pegomock.MethodInvocation -} - -func (c *MockConnection_NSDialOrDie_OngoingVerification) GetCapturedArguments() { -} - -func (c *MockConnection_NSDialOrDie_OngoingVerification) GetAllCapturedArguments() { -} - func (verifier *VerifierMockConnection) NodePods(_param0 string) *MockConnection_NodePods_OngoingVerification { params := []pegomock.Param{_param0} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NodePods", params, verifier.timeout) diff --git a/internal/model/registry.go b/internal/model/registry.go index 4d8c076f..e7e6421e 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -115,11 +115,25 @@ var Registry = map[string]ResourceMeta{ // Autoscaling... "autoscaling/v1/horizontalpodautoscalers": ResourceMeta{ + Model: &HorizontalPodAutoscaler{}, + Renderer: &render.HorizontalPodAutoscaler{}, + }, + "autoscaling/v2beta1/horizontalpodautoscalers": ResourceMeta{ + Model: &HorizontalPodAutoscaler{}, + Renderer: &render.HorizontalPodAutoscaler{}, + }, + "autoscaling/v2beta2/horizontalpodautoscalers": ResourceMeta{ + Model: &HorizontalPodAutoscaler{}, Renderer: &render.HorizontalPodAutoscaler{}, }, // CRDs... + "apiextensions.k8s.io/v1/customresourcedefinitions": ResourceMeta{ + Model: &CustomResourceDefinition{}, + Renderer: &render.CustomResourceDefinition{}, + }, "apiextensions.k8s.io/v1beta1/customresourcedefinitions": ResourceMeta{ + Model: &CustomResourceDefinition{}, Renderer: &render.CustomResourceDefinition{}, }, diff --git a/internal/model/table.go b/internal/model/table.go index 48d86405..67e98a6f 100644 --- a/internal/model/table.go +++ b/internal/model/table.go @@ -11,7 +11,10 @@ import ( "github.com/rs/zerolog/log" ) -const refreshRate = 1 * time.Second +const ( + refreshRate = 1 * time.Second + noDataCount = 2 +) type TableListener interface { TableDataChanged(render.TableData) @@ -25,6 +28,7 @@ type Table struct { listeners []TableListener inUpdate int32 refreshRate time.Duration + zeroCount int32 } // NewTable returns a new table model. @@ -112,7 +116,6 @@ func (t *Table) refresh(ctx context.Context) { // AddListener adds a new model listener. func (t *Table) AddListener(l TableListener) { t.listeners = append(t.listeners, l) - t.fireTableChanged(*t.data) } // RemoveListener delete a listener from the list. @@ -131,6 +134,12 @@ func (t *Table) RemoveListener(l TableListener) { } func (t *Table) fireTableChanged(data render.TableData) { + // Needed to wait for the cache to populate but if there is no data at all + // after X ticks need to tell the view no data is present + if len(data.RowEvents) == 0 && atomic.LoadInt32(&t.zeroCount) < noDataCount { + atomic.AddInt32(&t.zeroCount, 1) + return + } for _, l := range t.listeners { l.TableDataChanged(data) } @@ -152,7 +161,7 @@ func (t *Table) reconcile(ctx context.Context) error { } m, ok := Registry[string(t.gvr)] if !ok { - log.Warn().Msgf("Resource %s not found in registry. Going generic!", t.gvr) + log.Debug().Msgf("Resource %s not found in registry. Going generic!", t.gvr) m = ResourceMeta{ Model: &Generic{}, Renderer: &render.Generic{}, diff --git a/internal/render/hpa.go b/internal/render/hpa.go index f21eaf64..f68a4dde 100644 --- a/internal/render/hpa.go +++ b/internal/render/hpa.go @@ -3,9 +3,12 @@ package render import ( "fmt" "strconv" + "strings" "github.com/derailed/tview" autoscalingv1 "k8s.io/api/autoscaling/v1" + autoscalingv2beta1 "k8s.io/api/autoscaling/v2beta1" + autoscalingv2beta2 "k8s.io/api/autoscaling/v2beta2" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) @@ -28,7 +31,7 @@ func (HorizontalPodAutoscaler) Header(ns string) HeaderRow { return append(h, Header{Name: "NAME"}, Header{Name: "REFERENCE"}, - Header{Name: "TARGETS"}, + Header{Name: "TARGETS%"}, Header{Name: "MINPODS", Align: tview.AlignRight}, Header{Name: "MAXPODS", Align: tview.AlignRight}, Header{Name: "REPLICAS", Align: tview.AlignRight}, @@ -43,6 +46,21 @@ func (h HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error return fmt.Errorf("Expected HorizontalPodAutoscaler, but got %T", o) } + v := raw.Object["apiVersion"] + + switch v { + case "autoscaling/v1": + return h.renderV1(raw, ns, r) + case "autoscaling/v2beta1": + return h.renderV2b1(raw, ns, r) + case "autoscaling/v2beta2": + return h.renderV2b2(raw, ns, r) + default: + return fmt.Errorf("Unhandled HPA version %q", v) + } +} + +func (h HorizontalPodAutoscaler) renderV1(raw *unstructured.Unstructured, ns string, r *Row) error { var hpa autoscalingv1.HorizontalPodAutoscaler err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &hpa) if err != nil { @@ -57,7 +75,59 @@ func (h HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error r.Fields = append(r.Fields, hpa.ObjectMeta.Name, hpa.Spec.ScaleTargetRef.Name, - toMetrics(hpa.Spec, hpa.Status), + toMetricsV1(hpa.Spec, hpa.Status), + strconv.Itoa(int(*hpa.Spec.MinReplicas)), + strconv.Itoa(int(hpa.Spec.MaxReplicas)), + strconv.Itoa(int(hpa.Status.CurrentReplicas)), + toAge(hpa.ObjectMeta.CreationTimestamp), + ) + + return nil +} + +func (h HorizontalPodAutoscaler) renderV2b1(raw *unstructured.Unstructured, ns string, r *Row) error { + var hpa autoscalingv2beta1.HorizontalPodAutoscaler + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &hpa) + if err != nil { + return err + } + + r.ID = MetaFQN(hpa.ObjectMeta) + r.Fields = make(Fields, 0, len(h.Header(ns))) + if isAllNamespace(ns) { + r.Fields = append(r.Fields, hpa.Namespace) + } + + r.Fields = append(r.Fields, + hpa.ObjectMeta.Name, + hpa.Spec.ScaleTargetRef.Name, + toMetricsV2b1(hpa.Spec.Metrics, hpa.Status.CurrentMetrics), + strconv.Itoa(int(*hpa.Spec.MinReplicas)), + strconv.Itoa(int(hpa.Spec.MaxReplicas)), + strconv.Itoa(int(hpa.Status.CurrentReplicas)), + toAge(hpa.ObjectMeta.CreationTimestamp), + ) + + return nil +} + +func (h HorizontalPodAutoscaler) renderV2b2(raw *unstructured.Unstructured, ns string, r *Row) error { + var hpa autoscalingv2beta2.HorizontalPodAutoscaler + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &hpa) + if err != nil { + return err + } + + r.ID = MetaFQN(hpa.ObjectMeta) + r.Fields = make(Fields, 0, len(h.Header(ns))) + if isAllNamespace(ns) { + r.Fields = append(r.Fields, hpa.Namespace) + } + + r.Fields = append(r.Fields, + hpa.ObjectMeta.Name, + hpa.Spec.ScaleTargetRef.Name, + toMetricsV2b2(hpa.Spec.Metrics, hpa.Status.CurrentMetrics), strconv.Itoa(int(*hpa.Spec.MinReplicas)), strconv.Itoa(int(hpa.Spec.MaxReplicas)), strconv.Itoa(int(hpa.Status.CurrentReplicas)), @@ -70,7 +140,7 @@ func (h HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error // ---------------------------------------------------------------------------- // Helpers... -func toMetrics(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscalingv1.HorizontalPodAutoscalerStatus) string { +func toMetricsV1(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscalingv1.HorizontalPodAutoscalerStatus) string { current := "" if status.CurrentCPUUtilizationPercentage != nil { current = strconv.Itoa(int(*status.CurrentCPUUtilizationPercentage)) + "%" @@ -82,3 +152,176 @@ func toMetrics(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscalin } return current + "/" + target + "%" } + +func toMetricsV2b1(specs []autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { + if len(specs) == 0 { + return MissingValue + } + + list, count := []string{}, 0 + for i, spec := range specs { + list = append(list, checkHPAType(i, spec, statuses)) + count++ + } + + max, more := 2, false + if count > max { + list, more = list[:max], true + } + + ret := strings.Join(list, ", ") + if more { + return ret + " + " + strconv.Itoa(count-max) + "more..." + } + + return ret +} + +func toMetricsV2b2(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { + if len(specs) == 0 { + return MissingValue + } + + list, max, more, count := []string{}, 2, false, 0 + for i, spec := range specs { + current := "" + + switch spec.Type { + case autoscalingv2beta2.ExternalMetricSourceType: + list = append(list, externalMetricsV2b2(i, spec, statuses)) + case autoscalingv2beta2.PodsMetricSourceType: + if len(statuses) > i && statuses[i].Pods != nil { + current = statuses[i].Pods.Current.AverageValue.String() + } + list = append(list, current+"/"+spec.Pods.Target.AverageValue.String()) + case autoscalingv2beta2.ObjectMetricSourceType: + if len(statuses) > i && statuses[i].Object != nil { + current = statuses[i].Object.Current.Value.String() + } + list = append(list, current+"/"+spec.Object.Target.Value.String()) + case autoscalingv2beta2.ResourceMetricSourceType: + list = append(list, resourceMetricsV2b2(i, spec, statuses)) + default: + list = append(list, "") + } + count++ + } + + if count > max { + list, more = list[:max], true + } + + ret := strings.Join(list, ", ") + if more { + return ret + " + " + strconv.Itoa(count-max) + "more..." + } + + return ret +} + +func checkHPAType(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { + current := "" + + switch spec.Type { + case autoscalingv2beta1.ExternalMetricSourceType: + return externalMetricsV2b1(i, spec, statuses) + case autoscalingv2beta1.PodsMetricSourceType: + if len(statuses) > i && statuses[i].Pods != nil { + current = statuses[i].Pods.CurrentAverageValue.String() + } + return current + "/" + spec.Pods.TargetAverageValue.String() + case autoscalingv2beta1.ObjectMetricSourceType: + if len(statuses) > i && statuses[i].Object != nil { + current = statuses[i].Object.CurrentValue.String() + } + return current + "/" + spec.Object.TargetValue.String() + case autoscalingv2beta1.ResourceMetricSourceType: + return resourceMetricsV2b1(i, spec, statuses) + } + + return "" +} + +func externalMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { + current := "" + + if spec.External.Target.AverageValue != nil { + if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.Current.AverageValue != nil { + current = statuses[i].External.Current.AverageValue.String() + } + return current + "/" + spec.External.Target.AverageValue.String() + " (avg)" + } + if len(statuses) > i && statuses[i].External != nil { + current = statuses[i].External.Current.Value.String() + } + + return current + "/" + spec.External.Target.Value.String() +} + +func resourceMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { + current := "" + + if spec.Resource.Target.AverageValue != nil { + if len(statuses) > i && statuses[i].Resource != nil { + current = statuses[i].Resource.Current.AverageValue.String() + } + return current + "/" + spec.Resource.Target.AverageValue.String() + } + + if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.Current.AverageUtilization != nil { + current = AsPerc(float64(*statuses[i].Resource.Current.AverageUtilization)) + } + + target := "" + if spec.Resource.Target.AverageUtilization != nil { + target = AsPerc(float64(*spec.Resource.Target.AverageUtilization)) + } + + return current + "/" + target +} + +func externalMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { + current := "" + if spec.External.TargetAverageValue != nil { + if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.CurrentAverageValue != nil { + current = statuses[i].External.CurrentAverageValue.String() + } + return current + "/" + spec.External.TargetAverageValue.String() + " (avg)" + } + if len(statuses) > i && statuses[i].External != nil { + current = statuses[i].External.CurrentValue.String() + } + + return current + "/" + spec.External.TargetValue.String() +} + +func resourceMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { + current := "" + + if status := checkTargetMetricsV2b1(i, spec, statuses); status != "" { + return status + } + + if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.CurrentAverageUtilization != nil { + current = AsPerc(float64(*statuses[i].Resource.CurrentAverageUtilization)) + } + + target := "" + if spec.Resource.TargetAverageUtilization != nil { + target = AsPerc(float64(*spec.Resource.TargetAverageUtilization)) + } + + return current + "/" + target +} + +func checkTargetMetricsV2b1(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string { + if spec.Resource.TargetAverageValue == nil { + return "" + } + + var current string + if len(statuses) > i && statuses[i].Resource != nil { + current = statuses[i].Resource.CurrentAverageValue.String() + } + return current + "/" + spec.Resource.TargetAverageValue.String() +} diff --git a/internal/ui/app.go b/internal/ui/app.go index 97dd67ee..23d29c23 100644 --- a/internal/ui/app.go +++ b/internal/ui/app.go @@ -178,12 +178,6 @@ func (a *App) redrawCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } -// StatusReset reset log back to normal. -func (a *App) StatusReset() { - a.Logo().Reset() - a.Draw() -} - // View Accessora... // Crumbs return app crumba. diff --git a/internal/ui/command.go b/internal/ui/command.go index f50c4fc3..49160b06 100644 --- a/internal/ui/command.go +++ b/internal/ui/command.go @@ -6,7 +6,6 @@ import ( "github.com/derailed/k9s/internal/config" "github.com/derailed/tview" "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" ) const defaultPrompt = "%c> %s" @@ -86,7 +85,6 @@ func (c *Command) BufferActive(f bool, k BufferKind) { c.SetBackgroundColor(c.styles.BgColor()) c.Clear() } - log.Debug().Msgf("Command activated: %t", c.activated) } func colorFor(k BufferKind) tcell.Color { diff --git a/internal/ui/table_helper.go b/internal/ui/table_helper.go index e2d8036d..962800ef 100644 --- a/internal/ui/table_helper.go +++ b/internal/ui/table_helper.go @@ -152,6 +152,7 @@ func styleTitle(rc int, ns, base, path, buff string, styles *config.Styles) stri rc-- } + base = strings.Title(base) if ns == render.AllNamespaces { ns = render.NamespaceAll } diff --git a/internal/view/app.go b/internal/view/app.go index 25584a7a..93ae814b 100644 --- a/internal/view/app.go +++ b/internal/view/app.go @@ -338,13 +338,19 @@ func (a *App) Run() { } } -func (a *App) status(l ui.FlashLevel, msg string) { +func (a *App) Status(l ui.FlashLevel, msg string) { a.Flash().Info(msg) a.setIndicator(l, msg) a.setLogo(l, msg) a.Draw() } +// StatusReset reset log back to normal. +func (a *App) ClearStatus() { + a.Logo().Reset() + a.Draw() +} + func (a *App) setLogo(l ui.FlashLevel, msg string) { switch l { case ui.FlashErr: diff --git a/internal/view/benchmark.go b/internal/view/benchmark.go index 828a818c..c376b2f3 100644 --- a/internal/view/benchmark.go +++ b/internal/view/benchmark.go @@ -13,23 +13,17 @@ import ( "github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" ) -const resultTitle = "Benchmark Results" - // Benchmark represents a service benchmark results view. type Benchmark struct { ResourceViewer - - details *Details } // NewBench returns a new viewer. func NewBenchmark(gvr client.GVR) ResourceViewer { b := Benchmark{ ResourceViewer: NewBrowser(gvr), - details: NewDetails(resultTitle), } b.GetTable().SetBorderFocusColor(tcell.ColorSeaGreen) b.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorSeaGreen, tcell.AttrNone) @@ -46,27 +40,18 @@ func (b *Benchmark) benchContext(ctx context.Context) context.Context { } func (b *Benchmark) viewBench(app *App, ns, res, path string) { - log.Debug().Msgf("VIEWBENCH %q -- %q -- %q", ns, res, path) data, err := readBenchFile(app.Config, b.benchFile()) if err != nil { app.Flash().Errf("Unable to load bench file %s", err) return } - b.details.SetText(data) - b.details.SetSubject(fileToSubject(path)) - if err := app.inject(b.details); err != nil { + details := NewDetails(b.App(), "Benchmark", fileToSubject(path)).Update(data) + if err := app.inject(details); err != nil { app.Flash().Err(err) } } -func fileToSubject(path string) string { - tokens := strings.Split(path, "/") - log.Debug().Msgf("TOKENS %v", tokens) - ee := strings.Split(tokens[len(tokens)-1], "_") - return ee[0] + "/" + ee[1] -} - func (b *Benchmark) benchFile() string { r := b.GetTable().GetSelectedRowIndex() return ui.TrimCell(b.GetTable().SelectTable, r, 7) @@ -75,6 +60,12 @@ func (b *Benchmark) benchFile() string { // ---------------------------------------------------------------------------- // Helpers... +func fileToSubject(path string) string { + tokens := strings.Split(path, "/") + ee := strings.Split(tokens[len(tokens)-1], "_") + return ee[0] + "/" + ee[1] +} + func benchDir(cfg *config.Config) string { return filepath.Join(perf.K9sBenchDir, cfg.K9s.CurrentCluster) } diff --git a/internal/view/browser.go b/internal/view/browser.go index 4dfe8d95..a53fbc2f 100644 --- a/internal/view/browser.go +++ b/internal/view/browser.go @@ -34,9 +34,9 @@ type Browser struct { namespaces map[int]string gvr client.GVR - envFn EnvFunc meta metav1.APIResource accessor dao.Accessor + envFn EnvFunc contextFn ContextFunc bindKeysFn BindKeysFunc cancelFn context.CancelFunc @@ -72,7 +72,6 @@ func (b *Browser) Init(ctx context.Context) error { b.bindKeysFn(b.Actions()) } b.BaseTitle = b.meta.Kind - b.SetTitle(" [orange:i:]LOADING... ") b.accessor, err = dao.AccessorFor(b.app.factory, b.gvr) if err != nil { return err @@ -85,6 +84,7 @@ func (b *Browser) Init(ctx context.Context) error { b.Select(1, 0) } b.GetModel().AddListener(b) + b.App().Status(ui.FlashWarn, "Loading...") return nil } @@ -181,6 +181,23 @@ func (b *Browser) GVR() string { return string(b.gvr) } // GetTable returns the underlying table. func (b *Browser) GetTable() *Table { return b.Table } +// TableLoadChanged notifies view something went south. +func (b *Browser) TableLoadFailed(err error) { + b.app.QueueUpdateDraw(func() { + b.app.Flash().Err(err) + b.App().ClearStatus() + }) +} + +// TableDataChanged notifies view new data is available. +func (b *Browser) TableDataChanged(data render.TableData) { + b.app.QueueUpdateDraw(func() { + b.refreshActions() + b.Update(data) + b.App().ClearStatus() + }) +} + // ---------------------------------------------------------------------------- // Actions()... @@ -218,6 +235,7 @@ func (b *Browser) enterCmd(evt *tcell.EventKey) *tcell.EventKey { func (b *Browser) refreshCmd(*tcell.EventKey) *tcell.EventKey { b.app.Flash().Info("Refreshing...") b.refresh() + return nil } @@ -226,7 +244,6 @@ func (b *Browser) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { if len(selections) == 0 { return evt } - log.Debug().Msgf("DEL SELECTIONS %#v", selections) b.Stop() defer b.Start() @@ -235,52 +252,57 @@ func (b *Browser) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { if len(selections) > 1 { msg = fmt.Sprintf("Delete %d marked %s?", len(selections), b.gvr) } - - cancelFn := func() {} if dao.IsK9sMeta(b.meta) { - dialog.ShowConfirm(b.app.Content.Pages, "Confirm Delete", msg, func() { - b.ShowDeleted() - if len(selections) > 1 { - b.app.Flash().Infof("Delete %d marked %s", len(selections), b.gvr) - } else { - b.app.Flash().Infof("Delete resource %s %s", b.gvr, selections[0]) - } - for _, sel := range selections { - if err := b.accessor.(dao.Nuker).Delete(sel, true, true); err != nil { - b.app.Flash().Errf("Delete failed with `%s", err) - } else { - b.GetTable().DeleteMark(sel) - } - } - b.refresh() - b.SelectRow(1, true) - }, cancelFn) + b.simpleDelete(selections, msg) return nil } - - dialog.ShowDelete(b.app.Content.Pages, msg, func(cascade, force bool) { - b.ShowDeleted() - if len(selections) > 1 { - b.app.Flash().Infof("Delete %d marked %s", len(selections), b.gvr) - } else { - b.app.Flash().Infof("Delete resource %s %s", b.gvr, selections[0]) - } - for _, sel := range selections { - if err := b.accessor.(dao.Nuker).Delete(sel, cascade, force); err != nil { - b.app.Flash().Errf("Delete failed with `%s", err) - } else { - b.app.factory.DeleteForwarder(sel) - b.GetTable().DeleteMark(sel) - } - } - b.refresh() - b.SelectRow(1, true) - }, cancelFn) + b.resourceDelete(selections, msg) } return nil } +func (b *Browser) simpleDelete(selections []string, msg string) { + dialog.ShowConfirm(b.app.Content.Pages, "Confirm Delete", msg, func() { + b.ShowDeleted() + if len(selections) > 1 { + b.app.Flash().Infof("Delete %d marked %s", len(selections), b.gvr) + } else { + b.app.Flash().Infof("Delete resource %s %s", b.gvr, selections[0]) + } + for _, sel := range selections { + if err := b.accessor.(dao.Nuker).Delete(sel, true, true); err != nil { + b.app.Flash().Errf("Delete failed with `%s", err) + } else { + b.GetTable().DeleteMark(sel) + } + } + b.refresh() + b.SelectRow(1, true) + }, func() {}) +} + +func (b *Browser) resourceDelete(selections []string, msg string) { + dialog.ShowDelete(b.app.Content.Pages, msg, func(cascade, force bool) { + b.ShowDeleted() + if len(selections) > 1 { + b.app.Flash().Infof("Delete %d marked %s", len(selections), b.gvr) + } else { + b.app.Flash().Infof("Delete resource %s %s", b.gvr, selections[0]) + } + for _, sel := range selections { + if err := b.accessor.(dao.Nuker).Delete(sel, cascade, force); err != nil { + b.app.Flash().Errf("Delete failed with `%s", err) + } else { + b.app.factory.DeleteForwarder(sel) + b.GetTable().DeleteMark(sel) + } + } + b.refresh() + b.SelectRow(1, true) + }, func() {}) +} + func (b *Browser) describeCmd(evt *tcell.EventKey) *tcell.EventKey { path := b.GetSelectedItem() if path == "" { @@ -299,18 +321,7 @@ func (b *Browser) describeResource(app *App, _, _, sel string) { return } - details := NewDetails("Describe") - ctx := context.WithValue(context.Background(), internal.KeyApp, b.App()) - if err := details.Init(ctx); err != nil { - log.Error().Err(err).Msg("Details init failed") - return - } - details.SetSubject(sel) - details.SetTextColor(b.app.Styles.FgColor()) - details.Update(yaml) - // BOZO!! - // details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, yaml)) - // details.ScrollToBeginning() + details := NewDetails(b.App(), "Describe", sel).Update(yaml) if err := b.app.inject(details); err != nil { b.app.Flash().Err(err) } @@ -322,7 +333,6 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } - log.Debug().Msgf("------ NAMESPACES %q vs %q", path, b.GetModel().GetNamespace()) o, err := b.app.factory.Get(string(b.gvr), path, labels.Everything()) if err != nil { b.app.Flash().Errf("Unable to get resource %q -- %s", b.gvr, err) @@ -335,11 +345,7 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - details := NewDetails("YAML") - details.SetSubject(path) - details.SetTextColor(b.app.Styles.FgColor()) - details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, raw)) - details.ScrollToBeginning() + details := NewDetails(b.app, "YAML", path).Update(raw) if err := b.app.inject(details); err != nil { b.App().Flash().Err(err) } @@ -399,7 +405,6 @@ func (b *Browser) setNamespace(ns string) { if ns == render.NamespaceAll { ns = render.AllNamespaces } - log.Debug().Msgf("!!!!!! SETTING NS %q", ns) b.GetModel().SetNamespace(ns) } @@ -427,21 +432,6 @@ func (b *Browser) switchNamespaceCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } -// TableLoadChanged notifies view something went south. -func (b *Browser) TableLoadFailed(err error) { - b.app.QueueUpdateDraw(func() { - b.app.Flash().Err(err) - }) -} - -// TableDataChanged notifies view new data is available. -func (b *Browser) TableDataChanged(data render.TableData) { - b.app.QueueUpdateDraw(func() { - b.refreshActions() - b.Update(data) - }) -} - func (b *Browser) defaultContext() context.Context { ctx := context.Background() diff --git a/internal/view/details.go b/internal/view/details.go index 21efa517..b1c1c135 100644 --- a/internal/view/details.go +++ b/internal/view/details.go @@ -25,21 +25,20 @@ type Details struct { } // NewDetails returns a details viewer. -func NewDetails(title string) *Details { - return &Details{ +func NewDetails(app *App, title, subject string) *Details { + d := Details{ TextView: tview.NewTextView(), + app: app, title: title, + subject: subject, actions: make(ui.KeyActions), } + + return &d } // Init initializes the viewer. -func (d *Details) Init(ctx context.Context) error { - var err error - if d.app, err = extractApp(ctx); err != nil { - return err - } - +func (d *Details) Init(_ context.Context) error { if d.title != "" { d.SetBorder(true) } @@ -68,10 +67,12 @@ func (d *Details) StylesChanged(s *config.Styles) { d.Update(d.buff) } -func (d *Details) Update(buff string) { +func (d *Details) Update(buff string) *Details { d.buff = buff d.SetText(colorizeYAML(d.app.Styles.Views().Yaml, buff)) d.ScrollToBeginning() + + return d } func (d *Details) Actions() ui.KeyActions { @@ -107,10 +108,10 @@ func (d *Details) keyboard(evt *tcell.EventKey) *tcell.EventKey { if key == tcell.KeyRune { key = tcell.Key(evt.Rune()) } - if a, ok := d.actions[key]; ok { return a.Action(evt) } + return evt } diff --git a/internal/view/log.go b/internal/view/log.go index 2074a9d9..cc84f15b 100644 --- a/internal/view/log.go +++ b/internal/view/log.go @@ -71,16 +71,15 @@ func (l *Log) Init(ctx context.Context) (err error) { l.indicator = NewLogIndicator(l.app.Styles) l.AddItem(l.indicator, 1, 1, false) - l.logs = NewDetails("") - l.logs.SetBorder(false) - l.logs.SetDynamicColors(true) - l.logs.SetTextColor(config.AsColor(l.app.Styles.Views().Log.FgColor)) - l.logs.SetBackgroundColor(config.AsColor(l.app.Styles.Views().Log.BgColor)) - l.logs.SetWrap(true) - l.logs.SetMaxBuffer(l.app.Config.K9s.LogBufferSize) + l.logs = NewDetails(l.app, "", "") if err = l.logs.Init(ctx); err != nil { return err } + l.logs.SetWrap(false) + l.logs.SetMaxBuffer(l.app.Config.K9s.LogBufferSize) + l.logs.SetTextColor(config.AsColor(l.app.Styles.Views().Log.FgColor)) + l.logs.SetBackgroundColor(config.AsColor(l.app.Styles.Views().Log.BgColor)) + l.ansiWriter = tview.ANSIWriter(l.logs, l.app.Styles.Views().Log.FgColor, l.app.Styles.Views().Log.BgColor) l.AddItem(l.logs, 0, 1, true) l.bindKeys() diff --git a/internal/view/node.go b/internal/view/node.go index 0d6548b9..67ef5955 100644 --- a/internal/view/node.go +++ b/internal/view/node.go @@ -4,7 +4,6 @@ import ( "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/ui" "github.com/gdamore/tcell" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -46,8 +45,8 @@ func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey { } sel := n.GetTable().GetSelectedItem() - log.Debug().Msgf("------ VIEW NODE %q", sel) - o, err := n.App().factory.Client().DynDialOrDie().Resource(client.GVR(n.GVR()).AsGVR()).Get(sel, metav1.GetOptions{}) + gvr := client.GVR(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) return nil @@ -59,11 +58,7 @@ func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - details := NewDetails("YAML") - details.SetSubject(sel) - details.SetTextColor(n.App().Styles.FgColor()) - details.SetText(colorizeYAML(n.App().Styles.Views().Yaml, raw)) - details.ScrollToBeginning() + details := NewDetails(n.App(), "YAML", sel).Update(raw) if err := n.App().inject(details); err != nil { n.App().Flash().Err(err) } diff --git a/internal/view/port_forward.go b/internal/view/port_forward.go index 4e16cdc8..a8ddffd1 100644 --- a/internal/view/port_forward.go +++ b/internal/view/port_forward.go @@ -67,10 +67,10 @@ func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey { func (p *PortForward) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey { if p.bench != nil { log.Debug().Msg(">>> Benchmark cancelFned!!") - p.App().status(ui.FlashErr, "Benchmark Camceled!") + p.App().Status(ui.FlashErr, "Benchmark Camceled!") p.bench.Cancel() } - p.App().StatusReset() + p.App().ClearStatus() return nil } @@ -97,11 +97,11 @@ func (p *PortForward) benchCmd(evt *tcell.EventKey) *tcell.EventKey { var err error if p.bench, err = perf.NewBenchmark(base, cfg); err != nil { p.App().Flash().Errf("Bench failed %v", err) - p.App().StatusReset() + p.App().ClearStatus() return nil } - p.App().status(ui.FlashWarn, "Benchmark in progress...") + p.App().Status(ui.FlashWarn, "Benchmark in progress...") log.Debug().Msg("Bench starting...") go p.runBenchmark() @@ -113,15 +113,15 @@ func (p *PortForward) runBenchmark() { log.Debug().Msg("Bench Completed!") p.App().QueueUpdate(func() { if p.bench.Canceled() { - p.App().status(ui.FlashInfo, "Benchmark cancelFned") + p.App().Status(ui.FlashInfo, "Benchmark canceled") } else { - p.App().status(ui.FlashInfo, "Benchmark Completed!") + p.App().Status(ui.FlashInfo, "Benchmark Completed!") p.bench.Cancel() } p.bench = nil go func() { <-time.After(2 * time.Second) - p.App().QueueUpdate(func() { p.App().StatusReset() }) + p.App().QueueUpdate(func() { p.App().ClearStatus() }) }() }) }) diff --git a/internal/view/secret.go b/internal/view/secret.go index 6dfbaeaf..8fe543aa 100644 --- a/internal/view/secret.go +++ b/internal/view/secret.go @@ -62,11 +62,7 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey { return nil } - details := NewDetails("Decoder") - details.SetSubject(path) - details.SetTextColor(s.App().Styles.FgColor()) - details.SetText(colorizeYAML(s.App().Styles.Views().Yaml, string(raw))) - details.ScrollToBeginning() + details := NewDetails(s.App(), "Secret Decoder", path).Update(string(raw)) if err := s.App().inject(details); err != nil { s.App().Flash().Err(err) } diff --git a/internal/view/svc.go b/internal/view/svc.go index 0cd29f34..665bff85 100644 --- a/internal/view/svc.go +++ b/internal/view/svc.go @@ -67,10 +67,10 @@ func (s *Service) showPods(app *App, ns, gvr, path string) { func (s *Service) benchStopCmd(evt *tcell.EventKey) *tcell.EventKey { if s.bench != nil { log.Debug().Msg(">>> Benchmark canceled!!") - s.App().status(ui.FlashErr, "Benchmark Canceled!") + s.App().Status(ui.FlashErr, "Benchmark Canceled!") s.bench.Cancel() } - s.App().StatusReset() + s.App().ClearStatus() return nil } @@ -136,13 +136,14 @@ func (s *Service) benchCmd(evt *tcell.EventKey) *tcell.EventKey { } if err := s.runBenchmark(port, cfg); err != nil { s.App().Flash().Errf("Benchmark failed %v", err) - s.App().StatusReset() + s.App().ClearStatus() s.bench = nil } return nil } +// BOZO!! Refactor used by forwards func (s *Service) runBenchmark(port string, cfg config.BenchConfig) error { if cfg.HTTP.Host == "" { return fmt.Errorf("Invalid benchmark host %q", cfg.HTTP.Host) @@ -154,7 +155,7 @@ func (s *Service) runBenchmark(port string, cfg config.BenchConfig) error { return err } - s.App().status(ui.FlashWarn, "Benchmark in progress...") + s.App().Status(ui.FlashWarn, "Benchmark in progress...") log.Debug().Msg("Bench starting...") go s.bench.Run(s.App().Config.K9s.CurrentCluster, s.benchDone) @@ -165,9 +166,9 @@ func (s *Service) benchDone() { log.Debug().Msg("Bench Completed!") s.App().QueueUpdate(func() { if s.bench.Canceled() { - s.App().status(ui.FlashInfo, "Benchmark canceled") + s.App().Status(ui.FlashInfo, "Benchmark canceled") } else { - s.App().status(ui.FlashInfo, "Benchmark Completed!") + s.App().Status(ui.FlashInfo, "Benchmark Completed!") s.bench.Cancel() } s.bench = nil @@ -178,6 +179,6 @@ func (s *Service) benchDone() { func benchTimedOut(app *App) { <-time.After(2 * time.Second) app.QueueUpdate(func() { - app.StatusReset() + app.ClearStatus() }) } diff --git a/internal/view/table.go b/internal/view/table.go index e4ecd792..c223a50c 100644 --- a/internal/view/table.go +++ b/internal/view/table.go @@ -31,7 +31,6 @@ func (t *Table) Init(ctx context.Context) (err error) { ctx = context.WithValue(ctx, internal.KeyStyles, t.app.Styles) t.Table.Init(ctx) t.bindKeys() - t.GetModel().SetRefreshRate(time.Duration(t.app.Config.K9s.GetRefreshRate()) * time.Second) return nil diff --git a/internal/watch/factory.go b/internal/watch/factory.go index 08538969..7b89931e 100644 --- a/internal/watch/factory.go +++ b/internal/watch/factory.go @@ -74,7 +74,10 @@ func (f *Factory) Get(gvr, path string, sel labels.Selector) (runtime.Object, er // WaitForCachesync waits for all factories to update their cache. func (f *Factory) WaitForCacheSync() { for _, fac := range f.factories { - fac.WaitForCacheSync(f.stopChan) + m := fac.WaitForCacheSync(f.stopChan) + for k, v := range m { + log.Debug().Msgf("CACHE -- Loaded %q:%v", k, v) + } } } @@ -98,12 +101,10 @@ func (f *Factory) Terminate() { // RegisterForwarder registers a new portforward for a given container. func (f *Factory) AddForwarder(pf Forwarder) { f.forwarders[pf.Path()] = pf - f.forwarders.Dump() } // DeleteForwarder deletes portforward for a given container. func (f *Factory) DeleteForwarder(path string) { - f.forwarders.Dump() fwd, ok := f.forwarders[path] if !ok { log.Warn().Msgf("Unable to delete portForward %q", path) @@ -111,7 +112,6 @@ func (f *Factory) DeleteForwarder(path string) { } fwd.Stop() delete(f.forwarders, path) - f.forwarders.Dump() } // Forwards returns all portforwards. @@ -146,10 +146,11 @@ func (f *Factory) isClusterWide() bool { return ok } -func (f *Factory) preload(ns string) { - // verbs := []string{"get", "list", "watch"} +func (f *Factory) preload(_ string) { + // BOZO!! + verbs := []string{"get", "list", "watch"} // _, _ = f.CanForResource(ns, "v1/pods", verbs...) - // _, _ = f.CanForResource(allNamespaces, "apiextensions.k8s.io/v1beta1/customresourcedefinitions", verbs...) + _, _ = f.CanForResource("", "apiextensions.k8s.io/v1beta1/customresourcedefinitions", verbs...) // _, _ = f.CanForResource(clusterScope, "rbac.authorization.k8s.io/v1/clusterroles", verbs...) // _, _ = f.CanForResource(allNamespaces, "rbac.authorization.k8s.io/v1/roles", verbs...) } diff --git a/internal/watch/forwarders.go b/internal/watch/forwarders.go index 762d0e70..da89f35e 100644 --- a/internal/watch/forwarders.go +++ b/internal/watch/forwarders.go @@ -41,7 +41,6 @@ func NewForwarders() Forwarders { // KillAll stops and delete all port-forwards. func (ff Forwarders) DeleteAll() { - ff.Dump() for k, f := range ff { log.Debug().Msgf("Deleting forwarder %s", f.Path()) f.Stop() @@ -51,9 +50,6 @@ func (ff Forwarders) DeleteAll() { // Kill stops and delete a port-forwards associated with pod. func (ff Forwarders) Kill(path string) int { - ff.Dump() - - log.Debug().Msgf("Delete port-forward %q", path) hasContainer := strings.Contains(path, ":") var stats int for k, f := range ff { @@ -63,7 +59,7 @@ func (ff Forwarders) Kill(path string) int { } if victim == path { stats++ - log.Debug().Msgf("Stopping and delete port-forward %s", k) + log.Debug().Msgf("Stop + Delete port-forward %s", k) f.Stop() delete(ff, k) }