checkpoint

mine
derailed 2019-12-14 16:18:36 -07:00
parent 6839578a8c
commit 4127da865f
74 changed files with 651 additions and 2089 deletions

View File

@ -299,8 +299,7 @@ issues:
# of integration: much better don't allow issues in new code. # of integration: much better don't allow issues in new code.
# Default is false. # Default is false.
new: false new: false
# Show only new issues created after git revision `REV` # Show only new issues created after git revision `REV`
new-from-rev: REV # new-from-rev: REV
# Show only new issues created in git patch with set file path. # Show only new issues created in git patch with set file path.
# new-from-patch: path/to/patch/file # new-from-patch: path/to/patch/file

View File

@ -1,7 +1,6 @@
package client package client
import ( import (
"fmt"
"path/filepath" "path/filepath"
"sync" "sync"
"time" "time"
@ -10,7 +9,6 @@ import (
authorizationv1 "k8s.io/api/authorization/v1" authorizationv1 "k8s.io/api/authorization/v1"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/version" "k8s.io/apimachinery/pkg/version"
"k8s.io/client-go/discovery/cached/disk" "k8s.io/client-go/discovery/cached/disk"
@ -26,53 +24,46 @@ const NA = "n/a"
var supportedMetricsAPIVersions = []string{"v1beta1"} var supportedMetricsAPIVersions = []string{"v1beta1"}
type ( // Authorizer checks what a user can or cannot do to a resource.
// Collection of empty interfaces. type Authorizer interface {
Collection []interface{} // CanI returns true if the user can use these actions for a given resource.
CanI(ns, gvr string, verbs []string) (bool, error)
}
// Cruder represent a crudable Kubernetes resource. // BOZO!! Refactor!
Cruder interface { // Connection represents a Kubenetes apiserver connection.
Get(ns string, name string) (interface{}, error) type Connection interface {
List(ns string) (Collection, error) Authorizer
Delete(ns string, name string) error
SetFieldSelector(string)
SetLabelSelector(string)
}
// Connection represents a Kubenetes apiserver connection. Config() *Config
Connection interface { DialOrDie() kubernetes.Interface
Config() *Config SwitchContextOrDie(ctx string)
DialOrDie() kubernetes.Interface NSDialOrDie() dynamic.NamespaceableResourceInterface
SwitchContextOrDie(ctx string) CachedDiscovery() (*disk.CachedDiscoveryClient, error)
NSDialOrDie() dynamic.NamespaceableResourceInterface RestConfigOrDie() *restclient.Config
CachedDiscovery() (*disk.CachedDiscoveryClient, error) MXDial() (*versioned.Clientset, error)
RestConfigOrDie() *restclient.Config DynDialOrDie() dynamic.Interface
MXDial() (*versioned.Clientset, error) HasMetrics() bool
DynDialOrDie() dynamic.Interface IsNamespaced(n string) bool
HasMetrics() bool SupportsResource(group string) bool
IsNamespaced(n string) bool ValidNamespaces() ([]v1.Namespace, error)
SupportsResource(group string) bool SupportsRes(grp string, versions []string) (string, bool, error)
ValidNamespaces() ([]v1.Namespace, error) ServerVersion() (*version.Info, error)
NodePods(node string) (*v1.PodList, error) FetchNodes() (*v1.NodeList, error)
SupportsRes(grp string, versions []string) (string, bool, error) CurrentNamespaceName() (string, error)
ServerVersion() (*version.Info, error) }
FetchNodes() (*v1.NodeList, error)
CurrentNamespaceName() (string, error)
CanI(ns, gvr string, verbs []string) (bool, error)
}
// APIClient represents a Kubernetes api client. // APIClient represents a Kubernetes api client.
APIClient struct { type APIClient struct {
client kubernetes.Interface client kubernetes.Interface
dClient dynamic.Interface dClient dynamic.Interface
nsClient dynamic.NamespaceableResourceInterface nsClient dynamic.NamespaceableResourceInterface
mxsClient *versioned.Clientset mxsClient *versioned.Clientset
cachedDiscovery *disk.CachedDiscoveryClient cachedDiscovery *disk.CachedDiscoveryClient
config *Config config *Config
useMetricServer bool useMetricServer bool
mx sync.Mutex mx sync.Mutex
} }
)
// InitConnectionOrDie initialize connection from command line args. // InitConnectionOrDie initialize connection from command line args.
// Checks for connectivity with the api server. // Checks for connectivity with the api server.
@ -143,20 +134,6 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
return nn.Items, nil return nn.Items, nil
} }
// NodePods returns a collection of all available pods on a given node.
func (a *APIClient) NodePods(node string) (*v1.PodList, error) {
panic("NYI")
const selFmt = "spec.nodeName=%s,status.phase!=%s,status.phase!=%s"
fieldSelector, err := fields.ParseSelector(fmt.Sprintf(selFmt, node, v1.PodSucceeded, v1.PodFailed))
if err != nil {
return nil, err
}
return a.DialOrDie().CoreV1().Pods("").List(metav1.ListOptions{
FieldSelector: fieldSelector.String(),
})
}
// IsNamespaced check on server if given resource is namespaced // IsNamespaced check on server if given resource is namespaced
func (a *APIClient) IsNamespaced(res string) bool { func (a *APIClient) IsNamespaced(res string) bool {
discovery, err := a.CachedDiscovery() discovery, err := a.CachedDiscovery()

View File

@ -7,7 +7,6 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/watch" "github.com/derailed/k9s/internal/watch"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
@ -24,9 +23,6 @@ var _ Loggable = &Container{}
// Logs tails a given container logs // Logs tails a given container logs
func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error { func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts LogOptions) error {
log.Debug().Msgf("CO TAILLOGS %#v", ctx)
log.Debug().Msgf("CO TAILLOGS %#v", opts)
fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory) fac, ok := ctx.Value(internal.KeyFactory).(*watch.Factory)
if !ok { if !ok {
return errors.New("Expecting an informer") return errors.New("Expecting an informer")
@ -37,7 +33,7 @@ func (c *Container) TailLogs(ctx context.Context, logChan chan<- string, opts Lo
} }
var po v1.Pod var po v1.Pod
if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil {
return err return err
} }

View File

@ -29,7 +29,7 @@ type Pod struct {
} }
var _ Accessor = &Pod{} var _ Accessor = &Pod{}
var _Loggable = &Pod{} var _ Loggable = &Pod{}
// Logs fetch container logs for a given pod and container. // Logs fetch container logs for a given pod and container.
func (p *Pod) Logs(path string, opts *v1.PodLogOptions) *restclient.Request { func (p *Pod) Logs(path string, opts *v1.PodLogOptions) *restclient.Request {
@ -84,7 +84,7 @@ func (p *Pod) logs(ctx context.Context, c chan<- string, opts LogOptions) error
} }
var po v1.Pod var po v1.Pod
if runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil { if err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &po); err != nil {
return err return err
} }
opts.Color = asColor(po.Name) opts.Color = asColor(po.Name)
@ -166,6 +166,7 @@ func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts L
} }
} }
// ----------------------------------------------------------------------------
// Helpers... // Helpers...
func loggableContainers(s v1.PodStatus) []string { func loggableContainers(s v1.PodStatus) []string {

View File

@ -46,8 +46,8 @@ func Reconcile(ctx context.Context, table render.TableData, gvr client.GVR) (ren
return table, err return table, err
} }
log.Debug().Msgf("Model returned [%d] items", len(oo)) log.Debug().Msgf("Model returned [%d] items", len(oo))
rows := make(render.Rows, len(oo)) rows := make(render.Rows, len(oo))
// BOZO!! Pass in header len to avoid recomputing the header.
if err := m.Model.Hydrate(oo, rows, m.Renderer); err != nil { if err := m.Model.Hydrate(oo, rows, m.Renderer); err != nil {
return table, err return table, err
} }

View File

@ -100,83 +100,60 @@ func Load(f *watch.Factory) error {
return loadCRDs(f, resMetas) return loadCRDs(f, resMetas)
} }
// BOZO!! Need contermeasure for direct commands!
func loadNonResource(m ResourceMetas) error { func loadNonResource(m ResourceMetas) error {
m["aliases"] = metav1.APIResource{ m["aliases"] = metav1.APIResource{
Name: "aliases", Name: "aliases",
SingularName: "alias", Kind: "Aliases",
Namespaced: false, Categories: []string{"k9s"},
Kind: "Aliases",
Verbs: []string{},
Categories: []string{"k9s"},
} }
m["contexts"] = metav1.APIResource{ m["contexts"] = metav1.APIResource{
Name: "contexts", Name: "contexts",
SingularName: "context", Kind: "Contexts",
Namespaced: false, ShortNames: []string{"ctx"},
Kind: "Contexts", Categories: []string{"k9s"},
ShortNames: []string{"ctx"},
Verbs: []string{},
Categories: []string{"k9s"},
} }
m["screendumps"] = metav1.APIResource{ m["screendumps"] = metav1.APIResource{
Name: "screendumps", Name: "screendumps",
SingularName: "screendump", Kind: "ScreenDumps",
Namespaced: false, ShortNames: []string{"sd"},
Kind: "ScreenDumps", Verbs: []string{"delete"},
ShortNames: []string{"sd"}, Categories: []string{"k9s"},
Verbs: []string{"delete"},
Categories: []string{"k9s"},
} }
m["benchmarks"] = metav1.APIResource{ m["benchmarks"] = metav1.APIResource{
Name: "benchmarks", Name: "benchmarks",
SingularName: "benchmark", Kind: "Benchmarks",
Namespaced: false, ShortNames: []string{"be"},
Kind: "Benchmarks", Verbs: []string{"delete"},
ShortNames: []string{"be"}, Categories: []string{"k9s"},
Verbs: []string{"delete"},
Categories: []string{"k9s"},
} }
m["portforwards"] = metav1.APIResource{ m["portforwards"] = metav1.APIResource{
Name: "portforwards", Name: "portforwards",
SingularName: "portforward", Namespaced: true,
Namespaced: true, Kind: "PortForwards",
Kind: "PortForwards", ShortNames: []string{"pf"},
ShortNames: []string{"pf"}, Verbs: []string{"delete"},
Verbs: []string{"delete"}, Categories: []string{"k9s"},
Categories: []string{"k9s"},
} }
// BOZO!! policies can't be launch on command
m["rbac"] = metav1.APIResource{ m["rbac"] = metav1.APIResource{
Name: "Rbac", Name: "Rbac",
SingularName: "Rbac", Kind: "RBAC",
Namespaced: false, Categories: []string{"k9s"},
Kind: "RBAC",
Categories: []string{"k9s"},
} }
// BOZO!! Containers can't be launch on command
m["containers"] = metav1.APIResource{ m["containers"] = metav1.APIResource{
Name: "containers", Name: "containers",
SingularName: "container", Kind: "Containers",
Namespaced: false, Categories: []string{"k9s"},
Kind: "Containers",
Verbs: []string{},
Categories: []string{"k9s"},
} }
m["users"] = metav1.APIResource{ m["users"] = metav1.APIResource{
Name: "users", Name: "users",
SingularName: "user", Kind: "User",
Namespaced: false, Categories: []string{"k9s"},
Kind: "User",
Verbs: []string{},
Categories: []string{"k9s"},
} }
m["groups"] = metav1.APIResource{ m["groups"] = metav1.APIResource{
Name: "groups", Name: "groups",
SingularName: "group", Kind: "group",
Namespaced: false, Categories: []string{"k9s"},
Kind: "group",
Verbs: []string{},
Categories: []string{"k9s"},
} }
return nil return nil

View File

@ -3,19 +3,19 @@ package internal
// ContextKey represents context key. // ContextKey represents context key.
type ContextKey string type ContextKey string
// A collection of context keys.
const ( const (
// Factory represents a factory context key.
KeyFactory ContextKey = "factory" KeyFactory ContextKey = "factory"
KeyLabels = "labels" KeyLabels ContextKey = "labels"
KeyFields = "fields" KeyFields ContextKey = "fields"
KeyTable = "table" KeyTable ContextKey = "table"
KeyDir = "dir" KeyDir ContextKey = "dir"
KeyPath = "path" KeyPath ContextKey = "path"
KeySubject = "subject" KeySubject ContextKey = "subject"
KeyGVR = "gvr" KeyGVR ContextKey = "gvr"
KeyForwards = "forwards" KeyForwards ContextKey = "forwards"
KeyContainers = "containers" KeyContainers ContextKey = "containers"
KeyBenchCfg = "benchcfg" KeyBenchCfg ContextKey = "benchcfg"
KeyAliases = "aliases" KeyAliases ContextKey = "aliases"
KeyUID = "uid" KeyUID ContextKey = "uid"
) )

View File

@ -68,8 +68,11 @@ func (c *Container) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) er
var index int var index int
for _, o := range oo { for _, o := range oo {
co := o.(ContainerRes) co, ok := o.(ContainerRes)
row, err := renderCoRow(co.Container.Name, index, coMetricsFor(co.Container, c.pod, mmx, true), re) if !ok {
return fmt.Errorf("expecting containerres but got `%T", o)
}
row, err := renderCoRow(co.Container.Name, coMetricsFor(co.Container, c.pod, mmx, true), re)
if err != nil { if err != nil {
return err return err
} }
@ -80,7 +83,7 @@ func (c *Container) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) er
return nil return nil
} }
func renderCoRow(n string, index int, pmx *ContainerWithMetrics, re Renderer) (render.Row, error) { func renderCoRow(n string, pmx *ContainerWithMetrics, re Renderer) (render.Row, error) {
var row render.Row var row render.Row
if err := re.Render(pmx, n, &row); err != nil { if err := re.Render(pmx, n, &row); err != nil {
return render.Row{}, err return render.Row{}, err
@ -99,7 +102,11 @@ func coMetricsFor(co v1.Container, po *v1.Pod, mmx *mv1beta1.PodMetrics, isInit
} }
func containerMetrics(n string, mx runtime.Object) *mv1beta1.ContainerMetrics { func containerMetrics(n string, mx runtime.Object) *mv1beta1.ContainerMetrics {
pmx := mx.(*mv1beta1.PodMetrics) pmx, ok := mx.(*mv1beta1.PodMetrics)
if !ok {
log.Error().Err(fmt.Errorf("expecting podmetrics but got `%T", mx))
return nil
}
for _, m := range pmx.Containers { for _, m := range pmx.Containers {
if m.Name == n { if m.Name == n {
return &m return &m

View File

@ -71,10 +71,6 @@ func (g *Generic) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) erro
if !ok { if !ok {
return fmt.Errorf("expecting RowRes but got %#v", o) return fmt.Errorf("expecting RowRes but got %#v", o)
} }
count := len(res.Cells)
if g.namespace == "" {
count++
}
if err := gr.Render(res.TableRow, g.namespace, &rr[i]); err != nil { if err := gr.Render(res.TableRow, g.namespace, &rr[i]); err != nil {
return err return err
} }

View File

@ -1,20 +1,39 @@
package model package model
import ( import (
"fmt"
"github.com/derailed/tview" "github.com/derailed/tview"
runewidth "github.com/mattn/go-runewidth" runewidth "github.com/mattn/go-runewidth"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
func extractFQN(o runtime.Object) string { func extractFQN(o runtime.Object) string {
u := o.(*unstructured.Unstructured) u, ok := o.(*unstructured.Unstructured)
m := u.Object["metadata"].(map[string]interface{}) if !ok {
if _, ok := m["namespace"]; !ok { log.Error().Err(fmt.Errorf("expecting unstructured but got %T", o))
return FQN("", m["name"].(string)) return "na"
} }
ns, n := m["namespace"].(string), m["name"].(string) m, ok := u.Object["metadata"].(map[string]interface{})
if !ok {
log.Error().Err(fmt.Errorf("expecting interface map for metadata but got %T", u.Object["metadata"]))
return "na"
}
n, ok := m["name"].(string)
if !ok {
log.Error().Err(fmt.Errorf("expecting interface map for name but got %T", m["name"]))
return "na"
}
ns, ok := m["namespace"].(string)
if !ok {
return FQN("", n)
}
return FQN(ns, n) return FQN(ns, n)
} }

View File

@ -2,6 +2,7 @@ package model
import ( import (
"context" "context"
"fmt"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
@ -29,8 +30,8 @@ func (n *Node) List(_ context.Context) ([]runtime.Object, error) {
} }
oo := make([]runtime.Object, len(nn.Items)) oo := make([]runtime.Object, len(nn.Items))
for i, no := range nn.Items { for i, n := range nn.Items {
o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&no) o, err := runtime.DefaultUnstructuredConverter.ToUnstructured(n)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -39,6 +40,20 @@ func (n *Node) List(_ context.Context) ([]runtime.Object, error) {
return oo, nil return oo, nil
} }
func nameFromMeta(m map[string]interface{}) string {
meta, ok := m["metadata"].(map[string]interface{})
if !ok {
return "n/a"
}
name, ok := meta["name"].(string)
if !ok {
return "n/a"
}
return name
}
// Hydrate returns nodes as rows. // Hydrate returns nodes as rows.
func (n *Node) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { func (n *Node) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
mx := client.NewMetricsServer(n.factory.Client()) mx := client.NewMetricsServer(n.factory.Client())
@ -47,23 +62,28 @@ func (n *Node) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
log.Warn().Err(err).Msg("No node metrics") log.Warn().Err(err).Msg("No node metrics")
} }
var index int for i, o := range oo {
for _, no := range oo { no, ok := o.(*unstructured.Unstructured)
o := no.(*unstructured.Unstructured) if !ok {
pods, err := n.nodePods(n.factory, o.Object["metadata"].(map[string]interface{})["name"].(string)) return fmt.Errorf("expecting unstructured but got %T", o)
}
pods, err := n.nodePods(n.factory, nameFromMeta(no.Object))
if err != nil { if err != nil {
return err return err
} }
var ( var (
row render.Row row render.Row
nmx = NodeWithMetrics{object: o, mx: nodeMetricsFor(o, mmx), pods: pods} nmx = NodeWithMetrics{
object: no,
mx: nodeMetricsFor(o, mmx),
pods: pods,
}
) )
if err := re.Render(&nmx, "", &row); err != nil { if err := re.Render(&nmx, "", &row); err != nil {
return err return err
} }
rr[index] = row rr[i] = row
index++
} }
return nil return nil
@ -87,8 +107,10 @@ func (n *Node) nodePods(f Factory, node string) ([]*v1.Pod, error) {
pods := make([]*v1.Pod, 0, len(pp)) pods := make([]*v1.Pod, 0, len(pp))
for _, p := range pp { for _, p := range pp {
o := p.(*unstructured.Unstructured) o, ok := p.(*unstructured.Unstructured)
if !ok {
return nil, fmt.Errorf("expecting unstructured but got %T", p)
}
var pod v1.Pod var pod v1.Pod
err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &pod) err := runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &pod)
if err != nil { if err != nil {

View File

@ -2,6 +2,7 @@ package model
import ( import (
"context" "context"
"fmt"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
@ -37,8 +38,14 @@ func (p *Pod) List(ctx context.Context) ([]runtime.Object, error) {
var res []runtime.Object var res []runtime.Object
for _, o := range oo { for _, o := range oo {
u := o.(*unstructured.Unstructured) u, ok := o.(*unstructured.Unstructured)
spec := u.Object["spec"].(map[string]interface{}) if !ok {
return res, fmt.Errorf("expecting unstructured but got `%T", o)
}
spec, ok := u.Object["spec"].(map[string]interface{})
if !ok {
return res, fmt.Errorf("expecting interface map but got `%T", o)
}
if nodeName == "" || spec["nodeName"] == nodeName { if nodeName == "" || spec["nodeName"] == nodeName {
res = append(res, o) res = append(res, o)
} }

View File

@ -9,16 +9,12 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
// PortForward represents a portforward model. // PortForward represents a portforward model.
type PortForward struct { type PortForward struct {
Resource Resource
pod *v1.Pod
} }
// List returns a collection of screen dumps. // List returns a collection of screen dumps.
@ -51,7 +47,6 @@ func (c *PortForward) List(ctx context.Context) ([]runtime.Object, error) {
// Hydrate returns a pod as container rows. // Hydrate returns a pod as container rows.
func (c *PortForward) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { func (c *PortForward) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
for i, o := range oo { for i, o := range oo {
log.Debug().Msgf("PortFWD GOT %#v", o)
res, ok := o.(render.ForwardRes) res, ok := o.(render.ForwardRes)
if !ok { if !ok {
return fmt.Errorf("expecting a forwardres but got %T", o) return fmt.Errorf("expecting a forwardres but got %T", o)

View File

@ -76,16 +76,15 @@ func (r *Rbac) loadRoleBinding(path string) ([]runtime.Object, error) {
} }
var rb rbacv1.RoleBinding var rb rbacv1.RoleBinding
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) if err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb); err != nil {
if err != nil {
return nil, err return nil, err
} }
if rb.RoleRef.Kind == "ClusterRole" { if rb.RoleRef.Kind == "ClusterRole" {
kind := "rbac.authorization.k8s.io/v1/clusterroles" kind := "rbac.authorization.k8s.io/v1/clusterroles"
o, err := r.factory.Get(kind, client.FQN("-", rb.RoleRef.Name), labels.Everything()) o, e := r.factory.Get(kind, client.FQN("-", rb.RoleRef.Name), labels.Everything())
if err != nil { if e != nil {
return nil, err return nil, e
} }
var cr rbacv1.ClusterRole var cr rbacv1.ClusterRole
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr) err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
@ -186,7 +185,11 @@ func upsert(rr []runtime.Object, p *render.PolicyRes) []runtime.Object {
// Find locates a row by id. Retturns false is not found. // Find locates a row by id. Retturns false is not found.
func find(rr []runtime.Object, res string) (int, bool) { func find(rr []runtime.Object, res string) (int, bool) {
for i, r := range rr { for i, r := range rr {
p := r.(*render.PolicyRes) p, ok := r.(*render.PolicyRes)
if !ok {
log.Error().Err(fmt.Errorf("expecting policyres but got `%T", r))
return 0, false
}
if p.Resource == res { if p.Resource == res {
return i, true return i, true
} }

View File

@ -7,15 +7,12 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
// ScreenDump represents a collections of screendumps. // ScreenDump represents a collections of screendumps.
type ScreenDump struct { type ScreenDump struct {
Resource Resource
pod *v1.Pod
} }
// List returns a collection of screen dumps. // List returns a collection of screen dumps.

View File

@ -7,7 +7,6 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -58,58 +57,59 @@ func (s *Subject) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) erro
return nil return nil
} }
func (s *Subject) fetchClusterRoleBindings() ([]runtime.Object, error) { // BOZO!!
oo, err := s.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything()) // func (s *Subject) fetchClusterRoleBindings() ([]runtime.Object, error) {
if err != nil { // oo, err := s.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything())
return nil, err // if err != nil {
} // return nil, err
// }
rows := make([]runtime.Object, 0, len(oo)) // rows := make([]runtime.Object, 0, len(oo))
for _, o := range oo { // for _, o := range oo {
var crb rbacv1.ClusterRoleBinding // var crb rbacv1.ClusterRoleBinding
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb) // err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
for _, subject := range crb.Subjects { // for _, subject := range crb.Subjects {
if subject.Kind != s.subjectKind { // if subject.Kind != s.subjectKind {
continue // continue
} // }
rows = append(rows, SubjectRes{ // rows = append(rows, SubjectRes{
id: subject.Name, // id: subject.Name,
fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name}, // fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name},
}) // })
} // }
} // }
return rows, nil // return rows, nil
} // }
func (s *Subject) fetchRoleBindings() ([]runtime.Object, error) { // func (s *Subject) fetchRoleBindings() ([]runtime.Object, error) {
oo, err := s.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything()) // oo, err := s.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything())
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
rows := make([]runtime.Object, 0, len(oo)) // rows := make([]runtime.Object, 0, len(oo))
for _, o := range oo { // for _, o := range oo {
var rb rbacv1.RoleBinding // var rb rbacv1.RoleBinding
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb) // err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb)
if err != nil { // if err != nil {
return nil, err // return nil, err
} // }
for _, subject := range rb.Subjects { // for _, subject := range rb.Subjects {
if subject.Kind == s.subjectKind { // if subject.Kind == s.subjectKind {
rows = append(rows, SubjectRes{ // rows = append(rows, SubjectRes{
id: subject.Name, // id: subject.Name,
fields: render.Fields{subject.Name, "RoleBinding", rb.Name}, // fields: render.Fields{subject.Name, "RoleBinding", rb.Name},
}) // })
} // }
} // }
} // }
return rows, nil // return rows, nil
} // }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -67,7 +67,7 @@ type Lister interface {
List(context.Context) ([]runtime.Object, error) List(context.Context) ([]runtime.Object, error)
// Hydrate converts resource rows into tabular data. // Hydrate converts resource rows into tabular data.
Hydrate([]runtime.Object, render.Rows, Renderer) error Hydrate(oo []runtime.Object, rr render.Rows, r Renderer) error
} }
type Factory interface { type Factory interface {

View File

@ -10,31 +10,22 @@ package render
// colorerUCs []colorerUC // colorerUCs []colorerUC
// ) // )
// func TestNSColorer(t *testing.T) { // func TestDefaultColorer(t *testing.T) {
// var ( // uu := map[string]struct {
// ns = Row{Fields: Fields{"blee", "Active"}} // re render.RowEvent
// term = Row{Fields: Fields{"blee", Terminating}} // e tcell.Color
// dead = Row{Fields: Fields{"blee", "Inactive"}} // }{
// ) // "default": {render.RowEvent{}, ui.StdColor},
// "add": {render.RowEvent{Kind: render.EventAdd}, ui.AddColor},
// uu := colorerUCs{ // "delete": {render.RowEvent{Kind: render.EventDelete}, ui.KillColor},
// // Add AllNS // "update": {render.RowEvent{Kind: render.EventUpdate}, ui.ModColor},
// {"", RowEvent{
// Kind: EventAdd,
// Row: ns,
// },
// AddColor},
// // Mod AllNS
// {"", RowEvent{Kind: EventUpdate, Row: ns}, ModColor},
// // MoChange AllNS
// {"", RowEvent{Kind: EventUnchanged, Row: ns}, StdColor},
// // Bust NS
// {"", RowEvent{Kind: EventUnchanged, Row: term}, ErrColor},
// // Bust NS
// {"", RowEvent{Kind: EventUnchanged, Row: dead}, ErrColor},
// } // }
// for _, u := range uu {
// assert.Equal(t, u.e, nsColorer(u.ns, u.r)) // for k := range uu {
// u := uu[k]
// t.Run(k, func(t *testing.T) {
// assert.Equal(t, u.e, ui.DefaultColorer("", u.re))
// })
// } // }
// } // }

View File

@ -131,7 +131,7 @@ func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p metric
mem: ToMi(mem), mem: ToMi(mem),
} }
rcpu, rmem := containerResources(co) rcpu, rmem := containerResources(*co)
if rcpu != nil { if rcpu != nil {
p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue()))) p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue())))
} }
@ -177,19 +177,9 @@ func toState(s v1.ContainerState) string {
} }
} }
func toRes(r v1.ResourceList) (string, string) {
cpu, mem := r[v1.ResourceCPU], r[v1.ResourceMemory]
return ToMillicore(cpu.MilliValue()), ToMi(ToMB(mem.Value()))
}
func probe(p *v1.Probe) string { func probe(p *v1.Probe) string {
if p == nil { if p == nil {
return "off" return "off"
} }
return "on" return "on"
} }
func asMi(v int64) float64 {
return float64(v) / 1024 * 1024
}

View File

@ -38,27 +38,21 @@ func TestContextRender(t *testing.T) {
} }
var r render.Context var r render.Context
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
row := render.NewRow(4) row := render.NewRow(4)
err := r.Render(u.ctx, "", &row) err := r.Render(uc.ctx, "", &row)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, u.e, row) assert.Equal(t, uc.e, row)
}) })
} }
} }
// ----------------------------------------------------------------------------
// Helpers... // Helpers...
func newContext(n string) *api.Context {
return &api.Context{
Cluster: n,
AuthInfo: "blee",
Namespace: "zorg",
}
}
type config struct{} type config struct{}
func (k config) CurrentContextName() (string, error) { func (k config) CurrentContextName() (string, error) {

View File

@ -32,17 +32,36 @@ func (CustomResourceDefinition) Render(o interface{}, ns string, r *Row) error {
return fmt.Errorf("Expected CustomResourceDefinition, but got %T", o) return fmt.Errorf("Expected CustomResourceDefinition, but got %T", o)
} }
meta := crd.Object["metadata"].(map[string]interface{}) meta, ok := crd.Object["metadata"].(map[string]interface{})
t, err := time.Parse(time.RFC3339, meta["creationTimestamp"].(string)) if !ok {
return fmt.Errorf("expecting an interface map but got %T", crd.Object["metadata"])
}
t, err := time.Parse(time.RFC3339, extractMetaField(meta, "creationTimestamp"))
if err != nil { if err != nil {
log.Error().Err(err).Msgf("Fields timestamp %v", err) log.Error().Err(err).Msgf("Fields timestamp %v", err)
} }
r.ID = FQN(ClusterScope, meta["name"].(string)) r.ID = FQN(ClusterScope, extractMetaField(meta, "name"))
r.Fields = Fields{ r.Fields = Fields{
meta["name"].(string), extractMetaField(meta, "name"),
toAge(metav1.Time{t}), toAge(metav1.Time{Time: t}),
} }
return nil return nil
} }
func extractMetaField(m map[string]interface{}, field string) string {
f, ok := m[field]
if !ok {
log.Error().Err(fmt.Errorf("failed to extract field from meta %s", field))
return "n/a"
}
fs, ok := f.(string)
if !ok {
log.Error().Err(fmt.Errorf("failed to extract string from field %s", field))
return "n/a"
}
return fs
}

View File

@ -53,11 +53,12 @@ func TestDelta(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
d := render.NewDeltaRow(u.o, u.n, false) d := render.NewDeltaRow(uc.o, uc.n, false)
assert.Equal(t, u.e, d) assert.Equal(t, uc.e, d)
assert.Equal(t, u.blank, d.IsBlank()) assert.Equal(t, uc.blank, d.IsBlank())
}) })
} }
} }
@ -80,9 +81,10 @@ func TestDeltaBlank(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.r.IsBlank()) assert.Equal(t, uc.e, uc.r.IsBlank())
}) })
} }
} }

View File

@ -33,7 +33,7 @@ func (Endpoints) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (Endpoints) Render(o interface{}, ns string, r *Row) error { func (e Endpoints) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected Endpoints, but got %T", o) return fmt.Errorf("Expected Endpoints, but got %T", o)
@ -44,16 +44,16 @@ func (Endpoints) Render(o interface{}, ns string, r *Row) error {
return err return err
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(ep.ObjectMeta)
r.Fields = make(Fields, 0, len(e.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, ep.Namespace) r.Fields = append(r.Fields, ep.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
ep.Name, ep.Name,
missing(toEPs(ep.Subsets)), missing(toEPs(ep.Subsets)),
toAge(ep.ObjectMeta.CreationTimestamp), toAge(ep.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(ep.ObjectMeta), fields
return nil return nil
} }

View File

@ -53,7 +53,7 @@ func (Event) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (Event) Render(o interface{}, ns string, r *Row) error { func (e Event) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected Event, but got %T", o) return fmt.Errorf("Expected Event, but got %T", o)
@ -64,18 +64,18 @@ func (Event) Render(o interface{}, ns string, r *Row) error {
return err return err
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(ev.ObjectMeta)
r.Fields = make(Fields, 0, len(e.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, ev.Namespace) r.Fields = append(r.Fields, ev.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
ev.Name, ev.Name,
ev.Reason, ev.Reason,
ev.Source.Component, ev.Source.Component,
strconv.Itoa(int(ev.Count)), strconv.Itoa(int(ev.Count)),
Truncate(ev.Message, 80), Truncate(ev.Message, 80),
toAge(ev.LastTimestamp)) toAge(ev.LastTimestamp))
r.ID, r.Fields = MetaFQN(ev.ObjectMeta), fields
return nil return nil
} }

View File

@ -31,10 +31,11 @@ func TestSort(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
u.re.Sort("", u.col, u.asc) uc.re.Sort("", uc.col, uc.asc)
assert.Equal(t, u.e, u.re) assert.Equal(t, uc.e, uc.re)
}) })
} }
} }
@ -50,9 +51,10 @@ func TestDefaultColorer(t *testing.T) {
"std": {100, render.StdColor}, "std": {100, render.StdColor},
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, render.DefaultColorer("", render.RowEvent{})) assert.Equal(t, uc.e, render.DefaultColorer("", render.RowEvent{}))
}) })
} }
} }

View File

@ -1,7 +1,6 @@
package render package render
import ( import (
"fmt"
"testing" "testing"
"time" "time"
@ -49,9 +48,10 @@ func TestToAge(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, toAge(metav1.Time{Time: u.t})[:2]) assert.Equal(t, uc.e, toAge(metav1.Time{Time: uc.t})[:2])
}) })
} }
} }
@ -67,10 +67,11 @@ func TestToAgeHuma(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
ti := toAge(metav1.Time{Time: u.t}) ti := toAge(metav1.Time{Time: uc.t})
assert.Equal(t, u.e, toAgeHuman(ti)[:2]) assert.Equal(t, uc.e, toAgeHuman(ti)[:2])
}) })
} }
} }
@ -86,9 +87,10 @@ func TestJoin(t *testing.T) {
"sparse": {[]string{"a", "", "c"}, "a,c"}, "sparse": {[]string{"a", "", "c"}, "a,c"},
} }
for k, v := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, v.e, join(v.i, ",")) assert.Equal(t, uc.e, join(uc.i, ","))
}) })
} }
} }
@ -195,11 +197,12 @@ func TestToSelector(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
s := toSelector(u.m) s := toSelector(uc.m)
var match bool var match bool
for _, e := range u.e { for _, e := range uc.e {
if e == s { if e == s {
match = true match = true
} }
@ -225,9 +228,10 @@ func TestBlank(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, blank(u.a)) assert.Equal(t, uc.e, blank(uc.a))
}) })
} }
} }
@ -252,9 +256,10 @@ func TestIn(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, in(u.a, u.v)) assert.Equal(t, uc.e, in(uc.a, uc.v))
}) })
} }
} }
@ -268,9 +273,10 @@ func TestMetaFQN(t *testing.T) {
"nons": {metav1.ObjectMeta{Name: "blee"}, "blee"}, "nons": {metav1.ObjectMeta{Name: "blee"}, "blee"},
} }
for k, v := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, v.e, MetaFQN(v.m)) assert.Equal(t, uc.e, MetaFQN(uc.m))
}) })
} }
} }
@ -284,9 +290,10 @@ func TestFQN(t *testing.T) {
"nons": {n: "blee", e: "blee"}, "nons": {n: "blee", e: "blee"},
} }
for k, v := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
assert.Equal(t, v.e, FQN(v.ns, v.n)) assert.Equal(t, uc.e, FQN(uc.ns, uc.n))
}) })
} }
} }
@ -374,10 +381,11 @@ func BenchmarkAsPerc(b *testing.B) {
// Helpers... // Helpers...
func testTime() time.Time { // BOZO!!
t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00") // func testTime() time.Time {
if err != nil { // t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00")
fmt.Println("TestTime Failed", err) // if err != nil {
} // fmt.Println("TestTime Failed", err)
return t // }
} // return t
// }

View File

@ -37,7 +37,7 @@ func (HorizontalPodAutoscaler) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error { func (h HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected HorizontalPodAutoscaler, but got %T", o) return fmt.Errorf("Expected HorizontalPodAutoscaler, but got %T", o)
@ -48,11 +48,12 @@ func (HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error {
return err return err
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(hpa.ObjectMeta)
r.Fields = make(Fields, 0, len(h.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, hpa.Namespace) r.Fields = append(r.Fields, hpa.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
hpa.ObjectMeta.Name, hpa.ObjectMeta.Name,
hpa.Spec.ScaleTargetRef.Name, hpa.Spec.ScaleTargetRef.Name,
toMetrics(hpa.Spec, hpa.Status), toMetrics(hpa.Spec, hpa.Status),
@ -61,7 +62,6 @@ func (HorizontalPodAutoscaler) Render(o interface{}, ns string, r *Row) error {
strconv.Itoa(int(hpa.Status.CurrentReplicas)), strconv.Itoa(int(hpa.Status.CurrentReplicas)),
toAge(hpa.ObjectMeta.CreationTimestamp), toAge(hpa.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(hpa.ObjectMeta), fields
return nil return nil
} }

View File

@ -9,7 +9,6 @@ import (
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
) )
@ -77,8 +76,9 @@ func (n Node) Render(o interface{}, ns string, r *Row) error {
ro := make([]string, 10) ro := make([]string, 10)
nodeRoles(&no, ro) nodeRoles(&no, ro)
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(no.ObjectMeta)
fields = append(fields, r.Fields = make(Fields, 0, len(n.Header(ns)))
r.Fields = append(r.Fields,
no.Name, no.Name,
join(sta, ","), join(sta, ","),
join(ro, ","), join(ro, ","),
@ -94,11 +94,8 @@ func (n Node) Render(o interface{}, ns string, r *Row) error {
a.mem, a.mem,
toAge(no.ObjectMeta.CreationTimestamp), toAge(no.ObjectMeta.CreationTimestamp),
) )
r.ID = MetaFQN(no.ObjectMeta)
r.Fields = fields
return nil return nil
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -132,10 +129,6 @@ func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c metric, a metric, p
return return
} }
func withPerc(v, p string) string {
return v + " (" + p + ")"
}
func nodeRoles(node *v1.Node, res []string) { func nodeRoles(node *v1.Node, res []string) {
index := 0 index := 0
for k, v := range node.Labels { for k, v := range node.Labels {
@ -200,87 +193,6 @@ func status(status v1.NodeStatus, exempt bool, res []string) {
} }
} }
func findNodeRoles(no *v1.Node) []string {
roles := sets.NewString()
for k, v := range no.Labels {
switch {
case strings.HasPrefix(k, labelNodeRolePrefix):
if role := strings.TrimPrefix(k, labelNodeRolePrefix); len(role) > 0 {
roles.Insert(role)
}
case k == nodeLabelRole && v != "":
roles.Insert(v)
}
}
return roles.List()
}
func podsResources(name string, pods []*v1.Pod) (v1.ResourceList, v1.ResourceList, error) {
reqs, limits := v1.ResourceList{}, v1.ResourceList{}
for _, p := range pods {
preq, plim := podResources(p)
for k, v := range preq {
if value, ok := reqs[k]; !ok {
reqs[k] = v.DeepCopy()
} else {
value.Add(v)
reqs[k] = value
}
}
for k, v := range plim {
if value, ok := limits[k]; !ok {
limits[k] = v.DeepCopy()
} else {
value.Add(v)
limits[k] = value
}
}
}
return reqs, limits, nil
}
func podResources(pod *v1.Pod) (v1.ResourceList, v1.ResourceList) {
reqs, limits := v1.ResourceList{}, v1.ResourceList{}
for _, container := range pod.Spec.Containers {
addResources(reqs, container.Resources.Requests)
addResources(limits, container.Resources.Limits)
}
// init containers define the minimum of any resource
for _, container := range pod.Spec.InitContainers {
maxResources(reqs, container.Resources.Requests)
maxResources(limits, container.Resources.Limits)
}
return reqs, limits
}
// AddResources adds the resources from l2 to l1.
func addResources(l1, l2 v1.ResourceList) {
for name, quantity := range l2 {
if value, ok := l1[name]; ok {
value.Add(quantity)
l1[name] = value
} else {
l1[name] = quantity.DeepCopy()
}
}
}
// MaxResourceList sets list to the greater of l1/l2 for every resource.
func maxResources(l1, l2 v1.ResourceList) {
for name, quantity := range l2 {
if value, ok := l1[name]; ok {
if quantity.Cmp(value) > 0 {
l1[name] = quantity.DeepCopy()
}
} else {
l1[name] = quantity.DeepCopy()
}
}
}
func empty(s []string) bool { func empty(s []string) bool {
for _, v := range s { for _, v := range s {
if len(v) != 0 { if len(v) != 0 {

View File

@ -38,7 +38,7 @@ func (NetworkPolicy) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (NetworkPolicy) Render(o interface{}, ns string, r *Row) error { func (n NetworkPolicy) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected NetworkPolicy, but got %T", o) return fmt.Errorf("Expected NetworkPolicy, but got %T", o)
@ -52,11 +52,12 @@ func (NetworkPolicy) Render(o interface{}, ns string, r *Row) error {
ip, is, ib := ingress(np.Spec.Ingress) ip, is, ib := ingress(np.Spec.Ingress)
ep, es, eb := egress(np.Spec.Egress) ep, es, eb := egress(np.Spec.Egress)
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(np.ObjectMeta)
r.Fields = make(Fields, 0, len(n.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, np.Namespace) r.Fields = append(r.Fields, np.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
np.Name, np.Name,
is, is,
ip, ip,
@ -66,7 +67,6 @@ func (NetworkPolicy) Render(o interface{}, ns string, r *Row) error {
eb, eb,
toAge(np.ObjectMeta.CreationTimestamp), toAge(np.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(np.ObjectMeta), fields
return nil return nil
} }

View File

@ -7,6 +7,33 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestNSColorer(t *testing.T) {
var (
ns = render.Row{Fields: render.Fields{"blee", "Active"}}
term = render.Row{Fields: render.Fields{"blee", render.Terminating}}
dead = render.Row{Fields: render.Fields{"blee", "Inactive"}}
)
uu := colorerUCs{
// Add AllNS
{"", render.RowEvent{Kind: render.EventAdd, Row: ns}, render.AddColor},
// Mod AllNS
{"", render.RowEvent{Kind: render.EventUpdate, Row: ns}, render.ModColor},
// MoChange AllNS
{"", render.RowEvent{Kind: render.EventUnchanged, Row: ns}, render.StdColor},
// Bust NS
{"", render.RowEvent{Kind: render.EventUnchanged, Row: term}, render.ErrColor},
// Bust NS
{"", render.RowEvent{Kind: render.EventUnchanged, Row: dead}, render.ErrColor},
}
var n render.Namespace
f := n.ColorerFunc()
for _, u := range uu {
assert.Equal(t, u.e, f(u.ns, u.r))
}
}
func TestNamespaceRender(t *testing.T) { func TestNamespaceRender(t *testing.T) {
c := render.Namespace{} c := render.Namespace{}
r := render.NewRow(3) r := render.NewRow(3)

View File

@ -57,7 +57,7 @@ func (PodDisruptionBudget) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error { func (p PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected PodDisruptionBudget, but got %T", o) return fmt.Errorf("Expected PodDisruptionBudget, but got %T", o)
@ -68,11 +68,12 @@ func (PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error {
return err return err
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(pdb.ObjectMeta)
r.Fields = make(Fields, 0, len(p.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, pdb.Namespace) r.Fields = append(r.Fields, pdb.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
pdb.Name, pdb.Name,
numbToStr(pdb.Spec.MinAvailable), numbToStr(pdb.Spec.MinAvailable),
numbToStr(pdb.Spec.MaxUnavailable), numbToStr(pdb.Spec.MaxUnavailable),
@ -82,7 +83,6 @@ func (PodDisruptionBudget) Render(o interface{}, ns string, r *Row) error {
strconv.Itoa(int(pdb.Status.ExpectedPods)), strconv.Itoa(int(pdb.Status.ExpectedPods)),
toAge(pdb.ObjectMeta.CreationTimestamp), toAge(pdb.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(pdb.ObjectMeta), fields
return nil return nil
} }

View File

@ -5,7 +5,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/derailed/k9s/internal/color"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
@ -158,7 +157,7 @@ func (*Pod) gatherPodMX(pod *v1.Pod, mx *mv1beta1.PodMetrics) (c, p metric) {
return return
} }
func containerResources(co *v1.Container) (cpu, mem *resource.Quantity) { func containerResources(co v1.Container) (cpu, mem *resource.Quantity) {
req, limit := co.Resources.Requests, co.Resources.Limits req, limit := co.Resources.Requests, co.Resources.Limits
switch { switch {
case len(req) != 0: case len(req) != 0:
@ -171,7 +170,7 @@ func containerResources(co *v1.Container) (cpu, mem *resource.Quantity) {
func requestedRes(po *v1.Pod) (cpu, mem resource.Quantity) { func requestedRes(po *v1.Pod) (cpu, mem resource.Quantity) {
for _, co := range po.Spec.Containers { for _, co := range po.Spec.Containers {
c, m := containerResources(&co) c, m := containerResources(co)
if c != nil { if c != nil {
cpu.Add(*c) cpu.Add(*c)
} }
@ -225,13 +224,13 @@ func (p *Pod) phase(po *v1.Pod) string {
status = po.Status.Reason status = po.Status.Reason
} }
init, status := p.initContainerPhase(po.Status, len(po.Spec.InitContainers), status) status, ok := p.initContainerPhase(po.Status, len(po.Spec.InitContainers), status)
if init { if ok {
return status return status
} }
running, status := p.containerPhase(po.Status, status) status, ok = p.containerPhase(po.Status, status)
if running && status == "Completed" { if ok && status == "Completed" {
status = "Running" status = "Running"
} }
if po.DeletionTimestamp == nil { if po.DeletionTimestamp == nil {
@ -241,7 +240,7 @@ func (p *Pod) phase(po *v1.Pod) string {
return "Terminated" return "Terminated"
} }
func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) { func (*Pod) containerPhase(st v1.PodStatus, status string) (string, bool) {
var running bool var running bool
for i := len(st.ContainerStatuses) - 1; i >= 0; i-- { for i := len(st.ContainerStatuses) - 1; i >= 0; i-- {
cs := st.ContainerStatuses[i] cs := st.ContainerStatuses[i]
@ -261,29 +260,22 @@ func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) {
} }
} }
return running, status return status, running
} }
func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (bool, string) { func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (string, bool) {
for i, cs := range st.InitContainerStatuses { for i, cs := range st.InitContainerStatuses {
status := checkContainerStatus(cs, i, initCount) s := checkContainerStatus(cs, i, initCount)
if status == "" { if s == "" {
continue continue
} }
return true, status return s, true
} }
return false, status return status, false
}
func (*Pod) loggableContainers(s v1.PodStatus) []string {
var rcos []string
for _, c := range s.ContainerStatuses {
rcos = append(rcos, c.Name)
}
return rcos
} }
// ----------------------------------------------------------------------------
// Helpers.. // Helpers..
func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string { func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string {
@ -305,15 +297,3 @@ func checkContainerStatus(cs v1.ContainerStatus, i, initCount int) string {
return "Init:" + strconv.Itoa(i) + "/" + strconv.Itoa(initCount) return "Init:" + strconv.Itoa(i) + "/" + strconv.Itoa(initCount)
} }
} }
func asColor(n string) color.Paint {
var sum int
for _, r := range n {
sum += int(r)
}
return color.Paint(30 + 2 + sum%6)
}
func isSet(s *string) bool {
return s != nil && *s != ""
}

View File

@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/gdamore/tcell"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
res "k8s.io/apimachinery/pkg/api/resource" res "k8s.io/apimachinery/pkg/api/resource"
@ -13,6 +14,52 @@ import (
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
) )
type (
colorerUC struct {
ns string
r render.RowEvent
e tcell.Color
}
colorerUCs []colorerUC
)
func TestPodColorer(t *testing.T) {
var (
nsRow = render.Row{Fields: render.Fields{"blee", "fred", "1/1", "Running"}}
toastNS = render.Row{Fields: render.Fields{"blee", "fred", "1/1", "Boom"}}
notReadyNS = render.Row{Fields: render.Fields{"blee", "fred", "0/1", "Boom"}}
row = render.Row{Fields: render.Fields{"fred", "1/1", "Running"}}
toast = render.Row{Fields: render.Fields{"fred", "1/1", "Boom"}}
notReady = render.Row{Fields: render.Fields{"fred", "0/1", "Boom"}}
)
uu := colorerUCs{
// Add allNS
{"", render.RowEvent{Kind: render.EventAdd, Row: nsRow}, render.AddColor},
// Add Namespaced
{"blee", render.RowEvent{Kind: render.EventAdd, Row: row}, render.AddColor},
// Mod AllNS
{"", render.RowEvent{Kind: render.EventUpdate, Row: nsRow}, render.ModColor},
// Mod Namespaced
{"blee", render.RowEvent{Kind: render.EventUpdate, Row: row}, render.ModColor},
// Mod Busted AllNS
{"", render.RowEvent{Kind: render.EventUpdate, Row: toastNS}, render.ErrColor},
// Mod Busted Namespaced
{"blee", render.RowEvent{Kind: render.EventUpdate, Row: toast}, render.ErrColor},
// NotReady AllNS
{"", render.RowEvent{Kind: render.EventUpdate, Row: notReadyNS}, render.ErrColor},
// NotReady Namespaced
{"blee", render.RowEvent{Kind: render.EventUpdate, Row: notReady}, render.ErrColor},
}
var p render.Pod
f := p.ColorerFunc()
for _, u := range uu {
assert.Equal(t, u.e, f(u.ns, u.r))
}
}
func TestPodRender(t *testing.T) { func TestPodRender(t *testing.T) {
pom := podMetrics{load(t, "po"), makePodMX("nginx", "10m", "10Mi")} pom := podMetrics{load(t, "po"), makePodMX("nginx", "10m", "10Mi")}

View File

@ -42,8 +42,5 @@ func (Policy) Header(ns string) HeaderRow {
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (Policy) Render(o interface{}, gvr string, r *Row) error { func (Policy) Render(o interface{}, gvr string, r *Row) error {
panic("NYI")
return nil return nil
} }
// Helpers...

View File

@ -53,7 +53,7 @@ func (PersistentVolume) Header(string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (PersistentVolume) Render(o interface{}, ns string, r *Row) error { func (p PersistentVolume) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected PersistentVolume, but got %T", o) return fmt.Errorf("Expected PersistentVolume, but got %T", o)
@ -79,8 +79,8 @@ func (PersistentVolume) Render(o interface{}, ns string, r *Row) error {
size := pv.Spec.Capacity[v1.ResourceStorage] size := pv.Spec.Capacity[v1.ResourceStorage]
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(pv.ObjectMeta)
fields = append(fields, r.Fields = Fields{
pv.Name, pv.Name,
size.String(), size.String(),
accessMode(pv.Spec.AccessModes), accessMode(pv.Spec.AccessModes),
@ -90,8 +90,7 @@ func (PersistentVolume) Render(o interface{}, ns string, r *Row) error {
class, class,
pv.Status.Reason, pv.Status.Reason,
toAge(pv.ObjectMeta.CreationTimestamp), toAge(pv.ObjectMeta.CreationTimestamp),
) }
r.ID, r.Fields = MetaFQN(pv.ObjectMeta), fields
return nil return nil
} }

View File

@ -54,7 +54,7 @@ func (PersistentVolumeClaim) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error { func (p PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected PersistentVolumeClaim, but got %T", o) return fmt.Errorf("Expected PersistentVolumeClaim, but got %T", o)
@ -84,11 +84,12 @@ func (PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error {
} }
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(pvc.ObjectMeta)
r.Fields = make(Fields, 0, len(p.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, pvc.Namespace) r.Fields = append(r.Fields, pvc.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
pvc.Name, pvc.Name,
string(phase), string(phase),
pvc.Spec.VolumeName, pvc.Spec.VolumeName,
@ -97,7 +98,6 @@ func (PersistentVolumeClaim) Render(o interface{}, ns string, r *Row) error {
class, class,
toAge(pvc.ObjectMeta.CreationTimestamp), toAge(pvc.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(pvc.ObjectMeta), fields
return nil return nil
} }

View File

@ -31,6 +31,16 @@ type Header struct {
// HeaderRow represents a table header. // HeaderRow represents a table header.
type HeaderRow []Header type HeaderRow []Header
// Columns return header row columns as strings.
func (h HeaderRow) Columns() []string {
cc := make([]string, len(h))
for i, c := range h {
cc[i] = c.Name
}
return cc
}
// HasAge returns true if table has an age column. // HasAge returns true if table has an age column.
func (h HeaderRow) HasAge() bool { func (h HeaderRow) HasAge() bool {
for _, r := range h { for _, r := range h {

View File

@ -58,10 +58,11 @@ func TestRowDelete(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
rows := u.rows.Delete(u.id) rows := uc.rows.Delete(uc.id)
assert.Equal(t, u.e, rows) assert.Equal(t, uc.e, rows)
}) })
} }
} }
@ -135,10 +136,11 @@ func TestSortText(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
u.rows.Sort(u.col, u.asc) uc.rows.Sort(uc.col, uc.asc)
assert.Equal(t, u.e, u.rows) assert.Equal(t, uc.e, uc.rows)
}) })
} }
} }
@ -175,10 +177,11 @@ func TestSortDuration(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
u.rows.Sort(u.col, u.asc) uc.rows.Sort(uc.col, uc.asc)
assert.Equal(t, u.e, u.rows) assert.Equal(t, uc.e, uc.rows)
}) })
} }
} }
@ -216,10 +219,11 @@ func TestSortMetrics(t *testing.T) {
}, },
} }
for k, u := range uu { for k := range uu {
uc := uu[k]
t.Run(k, func(t *testing.T) { t.Run(k, func(t *testing.T) {
u.rows.Sort(u.col, u.asc) uc.rows.Sort(uc.col, uc.asc)
assert.Equal(t, u.e, u.rows) assert.Equal(t, uc.e, uc.rows)
}) })
} }
} }

View File

@ -53,7 +53,7 @@ func (ReplicaSet) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (ReplicaSet) Render(o interface{}, ns string, r *Row) error { func (s ReplicaSet) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected ReplicaSet, but got %T", o) return fmt.Errorf("Expected ReplicaSet, but got %T", o)
@ -64,18 +64,18 @@ func (ReplicaSet) Render(o interface{}, ns string, r *Row) error {
return err return err
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(rs.ObjectMeta)
r.Fields = make(Fields, 0, len(s.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, rs.Namespace) r.Fields = append(r.Fields, rs.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
rs.Name, rs.Name,
strconv.Itoa(int(*rs.Spec.Replicas)), strconv.Itoa(int(*rs.Spec.Replicas)),
strconv.Itoa(int(rs.Status.Replicas)), strconv.Itoa(int(rs.Status.Replicas)),
strconv.Itoa(int(rs.Status.ReadyReplicas)), strconv.Itoa(int(rs.Status.ReadyReplicas)),
toAge(rs.ObjectMeta.CreationTimestamp), toAge(rs.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(rs.ObjectMeta), fields
return nil return nil
} }

View File

@ -32,28 +32,27 @@ func (ServiceAccount) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (ServiceAccount) Render(o interface{}, ns string, r *Row) error { func (s ServiceAccount) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected ServiceAccount, but got %T", o) return fmt.Errorf("Expected ServiceAccount, but got %T", o)
} }
var s v1.ServiceAccount var sa v1.ServiceAccount
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &s) err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sa)
if err != nil { if err != nil {
return err return err
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(sa.ObjectMeta)
r.Fields = make(Fields, 0, len(s.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, s.Namespace) r.Fields = append(r.Fields, sa.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
s.Name, sa.Name,
strconv.Itoa(len(s.Secrets)), strconv.Itoa(len(sa.Secrets)),
toAge(s.ObjectMeta.CreationTimestamp), toAge(sa.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(s.ObjectMeta), fields
return nil return nil
} }

View File

@ -34,29 +34,28 @@ func (Secret) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (Secret) Render(o interface{}, ns string, r *Row) error { func (s Secret) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected Secret, but got %T", o) return fmt.Errorf("Expected Secret, but got %T", o)
} }
var s v1.Secret var sec v1.Secret
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &s) err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sec)
if err != nil { if err != nil {
return err return err
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(sec.ObjectMeta)
r.Fields = make(Fields, 0, len(s.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, s.Namespace) r.Fields = append(r.Fields, sec.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
s.Name, sec.Name,
string(s.Type), string(sec.Type),
strconv.Itoa(len(s.Data)), strconv.Itoa(len(sec.Data)),
toAge(s.ObjectMeta.CreationTimestamp), toAge(sec.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(s.ObjectMeta), fields
return nil return nil
} }

View File

@ -25,6 +25,5 @@ func (Subject) Header(ns string) HeaderRow {
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (Subject) Render(o interface{}, gvr string, r *Row) error { func (Subject) Render(o interface{}, gvr string, r *Row) error {
panic("NYI")
return nil return nil
} }

View File

@ -38,7 +38,7 @@ func (Service) Header(ns string) HeaderRow {
} }
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (Service) Render(o interface{}, ns string, r *Row) error { func (s Service) Render(o interface{}, ns string, r *Row) error {
raw, ok := o.(*unstructured.Unstructured) raw, ok := o.(*unstructured.Unstructured)
if !ok { if !ok {
return fmt.Errorf("Expected Service, but got %T", o) return fmt.Errorf("Expected Service, but got %T", o)
@ -49,11 +49,12 @@ func (Service) Render(o interface{}, ns string, r *Row) error {
return err return err
} }
fields := make(Fields, 0, len(r.Fields)) r.ID = MetaFQN(svc.ObjectMeta)
r.Fields = make(Fields, 0, len(s.Header(ns)))
if isAllNamespace(ns) { if isAllNamespace(ns) {
fields = append(fields, svc.Namespace) r.Fields = append(r.Fields, svc.Namespace)
} }
fields = append(fields, r.Fields = append(r.Fields,
svc.ObjectMeta.Name, svc.ObjectMeta.Name,
string(svc.Spec.Type), string(svc.Spec.Type),
svc.Spec.ClusterIP, svc.Spec.ClusterIP,
@ -63,8 +64,6 @@ func (Service) Render(o interface{}, ns string, r *Row) error {
toAge(svc.ObjectMeta.CreationTimestamp), toAge(svc.ObjectMeta.CreationTimestamp),
) )
r.ID, r.Fields = MetaFQN(svc.ObjectMeta), fields
return nil return nil
} }

View File

@ -1,30 +0,0 @@
package ui_test
// BOZO!!
// import (
// "testing"
// "github.com/derailed/k9s/internal/render"
// "github.com/derailed/k9s/internal/ui"
// "github.com/gdamore/tcell"
// "github.com/stretchr/testify/assert"
// )
// func TestDefaultColorer(t *testing.T) {
// uu := map[string]struct {
// re render.RowEvent
// e tcell.Color
// }{
// "default": {render.RowEvent{}, ui.StdColor},
// "add": {render.RowEvent{Kind: render.EventAdd}, ui.AddColor},
// "delete": {render.RowEvent{Kind: render.EventDelete}, ui.KillColor},
// "update": {render.RowEvent{Kind: render.EventUpdate}, ui.ModColor},
// }
// for k := range uu {
// u := uu[k]
// t.Run(k, func(t *testing.T) {
// assert.Equal(t, u.e, ui.DefaultColorer("", u.re))
// })
// }
// }

View File

@ -1,118 +0,0 @@
package ui
// BOZO!!
// func TestGroupSort(t *testing.T) {
// uu := []struct {
// asc bool
// rows []string
// expect []string
// }{
// {true, []string{"200m", "100m"}, []string{"100m", "200m"}},
// {true, []string{"200m", "100m"}, []string{"100m", "200m"}},
// {false, []string{"200m", "100m"}, []string{"200m", "100m"}},
// {true, []string{"10", "1"}, []string{"1", "10"}},
// {false, []string{"10", "1"}, []string{"10", "1"}},
// {true, []string{"100Mi", "10Mi"}, []string{"10Mi", "100Mi"}},
// {false, []string{"100Mi", "10Mi"}, []string{"100Mi", "10Mi"}},
// {true, []string{"xyz", "abc"}, []string{"abc", "xyz"}},
// {false, []string{"xyz", "abc"}, []string{"xyz", "abc"}},
// {true, []string{"2m30s", "1m10s"}, []string{"1m10s", "2m30s"}},
// {true, []string{"3d", "1d"}, []string{"1d", "3d"}},
// {true, []string{"95h", "93h"}, []string{"93h", "95h"}},
// {true, []string{"95d", "93d"}, []string{"93d", "95d"}},
// {true, []string{"1h10m", "59m"}, []string{"59m", "1h10m"}},
// {true, []string{"95m", "1h30m"}, []string{"1h30m", "95m"}},
// {true, []string{"b-21", "b-2"}, []string{"b-2", "b-21"}},
// {false, []string{"b-21", "b-2"}, []string{"b-21", "b-2"}},
// {true, []string{"4m", "3m2s"}, []string{"3m2s", "4m"}},
// {true, []string{"3y37d", "2y4d"}, []string{"2y4d", "3y37d"}},
// }
// for _, u := range uu {
// g := GroupSorter{rows: u.rows, asc: u.asc}
// sort.Sort(g)
// assert.Equal(t, u.expect, g.rows)
// }
// }
// func TestRowSort(t *testing.T) {
// uu := []struct {
// asc bool
// rows, expect resource.Rows
// }{
// {
// true,
// resource.Rows{resource.Row{"200m"}, resource.Row{"100m"}},
// resource.Rows{resource.Row{"100m"}, resource.Row{"200m"}},
// },
// {
// false,
// resource.Rows{resource.Row{"200m"}, resource.Row{"100m"}},
// resource.Rows{resource.Row{"200m"}, resource.Row{"100m"}},
// },
// {
// true,
// resource.Rows{resource.Row{"200Mi"}, resource.Row{"100Mi"}},
// resource.Rows{resource.Row{"100Mi"}, resource.Row{"200Mi"}},
// },
// {
// false,
// resource.Rows{resource.Row{"200Mi"}, resource.Row{"100Mi"}},
// resource.Rows{resource.Row{"200Mi"}, resource.Row{"100Mi"}},
// },
// {
// true,
// resource.Rows{resource.Row{"8m4s"}, resource.Row{"31m"}},
// resource.Rows{resource.Row{"8m4s"}, resource.Row{"31m"}},
// },
// {
// true,
// resource.Rows{resource.Row{"n/a"}, resource.Row{"31m"}},
// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}},
// },
// {
// true,
// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}},
// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}},
// },
// {
// false,
// resource.Rows{resource.Row{"n/a"}, resource.Row{"31m"}},
// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}},
// },
// {
// false,
// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}},
// resource.Rows{resource.Row{"31m"}, resource.Row{"n/a"}},
// },
// }
// for _, u := range uu {
// r := RowSorter{index: 0, rows: u.rows, asc: u.asc}
// sort.Sort(r)
// assert.Equal(t, u.expect, r.rows)
// }
// }
// func TestIsDurationSort(t *testing.T) {
// uu := map[string]struct {
// s1, s2 string
// asc, e bool
// }{
// "ascLess": {"10h5m", "2h10m", true, false},
// "descGreater": {"10h5m", "2h10m", false, true},
// "ascEqual": {"2h10m", "2h10m", true, true},
// "descEqual": {"2h10m", "2h10m", false, true},
// "ascGreater": {"10h10m", "2h5m", true, false},
// }
// for k := range uu {
// u := uu[k]
// t.Run(k, func(t *testing.T) {
// less, ok := isDurationSort(u.asc, u.s1, u.s2)
// assert.True(t, ok)
// assert.Equal(t, u.e, less)
// })
// }
// }

View File

@ -33,7 +33,6 @@ type Table struct {
cmdBuff *CmdBuff cmdBuff *CmdBuff
styles *config.Styles styles *config.Styles
sortCol SortColumn sortCol SortColumn
sortFn SortFn
colorerFn render.ColorerFunc colorerFn render.ColorerFunc
decorateFn DecorateFunc decorateFn DecorateFunc
} }
@ -158,7 +157,6 @@ func (t *Table) doUpdate(data render.TableData) {
t.adjustSorter(data) t.adjustSorter(data)
var row int
fg := config.AsColor(t.styles.GetTable().Header.FgColor) fg := config.AsColor(t.styles.GetTable().Header.FgColor)
bg := config.AsColor(t.styles.GetTable().Header.BgColor) bg := config.AsColor(t.styles.GetTable().Header.BgColor)
for col, h := range data.Header { for col, h := range data.Header {
@ -167,8 +165,6 @@ func (t *Table) doUpdate(data render.TableData) {
c.SetBackgroundColor(bg) c.SetBackgroundColor(bg)
c.SetTextColor(fg) c.SetTextColor(fg)
} }
row++
data.RowEvents.Sort(data.Namespace, t.sortCol.index, t.sortCol.asc) data.RowEvents.Sort(data.Namespace, t.sortCol.index, t.sortCol.asc)
pads := make(MaxyPad, len(data.Header)) pads := make(MaxyPad, len(data.Header))

View File

@ -29,9 +29,6 @@ const (
) )
var ( var (
cpuRX = regexp.MustCompile(`\A.{0,1}CPU`)
memRX = regexp.MustCompile(`\A.{0,1}MEM`)
// LabelCmd identifies a label query // LabelCmd identifies a label query
LabelCmd = regexp.MustCompile(`\A\-l`) LabelCmd = regexp.MustCompile(`\A\-l`)
@ -88,46 +85,6 @@ func SkinTitle(fmat string, style config.Frame) string {
return fmat return fmat
} }
// BOZO!!
// func sortRows(evts resource.RowEvents, sortFn SortFn, sortCol SortColumn, keys []string) {
// rows := make(resource.Rows, 0, len(evts))
// for k, r := range evts {
// rows = append(rows, append(r.Fields, k))
// }
// sortFn(rows, sortCol)
// for i, r := range rows {
// keys[i] = r[len(r)-1]
// }
// }
// func defaultSort(rows resource.Rows, sortCol SortColumn) {
// t := RowSorter{rows: rows, index: sortCol.index, asc: sortCol.asc}
// sort.Sort(t)
// }
// BOZO!!
// func sortAllRows(col SortColumn, rows resource.RowEvents, sortFn SortFn) (resource.Row, map[string]resource.Row) {
// keys := make([]string, len(rows))
// sortRows(rows, sortFn, col, keys)
// sec := make(map[string]resource.Row, len(rows))
// for _, k := range keys {
// grp := rows[k].Fields[col.index]
// sec[grp] = append(sec[grp], k)
// }
// // Performs secondary to sort by name for each groups.
// prim := make(resource.Row, 0, len(sec))
// for k, v := range sec {
// sort.Strings(v)
// prim = append(prim, k)
// }
// sort.Sort(GroupSorter{prim, col.asc})
// return prim, sec
// }
func sortIndicator(col SortColumn, style config.Table, index int, name string) string { func sortIndicator(col SortColumn, style config.Table, index int, name string) string {
if col.index != index { if col.index != index {
return name return name
@ -170,10 +127,9 @@ func rxFilter(q string, data render.TableData) (render.TableData, error) {
} }
func fuzzyFilter(q string, index int, data render.TableData) render.TableData { func fuzzyFilter(q string, index int, data render.TableData) render.TableData {
var ss, kk []string var ss []string
for _, re := range data.RowEvents { for _, re := range data.RowEvents {
ss = append(ss, re.Row.Fields[index]) ss = append(ss, re.Row.Fields[index])
kk = append(kk, re.Row.ID)
} }
filtered := render.TableData{ filtered := render.TableData{

View File

@ -40,76 +40,3 @@ func TestTrimLabelSelector(t *testing.T) {
}) })
} }
} }
// BOZO!!
// func TestTVSortRows(t *testing.T) {
// uu := []struct {
// rows resource.RowEvents
// col int
// asc bool
// first resource.Row
// e []string
// }{
// {
// resource.RowEvents{
// "row1": {Fields: resource.Row{"x", "y"}},
// "row2": {Fields: resource.Row{"a", "b"}},
// },
// 0,
// true,
// resource.Row{"a", "b"},
// []string{"row2", "row1"},
// },
// {
// resource.RowEvents{
// "row1": {Fields: resource.Row{"x", "y"}},
// "row2": {Fields: resource.Row{"a", "b"}},
// },
// 1,
// true,
// resource.Row{"a", "b"},
// []string{"row2", "row1"},
// },
// {
// resource.RowEvents{
// "row1": {Fields: resource.Row{"x", "y"}},
// "row2": {Fields: resource.Row{"a", "b"}},
// },
// 1,
// false,
// resource.Row{"x", "y"},
// []string{"row1", "row2"},
// },
// {
// resource.RowEvents{
// "row1": {Fields: resource.Row{"2175h48m0.06015s", "y"}},
// "row2": {Fields: resource.Row{"403h42m34.060166s", "b"}},
// },
// 0,
// true,
// resource.Row{"403h42m34.060166s", "b"},
// []string{"row2", "row1"},
// },
// }
// for _, u := range uu {
// keys := make([]string, len(u.rows))
// sortRows(u.rows, defaultSort, SortColumn{index: u.col, colCount: len(u.rows), asc: u.asc}, keys)
// assert.Equal(t, u.e, keys)
// assert.Equal(t, u.first, u.rows[u.e[0]].Fields)
// }
// }
// func BenchmarkTableSortRows(b *testing.B) {
// evts := resource.RowEvents{
// "row1": {Fields: resource.Row{"x", "y"}},
// "row2": {Fields: resource.Row{"a", "b"}},
// }
// sc := SortColumn{index: 0, colCount: 2, asc: true}
// keys := make([]string, len(evts))
// b.ResetTimer()
// b.ReportAllocs()
// for i := 0; i < b.N; i++ {
// sortRows(evts, defaultSort, sc, keys)
// }
// }

View File

@ -1,8 +1,6 @@
package ui package ui
import ( import "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/render"
)
type ( type (
// SortFn represent a function that can sort columnar data. // SortFn represent a function that can sort columnar data.

View File

@ -12,10 +12,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
const ( const aliasTitle = "Aliases"
aliasTitle = "Aliases"
aliasTitleFmt = " [mediumseagreen::b]%s([fuchsia::b]%d[fuchsia::-][mediumseagreen::-]) "
)
// Alias represents a command alias view. // Alias represents a command alias view.
type Alias struct { type Alias struct {
@ -32,7 +29,6 @@ func NewAlias(gvr client.GVR) ResourceViewer {
a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone) a.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorMediumSpringGreen, tcell.AttrNone)
a.SetBindKeysFn(a.bindKeys) a.SetBindKeysFn(a.bindKeys)
a.SetContextFn(a.aliasContext) a.SetContextFn(a.aliasContext)
// a.GetTable().SetEnterFn(a.gotoCmd)
return &a return &a
} }
@ -45,12 +41,9 @@ func (a *Alias) bindKeys(aa ui.KeyActions) {
aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace) aa.Delete(ui.KeyShiftA, ui.KeyShiftN, tcell.KeyCtrlS, tcell.KeyCtrlSpace, ui.KeySpace)
aa.Add(ui.KeyActions{ aa.Add(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, true), tcell.KeyEnter: ui.NewKeyAction("Goto", a.gotoCmd, true),
// BOZO!! ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.GetTable().SortColCmd(0, true), false),
// tcell.KeyEscape: ui.NewKeyAction("Reset", a.resetCmd, false), ui.KeyShiftC: ui.NewKeyAction("Sort Command", a.GetTable().SortColCmd(1, true), false),
// ui.KeySlash: ui.NewKeyAction("Filter", a.GetTable().activateCmd, false), ui.KeyShiftA: ui.NewKeyAction("Sort ApiGroup", a.GetTable().SortColCmd(2, true), false),
ui.KeyShiftR: ui.NewKeyAction("Sort Resource", a.GetTable().SortColCmd(0, true), false),
ui.KeyShiftC: ui.NewKeyAction("Sort Command", a.GetTable().SortColCmd(1, true), false),
ui.KeyShiftA: ui.NewKeyAction("Sort ApiGroup", a.GetTable().SortColCmd(2, true), false),
}) })
} }
@ -60,7 +53,9 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if r != 0 { if r != 0 {
s := ui.TrimCell(a.GetTable().SelectTable, r, 1) s := ui.TrimCell(a.GetTable().SelectTable, r, 1)
tokens := strings.Split(s, ",") tokens := strings.Split(s, ",")
a.App().gotoResource(tokens[0]) if err := a.App().gotoResource(tokens[0]); err != nil {
a.App().Flash().Err(err)
}
return nil return nil
} }
@ -69,71 +64,3 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
} }
return evt return evt
} }
func (a *Alias) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !a.GetTable().SearchBuff().Empty() {
a.GetTable().SearchBuff().Reset()
return nil
}
return a.App().PrevCmd(evt)
}
func (a *Alias) gotoCmd1(app *App, ns, res, path string) {
log.Debug().Msgf("GOTO %q -- %q -- %q", ns, res, path)
app.gotoResource(client.GVR(path).ToR())
// r, _ := a.GetTable().GetSelection()
// if r != 0 {
// s := ui.TrimCell(a.GetTable().SelectTable, r, 1)
// tokens := strings.Split(s, ",")
// a.App().Content.Pop()
// if err := a.App().gotoResource(tokens[0]); err != nil {
// a.App().Flash().Err(err)
// }
// return nil
// }
// if a.GetTable().SearchBuff().IsActive() {
// return a.GetTable().activateCmd(evt)
// }
// return evt
}
// BOZO!!
// func (a *Alias) hydrate() render.TableData {
// var re render.Alias
// data := render.TableData{
// Header: re.Header(render.AllNamespaces),
// RowEvents: make(render.RowEvents, 0, len(aliases.Alias)),
// Namespace: resource.NotNamespaced,
// }
// aa := make(config.ShortNames, len(aliases.Alias))
// for alias, gvr := range aliases.Alias {
// if _, ok := aa[gvr]; ok {
// aa[gvr] = append(aa[gvr], alias)
// } else {
// aa[gvr] = []string{alias}
// }
// }
// for gvr, aliases := range aa {
// var row render.Row
// if err := re.Render(aliases, gvr, &row); err != nil {
// log.Error().Err(err).Msgf("Alias render failed")
// continue
// }
// data.RowEvents = append(data.RowEvents, render.RowEvent{
// Kind: render.EventAdd,
// Row: row,
// })
// }
// return data
// }
// func (a *Alias) resetTitle() {
// a.SetTitle(fmt.Sprintf(aliasTitleFmt, aliasTitle, a.GetRowCount()-1))
// }

View File

@ -211,20 +211,26 @@ func (a *App) refreshIndicator() {
cluster := model.NewCluster(a.Conn(), mx) cluster := model.NewCluster(a.Conn(), mx)
var cmx client.ClusterMetrics var cmx client.ClusterMetrics
nos, nmx, err := fetchResources(a) nos, nmx, err := fetchResources(a)
cpu, mem := "0", "0" if err != nil {
if err == nil { log.Error().Err(err).Msgf("unable to refresh cluster indicator")
cluster.Metrics(nos, nmx, &cmx) return
cpu = render.AsPerc(cmx.PercCPU)
if cpu == "0" {
cpu = render.NAValue
}
mem = render.AsPerc(cmx.PercMEM)
if mem == "0" {
mem = render.NAValue
}
} }
info := fmt.Sprintf( if err := cluster.Metrics(nos, nmx, &cmx); err != nil {
log.Error().Err(err).Msgf("unable to refresh cluster indicator")
return
}
cpu := render.AsPerc(cmx.PercCPU)
if cpu == "0" {
cpu = render.NAValue
}
mem := render.AsPerc(cmx.PercMEM)
if mem == "0" {
mem = render.NAValue
}
a.indicator().SetPermanent(fmt.Sprintf(
indicatorFmt, indicatorFmt,
a.version, a.version,
cluster.ClusterName(), cluster.ClusterName(),
@ -232,8 +238,7 @@ func (a *App) refreshIndicator() {
cluster.Version(), cluster.Version(),
cpu, cpu,
mem, mem,
) ))
a.indicator().SetPermanent(info)
} }
func (a *App) switchNS(ns string) bool { func (a *App) switchNS(ns string) bool {

View File

@ -2,9 +2,7 @@ package view
import ( import (
"context" "context"
"fmt"
"io/ioutil" "io/ioutil"
"os"
"path/filepath" "path/filepath"
"strings" "strings"
@ -18,10 +16,7 @@ import (
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
const ( const resultTitle = "Benchmark Results"
benchTitle = "Benchmarks"
resultTitle = "Benchmark Results"
)
// Benchmark represents a service benchmark results view. // Benchmark represents a service benchmark results view.
type Benchmark struct { type Benchmark struct {
@ -50,31 +45,19 @@ func (b *Benchmark) benchContext(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeyDir, benchDir(b.App().Config)) return context.WithValue(ctx, internal.KeyDir, benchDir(b.App().Config))
} }
// BOZO!!
// // Start runs the refresh loop
// func (b *Bench) Start() {
// log.Debug().Msgf(">>>> Bench START")
// var ctx context.Context
// ctx, b.cancelFn = context.WithCancel(context.Background())
// if err := b.watchBenchDir(ctx); err != nil {
// b.App().Flash().Errf("Unable to watch benchmarks directory %s", err)
// }
// }
func (b *Benchmark) viewBench(app *App, ns, res, path string) { func (b *Benchmark) viewBench(app *App, ns, res, path string) {
log.Debug().Msgf("VIEWBENCH %q -- %q -- %q", ns, res, path) log.Debug().Msgf("VIEWBENCH %q -- %q -- %q", ns, res, path)
data, err := readBenchFile(app.Config, b.benchFile()) data, err := readBenchFile(app.Config, b.benchFile())
if err != nil { if err != nil {
b.App().Flash().Errf("Unable to load bench file %s", err) app.Flash().Errf("Unable to load bench file %s", err)
return return
} }
b.details.SetText(data) b.details.SetText(data)
b.details.SetSubject(fileToSubject(path)) b.details.SetSubject(fileToSubject(path))
b.App().inject(b.details) if err := app.inject(b.details); err != nil {
app.Flash().Err(err)
return }
} }
func fileToSubject(path string) string { func fileToSubject(path string) string {
@ -84,60 +67,30 @@ func fileToSubject(path string) string {
return ee[0] + "/" + ee[1] return ee[0] + "/" + ee[1]
} }
func (b *Benchmark) deleteCmd(evt *tcell.EventKey) *tcell.EventKey { // BOZO!!
if !b.GetTable().RowSelected() { // func (b *Benchmark) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil // if !b.GetTable().RowSelected() {
} // return nil
// }
sel, file := b.GetTable().GetSelectedItem(), b.benchFile() // sel, file := b.GetTable().GetSelectedItem(), b.benchFile()
dir := filepath.Join(perf.K9sBenchDir, b.App().Config.K9s.CurrentCluster) // dir := filepath.Join(perf.K9sBenchDir, b.App().Config.K9s.CurrentCluster)
showModal(b.App().Content.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() { // showModal(b.App().Content.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() {
if err := os.Remove(filepath.Join(dir, file)); err != nil { // if err := os.Remove(filepath.Join(dir, file)); err != nil {
b.App().Flash().Errf("Unable to delete file %s", err) // b.App().Flash().Errf("Unable to delete file %s", err)
return // return
} // }
b.App().Flash().Infof("Benchmark %s deleted!", sel) // b.App().Flash().Infof("Benchmark %s deleted!", sel)
}) // })
return nil // return nil
} // }
func (b *Benchmark) benchFile() string { func (b *Benchmark) benchFile() string {
r := b.GetTable().GetSelectedRowIndex() r := b.GetTable().GetSelectedRowIndex()
return ui.TrimCell(b.GetTable().SelectTable, r, 7) return ui.TrimCell(b.GetTable().SelectTable, r, 7)
} }
// BOZO!!
// func (b *Benchmark) watchBenchDir(ctx context.Context) error {
// w, err := fsnotify.NewWatcher()
// if err != nil {
// return err
// }
// go func() {
// for {
// select {
// case evt := <-w.Events:
// log.Debug().Msgf("Bench event %#v", evt)
// b.App().QueueUpdateDraw(func() {
// b.Refresh()
// })
// case err := <-w.Errors:
// log.Info().Err(err).Msg("Dir Watcher failed")
// return
// case <-ctx.Done():
// log.Debug().Msg("!!!! FS WATCHER DONE!!")
// if err := w.Close(); err != nil {
// log.Error().Err(err).Msg("Closing bench watched")
// }
// return
// }
// }
// }()
// return w.Add(benchDir(b.App().Config))
// }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers... // Helpers...
@ -145,10 +98,6 @@ func benchDir(cfg *config.Config) string {
return filepath.Join(perf.K9sBenchDir, cfg.K9s.CurrentCluster) return filepath.Join(perf.K9sBenchDir, cfg.K9s.CurrentCluster)
} }
func loadBenchDir(cfg *config.Config) ([]os.FileInfo, error) {
return ioutil.ReadDir(benchDir(cfg))
}
func readBenchFile(cfg *config.Config, n string) (string, error) { func readBenchFile(cfg *config.Config, n string) (string, error) {
data, err := ioutil.ReadFile(filepath.Join(benchDir(cfg), n)) data, err := ioutil.ReadFile(filepath.Join(benchDir(cfg), n))
if err != nil { if err != nil {

View File

@ -60,7 +60,7 @@ func (b *Browser) Init(ctx context.Context) error {
return err return err
} }
if err := b.Table.Init(ctx); err != nil { if err = b.Table.Init(ctx); err != nil {
return err return err
} }
if !dao.IsK9sMeta(b.meta) { if !dao.IsK9sMeta(b.meta) {
@ -79,7 +79,6 @@ func (b *Browser) Init(ctx context.Context) error {
log.Debug().Msgf("ACCESSOR FOR %s -- %#v", b.gvr, b.accessor) log.Debug().Msgf("ACCESSOR FOR %s -- %#v", b.gvr, b.accessor)
b.envFn = b.defaultK9sEnv b.envFn = b.defaultK9sEnv
b.Table.setFilterFn(b.filterBrowser)
b.setNamespace(b.App().Config.ActiveNamespace()) b.setNamespace(b.App().Config.ActiveNamespace())
b.refresh() b.refresh()
row, _ := b.GetSelection() row, _ := b.GetSelection()
@ -111,10 +110,7 @@ func (b *Browser) Stop() {
} }
func (b *Browser) Refresh() { func (b *Browser) Refresh() {
// BOZO!!
// b.app.QueueUpdateDraw(func() {
b.refresh() b.refresh()
// })
} }
// Name returns the component name. // Name returns the component name.
@ -142,12 +138,6 @@ func (b *Browser) GetTable() *Table {
return b.Table return b.Table
} }
func (b *Browser) filterBrowser(sel string) {
panic("NYI")
// b.list.SetLabelSelector(sel)
b.refresh()
}
func (b *Browser) update(ctx context.Context) { func (b *Browser) update(ctx context.Context) {
for { for {
select { select {
@ -263,7 +253,7 @@ func (b *Browser) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
func (b *Browser) defaultEnter(app *App, ns, _, sel string) { func (b *Browser) defaultEnter(app *App, _, _, sel string) {
log.Debug().Msgf("--------- Resource %q Verbs %v", sel, b.meta.Verbs) log.Debug().Msgf("--------- Resource %q Verbs %v", sel, b.meta.Verbs)
ns, n := client.Namespaced(sel) ns, n := client.Namespaced(sel)
yaml, err := dao.Describe(b.app.Conn(), b.gvr, ns, n) yaml, err := dao.Describe(b.app.Conn(), b.gvr, ns, n)
@ -277,7 +267,6 @@ func (b *Browser) defaultEnter(app *App, ns, _, sel string) {
details.SetTextColor(b.app.Styles.FgColor()) details.SetTextColor(b.app.Styles.FgColor())
details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, yaml)) details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, yaml))
details.ScrollToBeginning() details.ScrollToBeginning()
if err := b.app.inject(details); err != nil { if err := b.app.inject(details); err != nil {
b.app.Flash().Err(err) b.app.Flash().Err(err)
} }
@ -317,7 +306,9 @@ func (b *Browser) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
details.SetTextColor(b.app.Styles.FgColor()) details.SetTextColor(b.app.Styles.FgColor())
details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, raw)) details.SetText(colorizeYAML(b.app.Styles.Views().Yaml, raw))
details.ScrollToBeginning() details.ScrollToBeginning()
b.app.inject(details) if err := b.app.inject(details); err != nil {
b.App().Flash().Err(err)
}
return nil return nil
} }

View File

@ -150,7 +150,9 @@ func (v *clusterInfoView) refreshMetrics(cluster *model.Cluster, row int) {
} }
var cmx client.ClusterMetrics var cmx client.ClusterMetrics
cluster.Metrics(nos, nmx, &cmx) if err := cluster.Metrics(nos, nmx, &cmx); err != nil {
log.Error().Err(err).Msgf("failed to retrieve cluster metrics")
}
c := v.GetCell(row, 1) c := v.GetCell(row, 1)
cpu := render.AsPerc(cmx.PercCPU) cpu := render.AsPerc(cmx.PercCPU)
if cpu == "0" { if cpu == "0" {

View File

@ -140,7 +140,6 @@ func (c *command) exec(gvr string, comp model.Component) error {
log.Error().Err(err).Msg("Config save failed!") log.Error().Err(err).Msg("Config save failed!")
} }
c.app.Content.Stack.ClearHistory() c.app.Content.Stack.ClearHistory()
return c.app.inject(comp)
return nil return c.app.inject(comp)
} }

View File

@ -49,7 +49,9 @@ func (c *CronJob) showJobs(app *App, ns, res, path string) {
v := NewJob(client.GVR("batch/v1/jobs")) v := NewJob(client.GVR("batch/v1/jobs"))
v.SetContextFn(jobCtx(path, string(cj.UID))) v.SetContextFn(jobCtx(path, string(cj.UID)))
app.inject(v) if err := app.inject(v); err != nil {
app.Flash().Err(err)
}
} }
func jobCtx(path, uid string) ContextFunc { func jobCtx(path, uid string) ContextFunc {

View File

@ -34,7 +34,7 @@ func NewHelp() *Help {
} }
// Init initializes the component. // Init initializes the component.
func (v *Help) Init(ctx context.Context) (err error) { func (v *Help) Init(ctx context.Context) error {
if err := v.Table.Init(ctx); err != nil { if err := v.Table.Init(ctx); err != nil {
return nil return nil
} }

View File

@ -1,28 +1,27 @@
package view_test package view_test
// import ( import (
// "testing" "testing"
// "github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/client"
// "github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
// "github.com/derailed/k9s/internal/view" "github.com/derailed/k9s/internal/view"
// "github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
// ) )
// BOZO!! func TestHelp(t *testing.T) {
// func TestHelpNew(t *testing.T) { ctx := makeCtx()
// ctx := makeCtx()
// app := ctx.Value(ui.KeyApp).(*view.App) app := ctx.Value(ui.KeyApp).(*view.App)
// po := view.NewPod(client.GVR("v1/pods")) po := view.NewPod(client.GVR("v1/pods"))
// po.Init(ctx) po.Init(ctx)
// app.Content.Push(po) app.Content.Push(po)
// v := view.NewHelp() v := view.NewHelp()
// v.Init(ctx)
// assert.Equal(t, 32, v.GetRowCount()) assert.Nil(t, v.Init(ctx))
// assert.Equal(t, 10, v.GetColumnCount()) assert.Equal(t, 25, v.GetRowCount())
// assert.Equal(t, "<backspace>", v.GetCell(1, 0).Text) assert.Equal(t, 10, v.GetColumnCount())
// assert.Equal(t, "Erase", v.GetCell(1, 1).Text) assert.Equal(t, "<backspace>", v.GetCell(1, 0).Text)
// } assert.Equal(t, "Erase", v.GetCell(1, 1).Text)
}

View File

@ -13,8 +13,6 @@ import (
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"golang.org/x/text/language"
"golang.org/x/text/message"
) )
func showPodsWithLabels(app *App, path string, sel map[string]string) { func showPodsWithLabels(app *App, path string, sel map[string]string) {
@ -38,7 +36,9 @@ func showPods(app *App, path, labelSel, fieldSel string) {
if err := app.Config.SetActiveNamespace(ns); err != nil { if err := app.Config.SetActiveNamespace(ns); err != nil {
log.Error().Err(err).Msg("Config NS set failed!") log.Error().Err(err).Msg("Config NS set failed!")
} }
app.inject(v) if err := app.inject(v); err != nil {
app.Flash().Err(err)
}
} }
func podCtx(path, labelSel, fieldSel string) ContextFunc { func podCtx(path, labelSel, fieldSel string) ContextFunc {
@ -118,9 +118,3 @@ func fqn(ns, n string) string {
} }
return ns + "/" + n return ns + "/" + n
} }
// AsNumb prints a number with thousand separator.
func asNum(n int) string {
p := message.NewPrinter(language.English)
return p.Sprintf("%d", n)
}

View File

@ -23,7 +23,7 @@ func NewJob(gvr client.GVR) ResourceViewer {
return &j return &j
} }
// BOZO!! Change enter signature? // TODO!! Change enter signature?
func (*Job) showPods(app *App, _, res, path string) { func (*Job) showPods(app *App, _, res, path string) {
o, err := app.factory.Get("batch/v1/jobs", path, labels.Everything()) o, err := app.factory.Get("batch/v1/jobs", path, labels.Everything())
if err != nil { if err != nil {

View File

@ -55,5 +55,7 @@ func (l *LogsExtender) showLogs(path string, prev bool) {
log.Debug().Msgf("CUSTOM CO FUNC") log.Debug().Msgf("CUSTOM CO FUNC")
co = l.containerFn() co = l.containerFn()
} }
l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)) if err := l.App().inject(NewLog(client.GVR(l.GVR()), path, co, prev)); err != nil {
l.App().Flash().Err(err)
}
} }

View File

@ -8,8 +8,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
const nodeTitle = "Nodes"
// Node represents a node view. // Node represents a node view.
type Node struct { type Node struct {
ResourceViewer ResourceViewer
@ -65,7 +63,9 @@ func (n *Node) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
details.SetTextColor(n.App().Styles.FgColor()) details.SetTextColor(n.App().Styles.FgColor())
details.SetText(colorizeYAML(n.App().Styles.Views().Yaml, raw)) details.SetText(colorizeYAML(n.App().Styles.Views().Yaml, raw))
details.ScrollToBeginning() details.ScrollToBeginning()
n.App().inject(details) if err := n.App().inject(details); err != nil {
n.App().Flash().Err(err)
}
return nil return nil
} }

View File

@ -40,7 +40,9 @@ func (n *Namespace) bindKeys(aa ui.KeyActions) {
func (n *Namespace) switchNs(app *App, _, res, sel string) { func (n *Namespace) switchNs(app *App, _, res, sel string) {
n.useNamespace(sel) n.useNamespace(sel)
app.gotoResource("po") if err := app.gotoResource("po"); err != nil {
app.Flash().Err(err)
}
} }
func (n *Namespace) useNsCmd(evt *tcell.EventKey) *tcell.EventKey { func (n *Namespace) useNsCmd(evt *tcell.EventKey) *tcell.EventKey {

View File

@ -19,10 +19,7 @@ import (
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
const ( const shellCheck = "command -v bash >/dev/null && exec bash || exec sh"
podTitle = "Pods"
shellCheck = "command -v bash >/dev/null && exec bash || exec sh"
)
// Pod represents a pod viewer. // Pod represents a pod viewer.
type Pod struct { type Pod struct {
@ -59,7 +56,9 @@ func (p *Pod) showContainers(app *App, ns, gvr, path string) {
log.Debug().Msgf("SHOW CONTAINERS %q -- %q -- %q", gvr, ns, path) log.Debug().Msgf("SHOW CONTAINERS %q -- %q -- %q", gvr, ns, path)
co := NewContainer(client.GVR("containers")) co := NewContainer(client.GVR("containers"))
co.SetContextFn(p.podContext) co.SetContextFn(p.podContext)
app.inject(co) if err := app.inject(co); err != nil {
app.Flash().Err(err)
}
} }
func (p *Pod) podContext(ctx context.Context) context.Context { func (p *Pod) podContext(ctx context.Context) context.Context {
@ -124,7 +123,9 @@ func (p *Pod) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
picker.SetSelectedFunc(func(i int, t, d string, r rune) { picker.SetSelectedFunc(func(i int, t, d string, r rune) {
p.shellIn(sel, t) p.shellIn(sel, t)
}) })
p.App().inject(picker) if err := p.App().inject(picker); err != nil {
p.App().Flash().Err(err)
}
return evt return evt
} }

View File

@ -10,23 +10,16 @@ import (
) )
const ( const (
policyTitle = "Policy" group = "Group"
group = "Group" user = "User"
user = "User" sa = "ServiceAccount"
sa = "ServiceAccount" allVerbs = "*"
allVerbs = "*"
) )
type ( // Policy presents a RBAC policy viewer.
namespacedRole struct { type Policy struct {
ns, role string ResourceViewer
} }
// Policy presents a RBAC policy viewer.
Policy struct {
ResourceViewer
}
)
// NewPolicy returns a new viewer. // NewPolicy returns a new viewer.
func NewPolicy(gvr client.GVR) *Policy { func NewPolicy(gvr client.GVR) *Policy {
@ -44,27 +37,9 @@ func (p *Policy) Name() string {
return "policy" return "policy"
} }
// func (p *Policy) Start() {
// p.Stop()
// ctx, cancel := context.WithCancel(context.Background())
// p.cancel = cancel
// go func(ctx context.Context) {
// for {
// select {
// case <-ctx.Done():
// return
// case <-time.After(time.Duration(p.app.Config.K9s.GetRefreshRate()) * time.Second):
// p.refresh()
// }
// }
// }(ctx)
// }
func (p *Policy) bindKeys(aa ui.KeyActions) { func (p *Policy) bindKeys(aa ui.KeyActions) {
aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace)
aa.Add(ui.KeyActions{ aa.Add(ui.KeyActions{
// tcell.KeyEscape: ui.NewKeyAction("Back", p.resetCmd, false),
// ui.KeySlash: ui.NewKeyAction("Filter", p.activateCmd, false),
ui.KeyShiftP: ui.NewKeyAction("Sort Namespace", p.GetTable().SortColCmd(0, true), false), ui.KeyShiftP: ui.NewKeyAction("Sort Namespace", p.GetTable().SortColCmd(0, true), false),
ui.KeyShiftN: ui.NewKeyAction("Sort Name", p.GetTable().SortColCmd(1, true), false), ui.KeyShiftN: ui.NewKeyAction("Sort Name", p.GetTable().SortColCmd(1, true), false),
ui.KeyShiftO: ui.NewKeyAction("Sort Group", p.GetTable().SortColCmd(2, true), false), ui.KeyShiftO: ui.NewKeyAction("Sort Group", p.GetTable().SortColCmd(2, true), false),
@ -72,237 +47,6 @@ func (p *Policy) bindKeys(aa ui.KeyActions) {
}) })
} }
func (p *Policy) getTitle() string {
// BOZO!!
// return fmt.Sprintf(rbacTitleFmt, policyTitle, p.subjectKind+":"+p.subjectName, p.GetRowCount())
return ""
}
// func (p *Policy) refresh() {
// log.Debug().Msgf(">>>>>>>>>>>>>>> Refreshing Policies")
// // BOZO!!
// defer func(t time.Time) {
// log.Debug().Msgf("Policy Refresh elapsed %v", time.Since(t))
// }(time.Now())
// data, err := p.reconcile()
// if err != nil {
// log.Error().Err(err).Msgf("Refresh for %s:%s", p.subjectKind, p.subjectName)
// p.app.Flash().Err(err)
// }
// p.app.QueueUpdateDraw(func() {
// p.Update(data)
// })
// }
// func (p *Policy) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
// if !p.GetTable().SearchBuff().Empty() {
// p.GetTable().SearchBuff().Reset()
// return nil
// }
// return p.backCmd(evt)
// }
// func (p *Policy) backCmd(evt *tcell.EventKey) *tcell.EventKey {
// if p.cancel != nil {
// p.cancel()
// }
// if p.SearchBuff().IsActive() {
// p.SearchBuff().Reset()
// return nil
// }
// return p.app.PrevCmd(evt)
// }
// func (p *Policy) reconcile() (render.TableData, error) {
// // BOZO!!
// defer func(t time.Time) {
// log.Debug().Msgf("Policy Reconcile elapsed %v", time.Since(t))
// }(time.Now())
// var table render.TableData
// evts, errs := p.fetchClusterRoleBindings()
// if len(errs) > 0 {
// for _, err := range errs {
// log.Error().Err(err).Msg("Unable to find cluster policies")
// }
// return table, errs[0]
// }
// nevts, errs := p.namespacedPolicies()
// if len(errs) > 0 {
// for _, err := range errs {
// log.Error().Err(err).Msg("Unable to find cluster policies")
// }
// return table, errs[0]
// }
// for _, v := range nevts {
// evts = append(evts, v)
// }
// return buildTable(p, evts), nil
// }
// Protocol...
// func (p *Policy) Header() render.HeaderRow {
// return render.Policy{}.Header(render.AllNamespaces)
// }
// func (p *Policy) GetCache() render.RowEvents {
// return p.cache
// }
// func (p *Policy) SetCache(evts render.RowEvents) {
// p.cache = evts
// }
// func (p *Policy) fetchClusterRoleBindings() (render.Rows, []error) {
// var errs []error
// oo, err := p.app.factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything())
// if err != nil {
// return nil, append(errs, err)
// }
// roles := make([]string, 0, len(oo))
// for _, o := range oo {
// var crb rbacv1.ClusterRoleBinding
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb)
// if err != nil {
// errs = append(errs, err)
// continue
// }
// for _, s := range crb.Subjects {
// if s.Kind == p.subjectKind && s.Name == p.subjectName {
// roles = append(roles, crb.RoleRef.Name)
// }
// }
// }
// rows := make(render.Rows, 0, len(oo))
// for _, role := range roles {
// o, err := p.app.factory.Get(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterroles", role, labels.Everything())
// if err != nil {
// return nil, append(errs, err)
// }
// var cr rbacv1.ClusterRole
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
// if err != nil {
// errs = append(errs, err)
// continue
// }
// for _, v := range p.parseRules("*", "CR:"+role, cr.Rules) {
// rows = append(rows, v)
// }
// }
// return rows, errs
// }
// func (p *Policy) fetchRoleBindings() ([]namespacedRole, error) {
// oo, err := p.app.factory.List(render.AllNamespaces, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything())
// if err != nil {
// return nil, err
// }
// rr := make([]namespacedRole, 0, len(oo))
// for _, o := range oo {
// var rb rbacv1.RoleBinding
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb)
// if err != nil {
// return nil, err
// }
// for _, s := range rb.Subjects {
// if s.Kind == p.subjectKind && s.Name == p.subjectName {
// rr = append(rr, namespacedRole{rb.Namespace, rb.RoleRef.Name})
// }
// }
// }
// return rr, nil
// }
// func (p *Policy) fetchClusterRoles(errs []error, rr []namespacedRole) (render.Rows, []error) {
// rows := make(render.Rows, 0, len(rr))
// for _, r := range rr {
// o, err := p.app.factory.Get(r.ns, "rbac.authorization.k8s.io/v1/clusterroles", r.role, labels.Everything())
// if err != nil {
// return nil, append(errs, err)
// }
// var cr rbacv1.ClusterRole
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
// if err != nil {
// errs = append(errs, err)
// continue
// }
// rows = append(rows, p.parseRules(r.ns, "RO:"+r.role, cr.Rules)...)
// }
// return rows, errs
// }
// func (p *Policy) namespacedPolicies() (render.Rows, []error) {
// var errs []error
// roles, err := p.fetchRoleBindings()
// if err != nil {
// errs = append(errs, err)
// }
// return p.fetchClusterRoles(errs, roles)
// }
// func (p *Policy) parseRules(ns, binding string, rules []rbacv1.PolicyRule) render.Rows {
// m := make(render.Rows, 0, len(rules))
// for _, r := range rules {
// for _, grp := range r.APIGroups {
// for _, res := range r.Resources {
// k := res
// if grp != "" {
// k = res + "." + grp
// }
// for _, na := range r.ResourceNames {
// n := fqn(k, na)
// m = append(m, render.Row{
// ID: fqn(ns, n),
// Fields: append(policyRow(ns, n, grp, binding), asVerbs(r.Verbs)...),
// })
// }
// m = append(m, render.Row{
// ID: fqn(ns, k),
// Fields: append(policyRow(ns, k, grp, binding), asVerbs(r.Verbs)...),
// })
// }
// }
// for _, nres := range r.NonResourceURLs {
// if nres[0] != '/' {
// nres = "/" + nres
// }
// m = append(m, render.Row{
// ID: fqn(ns, nres),
// Fields: append(policyRow(ns, nres, "", binding), asVerbs(r.Verbs)...),
// })
// }
// }
// return m
// }
func policyRow(ns, res, grp, binding string) render.Fields {
if grp != "" {
grp = toGroup(grp)
}
r := make(render.Fields, 0, len(render.Policy{}.Header(render.AllNamespaces)))
return append(r, ns, res, grp, binding)
}
func mapSubject(subject string) string { func mapSubject(subject string) string {
switch subject { switch subject {
case "g": case "g":
@ -314,23 +58,6 @@ func mapSubject(subject string) string {
} }
} }
// func showSAPolicy(app *App, _, _, selection string) {
// _, n := client.Namespaced(selection)
// subject, err := mapFuSubject("ServiceAccount")
// if err != nil {
// app.Flash().Err(err)
// return
// }
// app.inject(NewPolicy(app, subject, n))
// }
func toGroup(g string) string {
if g == "" {
return "v1"
}
return g
}
func hasVerb(verbs []string, verb string) bool { func hasVerb(verbs []string, verb string) bool {
if len(verbs) == 1 && verbs[0] == allVerbs { if len(verbs) == 1 && verbs[0] == allVerbs {
return true return true

View File

@ -13,15 +13,11 @@ import (
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/derailed/tview" "github.com/derailed/tview"
"github.com/fsnotify/fsnotify"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
const ( const promptPage = "prompt"
portForwardTitle = "PortForwards"
promptPage = "prompt"
)
// PortForward presents active portforward viewer. // PortForward presents active portforward viewer.
type PortForward struct { type PortForward struct {
@ -49,37 +45,6 @@ func (p *PortForward) portForwardContext(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeyBenchCfg, p.App().Bench) return context.WithValue(ctx, internal.KeyBenchCfg, p.App().Bench)
} }
// BOZO!!
// // Start runs the refresh loop.
// func (p *PortForward) Start() {
// path := ui.BenchConfig(p.App().Config.K9s.CurrentCluster)
// var ctx context.Context
// ctx, p.cancelFn = context.WithCancel(context.Background())
// if err := watchFS(ctx, p.App(), config.K9sHome, path, p.reload); err != nil {
// p.App().Flash().Errf("RuRoh! Unable to watch benchmarks directory %s : %s", config.K9sHome, err)
// }
// }
// // Name returns the component name.
// func (p *PortForward) Name() string {
// return portForwardTitle
// }
// func (p *PortForward) reload() {
// path := ui.BenchConfig(p.App().Config.K9s.CurrentCluster)
// log.Debug().Msgf("Reloading Config %s", path)
// if err := p.App().Bench.Reload(path); err != nil {
// p.App().Flash().Err(err)
// }
// p.refresh()
// }
// func (p *PortForward) refresh() {
// p.Update(p.hydrate())
// p.App().SetFocus(p)
// p.UpdateTitle()
// }
func (p *PortForward) bindKeys(aa ui.KeyActions) { func (p *PortForward) bindKeys(aa ui.KeyActions) {
aa.Add(ui.KeyActions{ aa.Add(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Benchmarks", p.showBenchCmd, true), tcell.KeyEnter: ui.NewKeyAction("Benchmarks", p.showBenchCmd, true),
@ -94,7 +59,9 @@ func (p *PortForward) bindKeys(aa ui.KeyActions) {
} }
func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey { func (p *PortForward) showBenchCmd(evt *tcell.EventKey) *tcell.EventKey {
p.App().inject(NewBenchmark("benchmarks")) if err := p.App().inject(NewBenchmark("benchmarks")); err != nil {
p.App().Flash().Err(err)
}
return nil return nil
} }
@ -193,62 +160,9 @@ func (p *PortForward) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
// func (p *PortForward) hydrate() render.TableData {
// var re render.Forward
// data := render.TableData{
// Header: re.Header(render.AllNamespaces),
// RowEvents: make(render.RowEvents, 0, len(p.App().forwarders)),
// Namespace: render.AllNamespaces,
// }
// containers := p.App().Bench.Benchmarks.Containers
// for _, f := range p.App().forwarders {
// fqn := containerID(f.Path(), f.Container())
// cfg := benchCfg{
// c: p.App().Bench.Benchmarks.Defaults.C,
// n: p.App().Bench.Benchmarks.Defaults.N,
// }
// if config, ok := containers[fqn]; ok {
// cfg.c, cfg.n = config.C, config.N
// cfg.host, cfg.path = config.HTTP.Host, config.HTTP.Path
// }
// var row render.Row
// fwd := forwarder{
// Forwarder: f,
// BenchConfigurator: cfg,
// }
// if err := re.Render(fwd, render.AllNamespaces, &row); err != nil {
// log.Error().Err(err).Msgf("PortForward render failed")
// continue
// }
// data.RowEvents = append(data.RowEvents, render.RowEvent{Kind: render.EventAdd, Row: row})
// }
// return data
// }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers... // Helpers...
// var _ render.PortForwarder = forwarder{}
// type forwarder struct {
// render.Forwarder
// render.BenchConfigurator
// }
// type benchCfg struct {
// c, n int
// host, path string
// }
// func (b benchCfg) C() int { return b.c }
// func (b benchCfg) N() int { return b.n }
// func (b benchCfg) Host() string { return b.host }
// func (b benchCfg) HttpPath() string { return b.path }
func defaultConfig() config.BenchConfig { func defaultConfig() config.BenchConfig {
return config.BenchConfig{ return config.BenchConfig{
C: config.DefaultC, C: config.DefaultC,
@ -279,36 +193,3 @@ func showModal(p *ui.Pages, msg string, ok func()) {
func dismissModal(p *ui.Pages) { func dismissModal(p *ui.Pages) {
p.RemovePage(promptPage) p.RemovePage(promptPage)
} }
func watchFS(ctx context.Context, app *App, dir, file string, cb func()) error {
w, err := fsnotify.NewWatcher()
if err != nil {
return err
}
go func() {
for {
select {
case evt := <-w.Events:
log.Debug().Msgf("FS %s event %v", file, evt.Name)
if file == "" || evt.Name == file {
log.Debug().Msgf("Capturing Event %#v", evt)
app.QueueUpdateDraw(func() {
cb()
})
}
case err := <-w.Errors:
log.Info().Err(err).Msgf("FS %s watcher failed", dir)
return
case <-ctx.Done():
log.Debug().Msgf("<<FS %s WATCHER DONE>>", dir)
if err := w.Close(); err != nil {
log.Error().Err(err).Msg("Closing portforward watcher")
}
return
}
}
}()
return w.Add(dir)
}

View File

@ -9,19 +9,11 @@ import (
"github.com/derailed/k9s/internal/ui" "github.com/derailed/k9s/internal/ui"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
) )
const ( const (
ClusterRole roleKind = iota ClusterRole roleKind = iota
Role Role
clusterWide = "*"
rbacTitle = "Policies"
rbacTitleFmt = " [fg:bg:b]%s([hilite:bg:b]%s[fg:bg:-])[fg:bg:-][[count:bg:b]%d[fg:bg:-]][fg:bg:-] "
) )
var ( var (
@ -62,263 +54,47 @@ func NewRbac(gvr client.GVR) ResourceViewer {
return &r return &r
} }
func (r *Rbac) showPolicies(app *App, ns, resource, selection string) {
log.Debug().Msgf("SHOWING!! %q--%q--%q", ns, resource, selection)
}
func (r *Rbac) UpdateTitle() {
// BOZO!!
// r.GetTable().SetTitle(ui.SkinTitle(fmt.Sprintf(rbacTitleFmt, rbacTitle, r.path, r.GetRowCount()-1), r.app.Styles.Frame()))
}
func (r *Rbac) bindKeys(aa ui.KeyActions) { func (r *Rbac) bindKeys(aa ui.KeyActions) {
aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace) aa.Delete(ui.KeyShiftA, tcell.KeyCtrlSpace, ui.KeySpace)
aa.Add(ui.KeyActions{ aa.Add(ui.KeyActions{
// BOZO!!
// tcell.KeyEscape: ui.NewKeyAction("Reset", r.resetCmd, false),
// ui.KeySlash: ui.NewKeyAction("Filter", r.activateCmd, false),
ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", r.GetTable().SortColCmd(1, true), false), ui.KeyShiftO: ui.NewKeyAction("Sort APIGroup", r.GetTable().SortColCmd(1, true), false),
}) })
} }
// BOZO!! // BOZO!!
// func (r *Rbac) refresh() { // func showClusterRoleBinding(app *App, ns, gvr, path string) {
// if r.app.Conn() == nil { // o, err := app.factory.Get("rbac.authorization.k8s.io/v1/clusterrolebindings", path, labels.Everything())
// if err != nil {
// app.Flash().Err(err)
// return // return
// } // }
// data, err := r.reconcile(r.roleName, r.roleType)
// var crb rbacv1.ClusterRoleBinding
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb)
// if err != nil { // if err != nil {
// log.Error().Err(err).Msgf("Refresh for %s:%d", r.roleName, r.roleType) // app.Flash().Errf("Unable to retrieve clusterrolebindings for %s", path)
// r.app.Flash().Err(err) // return
// } // }
// r.Update(data)
// r.UpdateTitle() // // BOZO!! Must make sure cluster roles are in cache prior to loading rbac view.
// app.factory.ForResource("-", "rbac.authorization.k8s.io/v1/clusterroles")
// app.factory.WaitForCacheSync()
// // BOZO!!
// // app.inject(NewRbac(crb.RoleRef.Name, ClusterRole, selection))
// } // }
// func (r *Rbac) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
// if !r.GetTable().SearchBuff().Empty() {
// r.GetTable().SearchBuff().Reset()
// return nil
// }
// return r.App().PrevCmd(evt)
// }
// func (r *Rbac) backCmd(evt *tcell.EventKey) *tcell.EventKey {
// if r.cancelFn != nil {
// r.cancelFn()
// }
// if r.SearchBuff().IsActive() {
// r.SearchBuff().Reset()
// return nil
// }
// return r.app.PrevCmd(evt)
// }
// func (r *Rbac) reconcile(name string, kind roleKind) (render.TableData, error) {
// var table render.TableData
// rows, err := r.fetchRoles(name, kind)
// if err != nil {
// return table, err
// }
// return buildTable(r, rows), nil
// }
// func (r *Rbac) Header() render.HeaderRow {
// return render.Rbac{}.Header(render.AllNamespaces)
// }
// func (r *Rbac) GetCache() render.RowEvents {
// return r.cache
// }
// func (r *Rbac) SetCache(evts render.RowEvents) {
// r.cache = evts
// }
// func (r *Rbac) fetchRoles(name string, kind roleKind) (render.Rows, error) {
// switch kind {
// case ClusterRole:
// return r.loadClusterRoles(name)
// case Role:
// return r.loadRoles(name)
// default:
// return nil, fmt.Errorf("Expecting clusterrole/role but found %d", kind)
// }
// }
// func (r *Rbac) loadClusterRoles(name string) (render.Rows, error) {
// o, err := r.app.factory.Get("-", "rbac.authorization.k8s.io/v1/clusterroles", name, labels.Everything())
// if err != nil {
// return nil, err
// }
// var cr rbacv1.ClusterRole
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &cr)
// if err != nil {
// return nil, err
// }
// return r.parseRules(cr.Rules), nil
// }
// func (r *Rbac) loadRoles(path string) (render.Rows, error) {
// ns, n := client.Namespaced(path)
// o, err := r.app.factory.Get(ns, "rbac.authorization.k8s.io/v1/roles", n, labels.Everything())
// if err != nil {
// return nil, err
// }
// var ro rbacv1.Role
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &ro)
// if err != nil {
// return nil, err
// }
// return r.parseRules(ro.Rules), nil
// }
// func (r *Rbac) parseRules(rules []rbacv1.PolicyRule) render.Rows {
// m := make(render.Rows, 0, len(rules))
// for _, rule := range rules {
// for _, grp := range rule.APIGroups {
// for _, res := range rule.Resources {
// k := res
// if grp != "" {
// k = res + "." + grp
// }
// for _, na := range rule.ResourceNames {
// m = m.Upsert(r.prepRow(fqn(k, na), grp, rule.Verbs))
// }
// m = m.Upsert(r.prepRow(k, grp, rule.Verbs))
// }
// }
// for _, nres := range rule.NonResourceURLs {
// if nres[0] != '/' {
// nres = "/" + nres
// }
// m = m.Upsert(r.prepRow(nres, "", rule.Verbs))
// }
// }
// return m
// }
// func (r *Rbac) prepRow(res, grp string, verbs []string) render.Row {
// if grp != "" {
// grp = toGroup(grp)
// }
// fields := make(render.Fields, 0, len(r.Header()))
// fields = append(fields, res, group)
// return render.Row{
// ID: res,
// Fields: append(fields, verbs...),
// }
// }
// func asVerbs(verbs ...string) []string {
// const (
// verbLen = 4
// unknownLen = 30
// )
// r := make([]string, 0, len(k8sVerbs)+1)
// for _, v := range k8sVerbs {
// r = append(r, toVerbIcon(hasVerb(verbs, v)))
// }
// var unknowns []string
// for _, v := range verbs {
// if hv, ok := httpTok8sVerbs[v]; ok {
// v = hv
// }
// if !hasVerb(k8sVerbs, v) && v != clusterWide {
// unknowns = append(unknowns, v)
// }
// }
// return append(r, resource.Truncate(strings.Join(unknowns, ","), unknownLen))
// }
// func toVerbIcon(ok bool) string {
// if ok {
// return "[green::b] ✓ [::]"
// }
// return "[orangered::b] 𐄂 [::]"
// }
// func hasVerb(verbs []string, verb string) bool {
// if len(verbs) == 1 && verbs[0] == clusterWide {
// return true
// }
// for _, v := range verbs {
// if hv, ok := httpTok8sVerbs[v]; ok {
// if hv == verb {
// return true
// }
// }
// if v == verb {
// return true
// }
// }
// return false
// }
// func toGroup(g string) string {
// if g == "" {
// return "v1"
// }
// return g
// }
func showRoleBinding(app *App, _, resource, selection string) {
// ns, n := client.Namespaced(selection)
// rb, err := app.Conn().DialOrDie().RbacV1().RoleBindings(ns).Get(n, metav1.GetOptions{})
// if err != nil {
// app.Flash().Errf("Unable to retrieve rolebindings for %s", selection)
// return
// }
// BOZO!!
// app.inject(NewRbac(fqn(ns, rb.RoleRef.Name), Role, selection))
}
func showClusterRoleBinding(app *App, ns, gvr, path string) {
o, err := app.factory.Get("rbac.authorization.k8s.io/v1/clusterrolebindings", path, labels.Everything())
if err != nil {
app.Flash().Err(err)
return
}
var crb rbacv1.ClusterRoleBinding
err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb)
if err != nil {
app.Flash().Errf("Unable to retrieve clusterrolebindings for %s", path)
return
}
// BOZO!! Must make sure cluster roles are in cache prior to loading rbac view.
app.factory.ForResource("-", "rbac.authorization.k8s.io/v1/clusterroles")
app.factory.WaitForCacheSync()
// BOZO!!
// app.inject(NewRbac(crb.RoleRef.Name, ClusterRole, selection))
}
func showRBAC(app *App, _, gvr, path string) { func showRBAC(app *App, _, gvr, path string) {
log.Debug().Msgf("Showing RBAC %q--%q", gvr, path) log.Debug().Msgf("Showing RBAC %q--%q", gvr, path)
v := NewRbac(client.GVR("rbac")) v := NewRbac(client.GVR("rbac"))
v.SetContextFn(rbacCtxt(app, gvr, path)) v.SetContextFn(rbacCtxt(gvr, path))
app.inject(v)
if err := app.inject(v); err != nil {
app.Flash().Err(err)
}
} }
func rbacCtxt(app *App, gvr, path string) ContextFunc { func rbacCtxt(gvr, path string) ContextFunc {
return func(ctx context.Context) context.Context { return func(ctx context.Context) context.Context {
ctx = context.WithValue(ctx, internal.KeyPath, path) ctx = context.WithValue(ctx, internal.KeyPath, path)
return context.WithValue(ctx, internal.KeyGVR, gvr) return context.WithValue(ctx, internal.KeyGVR, gvr)

View File

@ -5,7 +5,6 @@ import (
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao" "github.com/derailed/k9s/internal/dao"
"github.com/rs/zerolog/log"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -16,16 +15,6 @@ func ToResource(o *unstructured.Unstructured, obj interface{}) error {
return runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &obj) return runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &obj)
} }
func showCRD(app *App, ns, resource, selection string) {
log.Debug().Msgf("Launching CRD %q -- %q -- %q", ns, resource, selection)
tokens := strings.Split(selection, ".")
_ = tokens
panic("NYI")
// if !app.gotoResource(tokens[0]) {
// app.Flash().Errf("Goto %s failed", tokens[0])
// }
}
func loadAliases() error { func loadAliases() error {
if err := aliases.Load(); err != nil { if err := aliases.Load(); err != nil {
return err return err
@ -57,8 +46,6 @@ func loadCustomViewers() MetaViewers {
miscRes(m) miscRes(m)
appsRes(m) appsRes(m)
rbacRes(m) rbacRes(m)
extRes(m)
netRes(m)
batchRes(m) batchRes(m)
return m return m
@ -80,30 +67,6 @@ func coreRes(vv MetaViewers) {
vv["v1/secrets"] = MetaViewer{ vv["v1/secrets"] = MetaViewer{
viewerFn: NewSecret, viewerFn: NewSecret,
} }
// vv["v1/serviceaccounts"] = MetaViewer{
// listFn: resource.NewServiceAccountList,
// enterFn: showSAPolicy,
// }
// vv["v1/configmaps"] = MetaViewer{
// listFn: resource.NewConfigMapList,
// }
// vv["v1/persistentvolumes"] = MetaViewer{
// listFn: resource.NewPersistentVolumeList,
// }
// vv["v1/persistentvolumeclaims"] = MetaViewer{
// listFn: resource.NewPersistentVolumeClaimList,
// }
// vv["v1/endpoints"] = MetaViewer{
// listFn: resource.NewEndpointsList,
// }
// vv["v1/events"] = MetaViewer{
// listFn: resource.NewEventList,
// }
// vv["v1/replicationcontrollers"] = MetaViewer{
// viewFn: NewReplicationController,
// listFn: resource.NewReplicationControllerList,
// }
} }
func miscRes(vv MetaViewers) { func miscRes(vv MetaViewers) {
@ -119,16 +82,6 @@ func miscRes(vv MetaViewers) {
vv["screendumps"] = MetaViewer{ vv["screendumps"] = MetaViewer{
viewerFn: NewScreenDump, viewerFn: NewScreenDump,
} }
// vv["storage.k8s.io/v1/storageclasses"] = MetaViewer{
// listFn: resource.NewStorageClassList,
// }
// vv["users"] = MetaViewer{
// viewFn: NewSubject,
// }
// vv["groups"] = MetaViewer{
// viewFn: NewSubject,
// }
vv["benchmarks"] = MetaViewer{ vv["benchmarks"] = MetaViewer{
viewerFn: NewBenchmark, viewerFn: NewBenchmark,
} }
@ -173,26 +126,6 @@ func rbacRes(vv MetaViewers) {
} }
} }
func extRes(vv MetaViewers) {
// vv["apiextensions.k8s.io/v1/customresourcedefinitions"] = MetaViewer{
// listFn: resource.NewCustomResourceDefinitionList,
// enterFn: showCRD,
// }
// vv["apiextensions.k8s.io/v1beta1/customresourcedefinitions"] = MetaViewer{
// listFn: resource.NewCustomResourceDefinitionList,
// enterFn: showCRD,
// }
}
func netRes(vv MetaViewers) {
// vv["networking.k8s.io/v1/networkpolicies"] = MetaViewer{
// listFn: resource.NewNetworkPolicyList,
// }
// vv["extensions/v1beta1/ingresses"] = MetaViewer{
// listFn: resource.NewIngressList,
// }
}
func batchRes(vv MetaViewers) { func batchRes(vv MetaViewers) {
vv["batch/v1beta1/cronjobs"] = MetaViewer{ vv["batch/v1beta1/cronjobs"] = MetaViewer{
viewerFn: NewCronJob, viewerFn: NewCronJob,
@ -201,16 +134,3 @@ func batchRes(vv MetaViewers) {
viewerFn: NewJob, viewerFn: NewJob,
} }
} }
// BOZO!!
// func policyRes(vv MetaViewers) {
// vv["policy/v1beta1/poddisruptionbudgets"] = MetaViewer{
// listFn: resource.NewPDBList,
// }
// }
// func autoscalingRes(vv MetaViewers) {
// vv["autoscaling/v1/horizontalpodautoscalers"] = MetaViewer{
// listFn: resource.NewHorizontalPodAutoscalerV1List,
// }
// }

View File

@ -9,13 +9,10 @@ import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/fsnotify/fsnotify"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
const dumpTitle = "Screen Dumps"
// ScreenDump presents a directory listing viewer. // ScreenDump presents a directory listing viewer.
type ScreenDump struct { type ScreenDump struct {
ResourceViewer ResourceViewer
@ -26,7 +23,6 @@ func NewScreenDump(gvr client.GVR) ResourceViewer {
s := ScreenDump{ s := ScreenDump{
ResourceViewer: NewBrowser(gvr), ResourceViewer: NewBrowser(gvr),
} }
// BOZO!! Rename Table
s.GetTable().SetBorderFocusColor(tcell.ColorSteelBlue) s.GetTable().SetBorderFocusColor(tcell.ColorSteelBlue)
s.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorRoyalBlue, tcell.AttrNone) s.GetTable().SetSelectedStyle(tcell.ColorWhite, tcell.ColorRoyalBlue, tcell.AttrNone)
s.GetTable().SetColorerFn(render.ScreenDump{}.ColorerFunc()) s.GetTable().SetColorerFn(render.ScreenDump{}.ColorerFunc())
@ -38,22 +34,6 @@ func NewScreenDump(gvr client.GVR) ResourceViewer {
return &s return &s
} }
// BOZO!!
// BOZO !! Need model watcher!
// // Start starts the directory watcher.
// func (s *ScreenDump) Start() {
// s.Stop()
// s.GetTable().Actions().Delete(tcell.KeyCtrlS)
// s.GetTable().Start()
// var ctx context.Context
// ctx, s.GetTable().cancelFn = context.WithCancel(context.Background())
// if err := s.watchDumpDir(ctx); err != nil {
// s.App().Flash().Errf("Unable to watch screen dumps directory %s", err)
// }
// }
func (s *ScreenDump) dirContext(ctx context.Context) context.Context { func (s *ScreenDump) dirContext(ctx context.Context) context.Context {
dir := filepath.Join(config.K9sDumpDir, s.App().Config.K9s.CurrentCluster) dir := filepath.Join(config.K9sDumpDir, s.App().Config.K9s.CurrentCluster)
return context.WithValue(ctx, internal.KeyDir, dir) return context.WithValue(ctx, internal.KeyDir, dir)
@ -68,31 +48,3 @@ func (s *ScreenDump) edit(app *App, ns, resource, path string) {
app.Flash().Err(errors.New("Failed to launch editor")) app.Flash().Err(errors.New("Failed to launch editor"))
} }
} }
func (s *ScreenDump) watchDumpDir(ctx context.Context) error {
w, err := fsnotify.NewWatcher()
if err != nil {
return err
}
go func() {
for {
select {
case evt := <-w.Events:
log.Debug().Msgf("ScreenDump event detected %#v", evt)
s.Refresh()
case err := <-w.Errors:
log.Error().Err(err).Msg("Dir Watcher failed")
return
case <-ctx.Done():
log.Debug().Msg("!!!! ScreenDump WATCHER DONE!!")
if err := w.Close(); err != nil {
log.Error().Err(err).Msg("Closing dump watcher")
}
return
}
}
}()
return w.Add(filepath.Join(config.K9sDumpDir, s.App().Config.K9s.CurrentCluster))
}

View File

@ -67,7 +67,9 @@ func (s *Secret) decodeCmd(evt *tcell.EventKey) *tcell.EventKey {
details.SetTextColor(s.App().Styles.FgColor()) details.SetTextColor(s.App().Styles.FgColor())
details.SetText(colorizeYAML(s.App().Styles.Views().Yaml, string(raw))) details.SetText(colorizeYAML(s.App().Styles.Views().Yaml, string(raw)))
details.ScrollToBeginning() details.ScrollToBeginning()
s.App().inject(details) if err := s.App().inject(details); err != nil {
s.App().Flash().Err(err)
}
return nil return nil
} }

View File

@ -19,7 +19,6 @@ type (
ResourceViewer ResourceViewer
subjectKind string subjectKind string
cache render.RowEvents
} }
) )
@ -27,32 +26,13 @@ type (
func NewSubject(gvr client.GVR) ResourceViewer { func NewSubject(gvr client.GVR) ResourceViewer {
s := Subject{ResourceViewer: NewBrowser(gvr)} s := Subject{ResourceViewer: NewBrowser(gvr)}
s.GetTable().SetColorerFn(render.Subject{}.ColorerFunc()) s.GetTable().SetColorerFn(render.Subject{}.ColorerFunc())
// BOZO!!
// s.GetTable().SetSortCol(1, len(s.Header()), true) // s.GetTable().SetSortCol(1, len(s.Header()), true)
s.SetBindKeysFn(s.bindKeys) s.SetBindKeysFn(s.bindKeys)
return &s return &s
} }
// BOZO!!
// // Start runs the refresh loop.
// func (s *Subject) Start() {
// s.Stop()
// var ctx context.Context
// ctx, s.cancelFn = context.WithCancel(context.Background())
// go func(ctx context.Context) {
// for {
// select {
// case <-ctx.Done():
// log.Debug().Msgf("Subject:%s Watch bailing out!", s.subjectKind)
// return
// case <-time.After(time.Duration(s.App().Config.K9s.GetRefreshRate()) * time.Second):
// s.refresh()
// }
// }
// }(ctx)
// }
// Name returns the component name // Name returns the component name
func (s *Subject) Name() string { func (s *Subject) Name() string {
return "subjects" return "subjects"
@ -62,10 +42,7 @@ func (s *Subject) bindKeys(aa ui.KeyActions) {
aa.Delete(ui.KeyShiftA, ui.KeyShiftP, tcell.KeyCtrlSpace, ui.KeySpace) aa.Delete(ui.KeyShiftA, ui.KeyShiftP, tcell.KeyCtrlSpace, ui.KeySpace)
aa.Add(ui.KeyActions{ aa.Add(ui.KeyActions{
tcell.KeyEnter: ui.NewKeyAction("Policies", s.policyCmd, true), tcell.KeyEnter: ui.NewKeyAction("Policies", s.policyCmd, true),
// BOZO!! ui.KeyShiftK: ui.NewKeyAction("Sort Kind", s.GetTable().SortColCmd(1, true), false),
// tcell.KeyEscape: ui.NewKeyAction("Back", s.resetCmd, false),
// ui.KeySlash: ui.NewKeyAction("Filter", s.activateCmd, false),
ui.KeyShiftK: ui.NewKeyAction("Sort Kind", s.GetTable().SortColCmd(1, true), false),
}) })
} }
@ -74,24 +51,12 @@ func (s *Subject) SetSubject(n string) {
s.subjectKind = mapSubject(n) s.subjectKind = mapSubject(n)
} }
// BOZO!!
// func (s *Subject) refresh() {
// log.Debug().Msgf("Refreshing Subject...")
// data, err := s.reconcile()
// if err != nil {
// log.Error().Err(err).Msgf("Refresh for %s", s.subjectKind)
// s.App().Flash().Err(err)
// }
// s.App().QueueUpdateDraw(func() {
// s.GetTable().Update(data)
// })
// }
func (s *Subject) policyCmd(evt *tcell.EventKey) *tcell.EventKey { func (s *Subject) policyCmd(evt *tcell.EventKey) *tcell.EventKey {
if !s.GetTable().RowSelected() { if !s.GetTable().RowSelected() {
return evt return evt
} }
// BOZO!!
// _, n := client.Namespaced(s.GetSelectedItem()) // _, n := client.Namespaced(s.GetSelectedItem())
// subject, err := mapFuSubject(s.subjectKind) // subject, err := mapFuSubject(s.subjectKind)
// if err != nil { // if err != nil {
@ -103,184 +68,3 @@ func (s *Subject) policyCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
// func (s *Subject) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
// if !s.SearchBuff().Empty() {
// s.SearchBuff().Reset()
// return nil
// }
// return s.backCmd(evt)
// }
// func (s *Subject) backCmd(evt *tcell.EventKey) *tcell.EventKey {
// if s.SearchBuff().IsActive() {
// s.SearchBuff().Reset()
// return nil
// }
// return s.App().PrevCmd(evt)
// }
// func (s *Subject) reconcile() (render.TableData, error) {
// var table render.TableData
// if s.App().Conn() == nil {
// return table, nil
// }
// rows, err := s.fetchClusterRoleBindings()
// if err != nil {
// return table, err
// }
// nrows, err := s.fetchRoleBindings()
// if err != nil {
// return table, err
// }
// for k, v := range nrows {
// rows[k] = v
// }
// return buildTable(s, rows), nil
// }
// func (s *Subject) Header() render.HeaderRow {
// return render.Subject{}.Header(render.AllNamespaces)
// }
// func (s *Subject) GetCache() render.RowEvents {
// return s.cache
// }
// func (s *Subject) SetCache(rows render.RowEvents) {
// s.cache = rows
// }
// func buildTable(c TableInfo, rows render.Rows) render.TableData {
// table := render.TableData{
// Header: c.Header(),
// Namespace: "*",
// }
// cache := c.GetCache()
// if len(cache) == 0 {
// cache := make(render.RowEvents, 0, len(rows))
// for _, row := range rows {
// cache = append(cache, render.RowEvent{Kind: render.EventAdd, Row: row})
// }
// table.RowEvents = cache
// return table
// }
// for _, row := range rows {
// idx, ok := cache.FindIndex(row.ID)
// if !ok {
// cache = append(cache, render.RowEvent{Kind: render.EventAdd, Row: row})
// continue
// }
// old := cache[idx].Row
// deltas := make(render.DeltaRow, len(row.Fields))
// if reflect.DeepEqual(old, row) {
// cache[idx].Kind = render.EventUnchanged
// cache[idx].Deltas = deltas
// continue
// }
// cache[idx].Kind = render.EventUpdate
// for i, field := range old.Fields {
// if field != row.Fields[i] {
// deltas[i] = field
// }
// }
// cache[idx].Deltas = deltas
// }
// for _, row := range rows {
// if _, ok := cache.FindIndex(row.ID); !ok {
// cache.Delete(row.ID)
// }
// }
// table.RowEvents = cache
// return table
// }
// func (s *Subject) fetchClusterRoleBindings() (render.Rows, error) {
// s.App().factory.Preload(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterroles")
// oo, err := s.App().factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterrolebindings", labels.Everything())
// if err != nil {
// return nil, err
// }
// rows := make(render.Rows, 0, len(oo))
// for _, o := range oo {
// var crb rbacv1.ClusterRoleBinding
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &crb)
// if err != nil {
// return nil, err
// }
// for _, subject := range crb.Subjects {
// if subject.Kind != s.subjectKind {
// continue
// }
// rows = append(rows, render.Row{
// ID: subject.Name,
// Fields: render.Fields{subject.Name, "ClusterRoleBinding", crb.Name},
// })
// }
// }
// return rows, nil
// }
// func (s *Subject) fetchRoleBindings() (render.Rows, error) {
// s.App().factory.Preload(render.ClusterScope, "rbac.authorization.k8s.io/v1/clusterroles")
// oo, err := s.App().factory.List(render.ClusterScope, "rbac.authorization.k8s.io/v1/rolebindings", labels.Everything())
// if err != nil {
// return nil, err
// }
// rows := make(render.Rows, 0, len(oo))
// for _, o := range oo {
// var rb rbacv1.RoleBinding
// err = runtime.DefaultUnstructuredConverter.FromUnstructured(o.(*unstructured.Unstructured).Object, &rb)
// if err != nil {
// return nil, err
// }
// for _, subject := range rb.Subjects {
// if subject.Kind == s.subjectKind {
// rows = append(rows, render.Row{
// ID: subject.Name,
// Fields: render.Fields{subject.Name, "RoleBinding", rb.Name},
// })
// }
// }
// }
// return rows, nil
// }
// func mapCmdSubject(subject string) string {
// switch subject {
// case "groups":
// return group
// case "sas":
// return sa
// default:
// return user
// }
// }
// func mapFuSubject(subject string) (string, error) {
// switch subject {
// case group:
// return "g", nil
// case sa:
// return "s", nil
// case user:
// return "u", nil
// default:
// return "", fmt.Errorf("Unknown subject %q should be one of user, group, serviceaccount", subject)
// }
// }

View File

@ -93,15 +93,6 @@ func (t *Table) saveCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil return nil
} }
func (t *Table) setFilterFn(fn func(string)) {
t.filterFn = fn
cmd := t.SearchBuff().String()
if ui.IsLabelSelector(cmd) && t.filterFn != nil {
t.filterFn(ui.TrimLabelSelector(cmd))
}
}
func (t *Table) bindKeys() { func (t *Table) bindKeys() {
t.Actions().Add(ui.KeyActions{ t.Actions().Add(ui.KeyActions{
ui.KeySpace: ui.NewKeyAction("Mark", t.markCmd, true), ui.KeySpace: ui.NewKeyAction("Mark", t.markCmd, true),

View File

@ -65,12 +65,7 @@ func saveTable(cluster, title, path string, data render.TableData) (string, erro
}() }()
w := csv.NewWriter(out) w := csv.NewWriter(out)
// BOZO!! Method on header if err := w.Write(data.Header.Columns()); err != nil {
headers := make([]string, len(data.Header))
for i, h := range data.Header {
headers[i] = h.Name
}
if err := w.Write([]string(headers)); err != nil {
return "", err return "", err
} }

View File

@ -13,7 +13,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
di "k8s.io/client-go/dynamic/dynamicinformer" di "k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/informers" "k8s.io/client-go/informers"
"k8s.io/client-go/informers/internalinterfaces"
) )
const ( const (
@ -22,46 +21,13 @@ const (
clusterScope = "-" clusterScope = "-"
) )
// BOZO!!
// // Authorizer checks what a user can or cannot do to a resource.
// type Authorizer interface {
// // CanI returns true if the user can use these actions for a given resource.
// CanI(ns, gvr string, verbs []string) (bool, error)
// }
// type Connection interface {
// Authorizer
// // DialOrDie dials client api.
// DialOrDie() kubernetes.Interface
// // MXDial dials metrics api.
// MXDial() (*versioned.Clientset, error)
// // DynDialOrDie dials dynamic client api.
// DynDialOrDie() dynamic.Interface
// // RestConfigOrDie return a client configuration.
// RestConfigOrDie() *restclient.Config
// // Config returns the current kubeconfig.
// Config() *k8s.Config
// // CachedDiscovery returns a cached client.
// CachedDiscovery() (*disk.CachedDiscoveryClient, error)
// // SwithContextOrDie switch to a new kube context.
// SwitchContextOrDie(ctx string)
// }
// Factory tracks various resource informers. // Factory tracks various resource informers.
type Factory struct { type Factory struct {
factories map[string]di.DynamicSharedInformerFactory factories map[string]di.DynamicSharedInformerFactory
client client.Connection client client.Connection
stopChan chan struct{} stopChan chan struct{}
tweakListOptions internalinterfaces.TweakListOptionsFunc activeNS string
activeNS string forwarders Forwarders
forwarders Forwarders
} }
// NewFactory returns a new informers factory. // NewFactory returns a new informers factory.
@ -259,14 +225,6 @@ func (f *Factory) ensureFactory(ns string) di.DynamicSharedInformerFactory {
return f.factories[ns] return f.factories[ns]
} }
func (f *Factory) register(gvr, ns string, stopChan <-chan struct{}) error {
log.Debug().Msgf("Registering GVR %q - %s", ns, gvr)
f.factories[ns].ForResource(toGVR(gvr))
f.factories[ns].Start(stopChan)
return nil
}
func toGVR(gvr string) schema.GroupVersionResource { func toGVR(gvr string) schema.GroupVersionResource {
log.Debug().Msgf("GVR -- %q", gvr) log.Debug().Msgf("GVR -- %q", gvr)
tokens := strings.Split(gvr, "/") tokens := strings.Split(gvr, "/")