fix hpas + cleanup
parent
15667435be
commit
f814661fa0
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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{},
|
||||
|
|
|
|||
|
|
@ -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 := "<unknown>"
|
||||
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 := "<unknown>"
|
||||
|
||||
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, "<unknown type>")
|
||||
}
|
||||
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 := "<unknown>"
|
||||
|
||||
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 "<unknown type>"
|
||||
}
|
||||
|
||||
func externalMetricsV2b2(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string {
|
||||
current := "<unknown>"
|
||||
|
||||
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 := "<unknown>"
|
||||
|
||||
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 := "<auto>"
|
||||
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 := "<unknown>"
|
||||
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 := "<unknown>"
|
||||
|
||||
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 := "<auto>"
|
||||
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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() })
|
||||
}()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue