checkpoint

mine
derailed 2019-12-26 13:53:49 -07:00
parent add0d678f0
commit 99fa0e9952
40 changed files with 541 additions and 413 deletions

2
go.mod
View File

@ -36,7 +36,7 @@ require (
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
github.com/fsnotify/fsnotify v1.4.7 github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell v1.3.0 github.com/gdamore/tcell v1.3.0
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/golang/mock v1.2.0 github.com/golang/mock v1.2.0
github.com/google/btree v1.0.0 // indirect github.com/google/btree v1.0.0 // indirect

View File

@ -64,11 +64,11 @@ func (g GVR) ToRAndG() (string, string) {
tokens := strings.Split(string(g), "/") tokens := strings.Split(string(g), "/")
switch len(tokens) { switch len(tokens) {
case 3: case 3:
return tokens[0], tokens[2] return tokens[2], tokens[0]
case 2: case 2:
return "", tokens[1] return tokens[1], "core"
default: default:
return "", tokens[0] return tokens[0], "core"
} }
} }

View File

@ -24,9 +24,9 @@ type Aliases struct {
// NewAliases return a new alias. // NewAliases return a new alias.
func NewAliases() Aliases { func NewAliases() Aliases {
aa := Aliases{Alias: make(Alias, 50)} return Aliases{
aa.loadDefaults() Alias: make(Alias, 50),
return aa }
} }
func (a Aliases) loadDefaults() { func (a Aliases) loadDefaults() {
@ -81,6 +81,7 @@ func (a Aliases) loadDefaults() {
// Load K9s aliases. // Load K9s aliases.
func (a Aliases) Load() error { func (a Aliases) Load() error {
a.loadDefaults()
return a.LoadAliases(K9sAlias) return a.LoadAliases(K9sAlias)
} }

View File

@ -73,7 +73,7 @@ func TestAliasesLoad(t *testing.T) {
a := config.NewAliases() a := config.NewAliases()
assert.Nil(t, a.LoadAliases("test_assets/alias.yml")) assert.Nil(t, a.LoadAliases("test_assets/alias.yml"))
assert.Equal(t, 27, len(a.Alias)) assert.Equal(t, 2, len(a.Alias))
} }
func TestAliasesSave(t *testing.T) { func TestAliasesSave(t *testing.T) {
@ -83,5 +83,5 @@ func TestAliasesSave(t *testing.T) {
assert.Nil(t, a.SaveAliases("/tmp/a.yml")) assert.Nil(t, a.SaveAliases("/tmp/a.yml"))
assert.Nil(t, a.LoadAliases("/tmp/a.yml")) assert.Nil(t, a.LoadAliases("/tmp/a.yml"))
assert.Equal(t, 28, len(a.Alias)) assert.Equal(t, 2, len(a.Alias))
} }

View File

@ -1,8 +1,64 @@
package dao package dao
// Alias represents an alias resource. import (
"strings"
"github.com/derailed/k9s/internal/config"
)
// Alias tracks standard and custom command aliases.
type Alias struct { type Alias struct {
Generic config.Aliases
factory Factory
} }
var _ Accessor = &Alias{} // NewAlias returns a new set of aliases.
func NewAlias(f Factory) *Alias {
return &Alias{
Aliases: config.NewAliases(),
factory: f,
}
}
// ClearAliases remove all aliases.
func (a *Alias) Clear() {
for k := range a.Alias {
delete(a.Alias, k)
}
}
// Ensure makes sure alias are loaded.
func (a *Alias) Ensure() (config.Alias, error) {
if len(a.Alias) == 0 {
if err := LoadResources(a.factory); err != nil {
return config.Alias{}, err
}
return a.Alias, a.load()
}
return a.Alias, nil
}
func (a *Alias) load() error {
if err := a.Load(); err != nil {
return err
}
for _, gvr := range AllGVRs() {
meta, err := MetaFor(gvr)
if err != nil {
return err
}
if _, ok := a.Alias[meta.Kind]; ok {
continue
}
a.Define(string(gvr), strings.ToLower(meta.Kind), meta.Name)
if meta.SingularName != "" {
a.Define(string(gvr), meta.SingularName)
}
if meta.ShortNames != nil {
a.Define(string(gvr), meta.ShortNames...)
}
}
return nil
}

View File

@ -2,8 +2,6 @@ package dao
import ( import (
"os" "os"
"github.com/rs/zerolog/log"
) )
// Benchmark represents a benchmark resource. // Benchmark represents a benchmark resource.
@ -16,6 +14,5 @@ var _ Nuker = &Benchmark{}
// Delete a Benchmark. // Delete a Benchmark.
func (d *Benchmark) Delete(path string, cascade, force bool) error { func (d *Benchmark) Delete(path string, cascade, force bool) error {
log.Debug().Msgf("Benchmark DELETE %q", path)
return os.Remove(path) return os.Remove(path)
} }

View File

@ -4,12 +4,11 @@ import (
"fmt" "fmt"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "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/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
) )
type Context struct { type Context struct {
@ -29,7 +28,7 @@ func (c *Context) Get(_, n string) (runtime.Object, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &NamedContext{Name: n, Context: ctx}, nil return &render.NamedContext{Name: n, Context: ctx}, nil
} }
// List all Contexts on the current cluster. // List all Contexts on the current cluster.
@ -40,7 +39,7 @@ func (c *Context) List(string, metav1.ListOptions) ([]runtime.Object, error) {
} }
cc := make([]runtime.Object, 0, len(ctxs)) cc := make([]runtime.Object, 0, len(ctxs))
for k, v := range ctxs { for k, v := range ctxs {
cc = append(cc, NewNamedContext(c.config(), k, v)) cc = append(cc, render.NewNamedContext(c.config(), k, v))
} }
return cc, nil return cc, nil
@ -90,33 +89,33 @@ func (c *Context) KubeUpdate(n string) error {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// NamedContext represents a named cluster context. // // NamedContext represents a named cluster context.
type NamedContext struct { // type NamedContext struct {
Name string // Name string
Context *api.Context // Context *api.Context
config *client.Config // config *client.Config
} // }
// NewNamedContext returns a new named context. // // NewNamedContext returns a new named context.
func NewNamedContext(c *client.Config, n string, ctx *api.Context) *NamedContext { // func NewNamedContext(c *client.Config, n string, ctx *api.Context) *NamedContext {
return &NamedContext{Name: n, Context: ctx, config: c} // return &NamedContext{Name: n, Context: ctx, config: c}
} // }
// MustCurrentContextName return the active context name. // // MustCurrentContextName return the active context name.
func (c *NamedContext) MustCurrentContextName() string { // func (c *NamedContext) MustCurrentContextName() string {
cl, err := c.config.CurrentContextName() // cl, err := c.config.CurrentContextName()
if err != nil { // if err != nil {
log.Fatal().Err(err).Msg("Fetching current context") // log.Fatal().Err(err).Msg("Fetching current context")
} // }
return cl // return cl
} // }
// GetObjectKind returns a schema object. // // GetObjectKind returns a schema object.
func (c *NamedContext) GetObjectKind() schema.ObjectKind { // func (c *NamedContext) GetObjectKind() schema.ObjectKind {
return nil // return nil
} // }
// DeepCopyObject returns a container copy. // // DeepCopyObject returns a container copy.
func (c *NamedContext) DeepCopyObject() runtime.Object { // func (c *NamedContext) DeepCopyObject() runtime.Object {
return c // return c
} // }

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
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"
@ -62,7 +61,6 @@ func (d *Deployment) Restart(path string) error {
// Logs tail logs for all pods represented by this Deployment. // Logs tail logs for all pods represented by this Deployment.
func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (d *Deployment) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
log.Debug().Msgf("Tailing Deployment %q", opts.Path)
o, err := d.Get(string(d.gvr), opts.Path, labels.Everything()) o, err := d.Get(string(d.gvr), opts.Path, labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -52,7 +52,6 @@ func (d *DaemonSet) Restart(path string) error {
// Logs tail logs for all pods represented by this DaemonSet. // Logs tail logs for all pods represented by this DaemonSet.
func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (d *DaemonSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
log.Debug().Msgf("Tailing DaemonSet %q", opts.Path)
o, err := d.Get("apps/v1/daemonsets", opts.Path, labels.Everything()) o, err := d.Get("apps/v1/daemonsets", opts.Path, labels.Everything())
if err != nil { if err != nil {
return err return err
@ -112,6 +111,7 @@ func podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts L
return nil return nil
} }
// ----------------------------------------------------------------------------
// Helpers... // Helpers...
func toSelector(m map[string]string) string { func toSelector(m map[string]string) string {

View File

@ -2,7 +2,6 @@ package dao
import ( import (
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
) )
@ -25,7 +24,6 @@ func (g *Generic) Delete(path string, cascade, force bool) error {
} }
ns, n := client.Namespaced(path) ns, n := client.Namespaced(path)
log.Debug().Msgf("DELETING %q:%q -- %q", ns, n, path)
opts := metav1.DeleteOptions{PropagationPolicy: &p} opts := metav1.DeleteOptions{PropagationPolicy: &p}
if ns != "-" { if ns != "-" {
return g.dynClient().Namespace(ns).Delete(n, &opts) return g.dynClient().Namespace(ns).Delete(n, &opts)

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"github.com/rs/zerolog/log"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/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"
@ -21,7 +20,6 @@ var _ Loggable = &Job{}
// Logs tail logs for all pods represented by this Job. // Logs tail logs for all pods represented by this Job.
func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (j *Job) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
log.Debug().Msgf("Tailing Job %#v", opts)
o, err := j.Get(string(j.gvr), opts.Path, labels.Everything()) o, err := j.Get(string(j.gvr), opts.Path, labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -1,9 +1,5 @@
package dao package dao
import (
"github.com/rs/zerolog/log"
)
type PortForward struct { type PortForward struct {
Generic Generic
} }
@ -13,7 +9,6 @@ var _ Nuker = &PortForward{}
// Delete a portforward. // Delete a portforward.
func (p *PortForward) Delete(path string, cascade, force bool) error { func (p *PortForward) Delete(path string, cascade, force bool) error {
log.Debug().Msgf("PortForward DELETE %q", path)
p.Factory.DeleteForwarder(path) p.Factory.DeleteForwarder(path)
return nil return nil
} }

View File

@ -5,7 +5,6 @@ import (
"sort" "sort"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/watch"
"github.com/rs/zerolog/log" "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"
@ -26,7 +25,6 @@ var resMetas = ResourceMetas{}
// Customize here for non resource types or types with metrics or logs. // Customize here for non resource types or types with metrics or logs.
func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) { func AccessorFor(f Factory, gvr client.GVR) (Accessor, error) {
m := Accessors{ m := Accessors{
"alias": &Alias{},
"contexts": &Context{}, "contexts": &Context{},
"containers": &Container{}, "containers": &Container{},
"screendumps": &ScreenDump{}, "screendumps": &ScreenDump{},
@ -88,7 +86,8 @@ func IsK9sMeta(m metav1.APIResource) bool {
} }
// Load hydrates server preferred+CRDs resource metadata. // Load hydrates server preferred+CRDs resource metadata.
func Load(f *watch.Factory) error { func LoadResources(f Factory) error {
log.Debug().Msgf("LOAD RES")
resMetas = make(ResourceMetas, 100) resMetas = make(ResourceMetas, 100)
if err := loadPreferred(f, resMetas); err != nil { if err := loadPreferred(f, resMetas); err != nil {
return err return err
@ -136,12 +135,12 @@ func loadNonResource(m ResourceMetas) error {
Categories: []string{"k9s"}, Categories: []string{"k9s"},
} }
m["rbac"] = metav1.APIResource{ m["rbac"] = metav1.APIResource{
Name: "Rbac", Name: "rbacs",
Kind: "Rules", Kind: "Rules",
Categories: []string{"k9s"}, Categories: []string{"k9s"},
} }
m["policy"] = metav1.APIResource{ m["policy"] = metav1.APIResource{
Name: "Policy", Name: "policies",
Kind: "Rules", Kind: "Rules",
Namespaced: true, Namespaced: true,
Categories: []string{"k9s"}, Categories: []string{"k9s"},
@ -158,14 +157,14 @@ func loadNonResource(m ResourceMetas) error {
} }
m["groups"] = metav1.APIResource{ m["groups"] = metav1.APIResource{
Name: "groups", Name: "groups",
Kind: "group", Kind: "Group",
Categories: []string{"k9s"}, Categories: []string{"k9s"},
} }
return nil return nil
} }
func loadPreferred(f *watch.Factory, m ResourceMetas) error { func loadPreferred(f Factory, m ResourceMetas) error {
discovery, err := f.Client().CachedDiscovery() discovery, err := f.Client().CachedDiscovery()
if err != nil { if err != nil {
return err return err
@ -185,7 +184,7 @@ func loadPreferred(f *watch.Factory, m ResourceMetas) error {
return nil return nil
} }
func loadCRDs(f *watch.Factory, m ResourceMetas) error { func loadCRDs(f Factory, m ResourceMetas) error {
oo, err := f.List("apiextensions.k8s.io/v1beta1/customresourcedefinitions", "", labels.Everything()) oo, err := f.List("apiextensions.k8s.io/v1beta1/customresourcedefinitions", "", labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -2,8 +2,6 @@ package dao
import ( import (
"os" "os"
"github.com/rs/zerolog/log"
) )
type ScreenDump struct { type ScreenDump struct {
@ -15,6 +13,5 @@ var _ Nuker = &ScreenDump{}
// Delete a ScreenDump. // Delete a ScreenDump.
func (d *ScreenDump) Delete(path string, cascade, force bool) error { func (d *ScreenDump) Delete(path string, cascade, force bool) error {
log.Debug().Msgf("ScreenDump DELETE %q", path)
return os.Remove(path) return os.Remove(path)
} }

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/client"
"github.com/rs/zerolog/log"
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
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"
@ -62,7 +61,6 @@ func (s *StatefulSet) Restart(path string) error {
// Logs tail logs for all pods represented by this StatefulSet. // Logs tail logs for all pods represented by this StatefulSet.
func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (s *StatefulSet) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
log.Debug().Msgf("Tailing StatefulSet %q", opts.Path)
o, err := s.Get(string(s.gvr), opts.Path, labels.Everything()) o, err := s.Get(string(s.gvr), opts.Path, labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -5,7 +5,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"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"
@ -21,7 +20,6 @@ var _ Loggable = &Service{}
// Logs tail logs for all pods represented by this Service. // Logs tail logs for all pods represented by this Service.
func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error { func (s *Service) TailLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
log.Debug().Msgf("Tailing Service %q", opts.Path)
o, err := s.Get(string(s.gvr), opts.Path, labels.Everything()) o, err := s.Get(string(s.gvr), opts.Path, labels.Everything())
if err != nil { if err != nil {
return err return err

View File

@ -7,6 +7,7 @@ import (
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
@ -18,13 +19,13 @@ type Alias struct {
// List returns a collection of screen dumps. // List returns a collection of screen dumps.
func (b *Alias) List(ctx context.Context) ([]runtime.Object, error) { func (b *Alias) List(ctx context.Context) ([]runtime.Object, error) {
aa, ok := ctx.Value(internal.KeyAliases).(config.Alias) a, ok := ctx.Value(internal.KeyAliases).(*dao.Alias)
if !ok { if !ok {
return nil, errors.New("no aliases found in context") return nil, errors.New("no aliases found in context")
} }
m := make(config.ShortNames, len(aa)) m := make(config.ShortNames, len(a.Alias))
for alias, gvr := range aa { for alias, gvr := range a.Alias {
if _, ok := m[gvr]; ok { if _, ok := m[gvr]; ok {
m[gvr] = append(m[gvr], alias) m[gvr] = append(m[gvr], alias)
} else { } else {
@ -40,13 +41,3 @@ func (b *Alias) List(ctx context.Context) ([]runtime.Object, error) {
return oo, nil return oo, nil
} }
// Hydrate returns a pod as container rows.
func (b *Alias) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
for i, o := range oo {
if err := re.Render(o, render.NonResource, &rr[i]); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,84 @@
package model_test
import (
"context"
"testing"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/watch"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
)
func TestAliasList(t *testing.T) {
a := model.Alias{}
a.Init(render.ClusterScope, "aliases", makeFactory())
ctx := context.WithValue(context.Background(), internal.KeyAliases, makeAliases())
oo, err := a.List(ctx)
assert.Nil(t, err)
assert.Equal(t, 2, len(oo))
assert.Equal(t, 2, len(oo[0].(render.AliasRes).Aliases))
}
func TestAliasHydrate(t *testing.T) {
a := model.Alias{}
a.Init(render.ClusterScope, "aliases", makeFactory())
ctx := context.WithValue(context.Background(), internal.KeyAliases, makeAliases())
oo, err := a.List(ctx)
assert.Nil(t, err)
rr := make(render.Rows, len(oo))
assert.Nil(t, a.Hydrate(oo, rr, render.Alias{}))
assert.Equal(t, 2, len(rr))
}
// ----------------------------------------------------------------------------
// Helpers...
func makeAliases() *dao.Alias {
return &dao.Alias{
Aliases: config.Aliases{
Alias: config.Alias{
"fred": "v1/fred",
"f": "v1/fred",
"blee": "v1/blee",
"b": "v1/blee",
},
},
}
}
type testFactory struct{}
var _ model.Factory = testFactory{}
func (f testFactory) Client() client.Connection {
return nil
}
func (f testFactory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) {
return nil, nil
}
func (f testFactory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) {
return nil, nil
}
func (f testFactory) ForResource(ns, gvr string) informers.GenericInformer {
return nil
}
func (f testFactory) WaitForCacheSync() {}
func (f testFactory) Forwarders() watch.Forwarders {
return nil
}
func makeFactory() model.Factory {
return testFactory{}
}

View File

@ -35,13 +35,3 @@ func (b *Benchmark) List(ctx context.Context) ([]runtime.Object, error) {
return oo, nil return oo, nil
} }
// Hydrate returns a pod as container rows.
func (b *Benchmark) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
for i, o := range oo {
if err := re.Render(o, render.NonResource, &rr[i]); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,49 @@
package model_test
import (
"context"
"testing"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render"
"github.com/stretchr/testify/assert"
)
func TestBenchmarkList(t *testing.T) {
a := model.Benchmark{}
a.Init(render.ClusterScope, "benchmarks", makeFactory())
ctx := context.WithValue(context.Background(), internal.KeyDir, "test_assets/bench")
oo, err := a.List(ctx)
assert.Nil(t, err)
assert.Equal(t, 1, len(oo))
assert.Equal(t, "test_assets/bench/default_fred_1577308050814961000.txt", oo[0].(render.BenchInfo).Path)
}
func TestBenchmarkHydrate(t *testing.T) {
a := model.Benchmark{}
a.Init(render.ClusterScope, "benchmarks", makeFactory())
ctx := context.WithValue(context.Background(), internal.KeyDir, "test_assets/bench")
oo, err := a.List(ctx)
assert.Nil(t, err)
rr := make(render.Rows, len(oo))
assert.Nil(t, a.Hydrate(oo, rr, render.Benchmark{}))
assert.Equal(t, 1, len(rr))
assert.Equal(t, "test_assets/bench/default_fred_1577308050814961000.txt", rr[0].ID)
assert.Equal(t, render.Fields{
"default",
"fred",
"fail",
"816.6403",
"0.0122",
"0",
"0",
"default_fred_1577308050814961000.txt",
},
rr[0].Fields[:len(rr[0].Fields)-1],
)
}

View File

@ -9,16 +9,12 @@ import (
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
) )
var _ render.ContainerWithMetrics = &ContainerWithMetrics{}
// Container represents a container model. // Container represents a container model.
type Container struct { type Container struct {
Resource Resource
@ -46,77 +42,61 @@ func (c *Container) List(ctx context.Context) ([]runtime.Object, error) {
return nil, err return nil, err
} }
c.pod = &po c.pod = &po
res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers)) res := make([]runtime.Object, 0, len(po.Spec.InitContainers)+len(po.Spec.Containers))
mx := client.NewMetricsServer(c.factory.Client())
var pmx *mv1beta1.PodMetrics
if c.factory.Client() != nil {
var err error
pmx, err = mx.FetchPodMetrics(c.namespace, c.pod.Name)
if err != nil {
log.Warn().Err(err).Msgf("No metrics found for pod %q:%q", c.namespace, c.pod.Name)
}
}
for _, co := range po.Spec.InitContainers { for _, co := range po.Spec.InitContainers {
res = append(res, ContainerRes{co}) res = append(res, makeContainerRes(co, po, pmx, true))
} }
for _, co := range po.Spec.Containers { for _, co := range po.Spec.Containers {
res = append(res, ContainerRes{co}) res = append(res, makeContainerRes(co, po, pmx, false))
} }
return res, nil return res, nil
} }
// Hydrate returns a pod as container rows. // ----------------------------------------------------------------------------
func (c *Container) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { // Helpers...
mx := client.NewMetricsServer(c.factory.Client().(client.Connection))
mmx, err := mx.FetchPodMetrics(c.namespace, c.pod.Name) func makeContainerRes(co v1.Container, po v1.Pod, pmx *mv1beta1.PodMetrics, isInit bool) render.ContainerRes {
cmx, err := containerMetrics(co.Name, pmx)
if err != nil { if err != nil {
log.Warn().Err(err).Msgf("No metrics found for pod %q:%q", c.namespace, c.pod.Name) log.Warn().Err(err).Msgf("Container metrics for %s", co.Name)
} }
var index int return render.ContainerRes{
for _, o := range oo { Container: co,
co, ok := o.(ContainerRes) Status: getContainerStatus(co.Name, po.Status),
if !ok { Metrics: cmx,
return fmt.Errorf("expecting containerres but got `%T", o) IsInit: isInit,
} Age: po.ObjectMeta.CreationTimestamp,
row, err := renderCoRow(co.Container.Name, coMetricsFor(co.Container, c.pod, mmx, true), re)
if err != nil {
return err
}
rr[index] = row
index++
}
return nil
}
func renderCoRow(n string, pmx *ContainerWithMetrics, re Renderer) (render.Row, error) {
var row render.Row
if err := re.Render(pmx, n, &row); err != nil {
return render.Row{}, err
}
return row, nil
}
func coMetricsFor(co v1.Container, po *v1.Pod, mmx *mv1beta1.PodMetrics, isInit bool) *ContainerWithMetrics {
return &ContainerWithMetrics{
container: &co,
status: getContainerStatus(co.Name, po.Status),
metrics: containerMetrics(co.Name, mmx),
isInit: isInit,
age: po.ObjectMeta.CreationTimestamp,
} }
} }
func containerMetrics(n string, mx runtime.Object) *mv1beta1.ContainerMetrics { func containerMetrics(n string, mx runtime.Object) (*mv1beta1.ContainerMetrics, error) {
pmx, ok := mx.(*mv1beta1.PodMetrics) pmx, ok := mx.(*mv1beta1.PodMetrics)
if !ok { if !ok {
log.Error().Err(fmt.Errorf("expecting podmetrics but got `%T", mx)) return nil, fmt.Errorf("expecting podmetrics but got `%T", mx)
return nil }
if pmx == nil {
return nil, fmt.Errorf("no metrics for container %s", n)
} }
for _, m := range pmx.Containers { for _, m := range pmx.Containers {
if m.Name == n { if m.Name == n {
return &m return &m, nil
} }
} }
return nil return nil, nil
} }
// ----------------------------------------------------------------------------
func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus { func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus {
for _, c := range status.ContainerStatuses { for _, c := range status.ContainerStatuses {
if c.Name == co { if c.Name == co {
@ -132,50 +112,3 @@ func getContainerStatus(co string, status v1.PodStatus) *v1.ContainerStatus {
return nil return nil
} }
// ContainerWithMetrics represents a container and its metrics.
type ContainerWithMetrics struct {
container *v1.Container
status *v1.ContainerStatus
metrics *mv1beta1.ContainerMetrics
isInit bool
age metav1.Time
}
func (c *ContainerWithMetrics) IsInit() bool {
return c.isInit
}
func (c *ContainerWithMetrics) Container() *v1.Container {
return c.container
}
func (c *ContainerWithMetrics) ContainerStatus() *v1.ContainerStatus {
return c.status
}
// Metrics returns the metrics associated with the pod.
func (c *ContainerWithMetrics) Metrics() *mv1beta1.ContainerMetrics {
return c.metrics
}
func (c *ContainerWithMetrics) Age() metav1.Time {
return c.age
}
// ----------------------------------------------------------------------------
// ContainerRes represents a container K8s resource.
type ContainerRes struct {
v1.Container
}
// GetObjectKind returns a schema object.
func (c ContainerRes) GetObjectKind() schema.ObjectKind {
return nil
}
// DeepCopyObject returns a container copy.
func (c ContainerRes) DeepCopyObject() runtime.Object {
return c
}

View File

@ -0,0 +1,113 @@
package model_test
import (
"context"
"testing"
"github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/client"
"github.com/derailed/k9s/internal/model"
"github.com/derailed/k9s/internal/render"
"github.com/derailed/k9s/internal/watch"
"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/informers"
)
func TestContainerList(t *testing.T) {
c := model.Container{}
c.Init(render.ClusterScope, "containers", makePodFactory())
ctx := context.WithValue(context.Background(), internal.KeyPath, "fred/p1")
oo, err := c.List(ctx)
assert.Nil(t, err)
assert.Equal(t, 1, len(oo))
}
func TestContainerHydrate(t *testing.T) {
c := model.Container{}
c.Init(render.ClusterScope, "containers", makePodFactory())
ctx := context.WithValue(context.Background(), internal.KeyPath, "fred/p1")
oo, err := c.List(ctx)
assert.Nil(t, err)
rr := make(render.Rows, len(oo))
assert.Nil(t, c.Hydrate(oo, rr, render.Container{}))
assert.Equal(t, 1, len(rr))
assert.Equal(t, "fred", rr[0].ID)
assert.Equal(t, render.Fields{"fred", "blee", "false", "Running", "false", "0", "off:off", "n/a", "n/a", "n/a", "n/a", ""}, rr[0].Fields[0:len(rr[0].Fields)-1])
}
// ----------------------------------------------------------------------------
// Helpers...
type podFactory struct{}
var _ model.Factory = testFactory{}
func (f podFactory) Client() client.Connection {
return nil
}
func (f podFactory) Get(gvr, path string, sel labels.Selector) (runtime.Object, error) {
var m map[string]interface{}
if err := yaml.Unmarshal([]byte(poYaml()), &m); err != nil {
return nil, err
}
return &unstructured.Unstructured{Object: m}, nil
}
func (f podFactory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) {
return nil, nil
}
func (f podFactory) ForResource(ns, gvr string) informers.GenericInformer { return nil }
func (f podFactory) WaitForCacheSync() {}
func (f podFactory) Forwarders() watch.Forwarders { return nil }
func makePodFactory() model.Factory {
return podFactory{}
}
func poYaml() string {
return `apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2018-12-14T17:36:43Z"
labels:
blee: duh
name: fred
namespace: blee
spec:
containers:
- env:
- name: fred
value: "1"
valueFrom:
configMapKeyRef:
key: blee
image: blee
name: fred
resources: {}
priority: 1
priorityClassName: bozo
volumes:
- hostPath:
path: /blee
type: Directory
name: fred
status:
containerStatuses:
- image: ""
imageID: ""
lastState: {}
name: fred
ready: false
restartCount: 0
state:
running:
startedAt: null
phase: Running
`
}

View File

@ -2,7 +2,6 @@ package model
import ( import (
"context" "context"
"fmt"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
@ -27,23 +26,3 @@ func (c *Context) List(_ context.Context) ([]runtime.Object, error) {
return cc, nil return cc, nil
} }
// Hydrate returns nodes as rows.
func (n *Context) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
var index int
for _, o := range oo {
ctx, ok := o.(*render.NamedContext)
if !ok {
return fmt.Errorf("expecting named context but got %T", o)
}
var row render.Row
if err := re.Render(ctx, "", &row); err != nil {
return err
}
rr[index] = row
index++
}
return nil
}

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/render"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -62,15 +61,8 @@ func (c *Job) List(ctx context.Context) ([]runtime.Object, error) {
return jj, nil return jj, nil
} }
// Hydrate returns a pod as container rows. // ----------------------------------------------------------------------------
func (c *Job) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { // Helpers...
for i, o := range oo {
if err := re.Render(o, c.namespace, &rr[i]); err != nil {
return err
}
}
return nil
}
func isControlledBy(cuid, id string) bool { func isControlledBy(cuid, id string) bool {
tokens := strings.Split(cuid, "-") tokens := strings.Split(cuid, "-")

View File

@ -44,22 +44,6 @@ func (c *PortForward) List(ctx context.Context) ([]runtime.Object, error) {
return oo, nil return oo, nil
} }
// Hydrate returns a pod as container rows.
func (c *PortForward) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
for i, o := range oo {
res, ok := o.(render.ForwardRes)
if !ok {
return fmt.Errorf("expecting a forwardres but got %T", o)
}
if err := re.Render(res, render.NonResource, &rr[i]); err != nil {
return err
}
}
return nil
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers... // Helpers...

View File

@ -1,4 +1,4 @@
package dao package model
import ( import (
"context" "context"
@ -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/model"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
) )
@ -27,16 +26,16 @@ func Reconcile(ctx context.Context, table render.TableData, gvr client.GVR) (ren
if !ok { if !ok {
return table, fmt.Errorf("no factory found for %s", gvr) return table, fmt.Errorf("no factory found for %s", gvr)
} }
m, ok := model.Registry[string(gvr)] m, ok := Registry[string(gvr)]
if !ok { if !ok {
log.Warn().Msgf("Resource %s not found in registry. Going generic!", gvr) log.Warn().Msgf("Resource %s not found in registry. Going generic!", gvr)
m = model.ResourceMeta{ m = ResourceMeta{
Model: &model.Generic{}, Model: &Generic{},
Renderer: &render.Generic{}, Renderer: &render.Generic{},
} }
} }
if m.Model == nil { if m.Model == nil {
m.Model = &model.Resource{} m.Model = &Resource{}
} }
m.Model.Init(table.Namespace, string(gvr), factory) m.Model.Init(table.Namespace, string(gvr), factory)

View File

@ -41,14 +41,10 @@ func (r *Resource) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) err
log.Debug().Msgf("HYDRATE elapsed: %v", time.Since(t)) log.Debug().Msgf("HYDRATE elapsed: %v", time.Since(t))
}(time.Now()) }(time.Now())
var index int for i, o := range oo {
for _, o := range oo { if err := re.Render(o, r.namespace, &rr[i]); err != nil {
var row render.Row
if err := re.Render(o, r.namespace, &row); err != nil {
return err return err
} }
rr[index] = row
index++
} }
return nil return nil

View File

@ -34,13 +34,3 @@ func (c *ScreenDump) List(ctx context.Context) ([]runtime.Object, error) {
return oo, nil return oo, nil
} }
// Hydrate returns a pod as container rows.
func (c *ScreenDump) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error {
for i, o := range oo {
if err := re.Render(o, render.NonResource, &rr[i]); err != nil {
return err
}
}
return nil
}

View File

@ -3,7 +3,6 @@ package model
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/derailed/k9s/internal" "github.com/derailed/k9s/internal"
"github.com/derailed/k9s/internal/render" "github.com/derailed/k9s/internal/render"
@ -60,21 +59,8 @@ func (s *Subject) List(ctx context.Context) ([]runtime.Object, error) {
return oo, nil return oo, nil
} }
// Hydrate returns a pod as container rows. // ----------------------------------------------------------------------------
func (s *Subject) Hydrate(oo []runtime.Object, rr render.Rows, re Renderer) error { // Helpers...
for i, o := range oo {
res, ok := o.(render.SubjectRef)
if !ok {
return fmt.Errorf("expecting unstructured but got %T", o)
}
if err := re.Render(res, render.AllNamespaces, &rr[i]); err != nil {
return err
}
}
return nil
}
func inSubjectRes(oo []runtime.Object, match string) bool { func inSubjectRes(oo []runtime.Object, match string) bool {
for _, o := range oo { for _, o := range oo {

View File

@ -0,0 +1,24 @@
Summary:
Total: 816.6403 secs
Slowest: 0.0000 secs
Fastest: 0.0000 secs
Average: NaN secs
Requests/sec: 0.0122
Response time histogram:
Latency distribution:
Details (average, fastest, slowest):
DNS+dialup: NaN secs, 0.0000 secs, 0.0000 secs
DNS-lookup: NaN secs, 0.0000 secs, 0.0000 secs
req write: NaN secs, 0.0000 secs, 0.0000 secs
resp wait: NaN secs, 0.0000 secs, 0.0000 secs
resp read: NaN secs, 0.0000 secs, 0.0000 secs
Status code distribution:
Error distribution:
[10] Get http://192.168.64.126:30805/: dial tcp 192.168.64.126:30805: connect: operation timed out

View File

@ -31,16 +31,15 @@ func (Alias) Header(ns string) HeaderRow {
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
// BOZO!! Pass in a row with pre-alloc fields?? // BOZO!! Pass in a row with pre-alloc fields??
func (Alias) Render(o interface{}, gvr string, r *Row) error { func (Alias) Render(o interface{}, ns string, r *Row) error {
a, ok := o.(AliasRes) a, ok := o.(AliasRes)
if !ok { if !ok {
return fmt.Errorf("expected AliasRes, but got %T", o) return fmt.Errorf("expected AliasRes, but got %T", o)
} }
_ = a
r.ID = gvr r.ID = a.GVR
gvr1 := client.GVR(a.GVR) gvr := client.GVR(a.GVR)
grp, res := gvr1.ToRAndG() res, grp := gvr.ToRAndG()
r.Fields = append(r.Fields, r.Fields = append(r.Fields,
res, res,
strings.Join(a.Aliases, ","), strings.Join(a.Aliases, ","),
@ -50,6 +49,7 @@ func (Alias) Render(o interface{}, gvr string, r *Row) error {
return nil return nil
} }
// ----------------------------------------------------------------------------
// Helpers... // Helpers...
// AliasRes represents an alias resource. // AliasRes represents an alias resource.

View File

@ -9,6 +9,8 @@ import (
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
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/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
) )
@ -81,35 +83,33 @@ func (Container) Header(ns string) HeaderRow {
// Render renders a K8s resource to screen. // Render renders a K8s resource to screen.
func (c Container) Render(o interface{}, name string, r *Row) error { func (c Container) Render(o interface{}, name string, r *Row) error {
oo, ok := o.(ContainerWithMetrics) co, ok := o.(ContainerRes)
if !ok { if !ok {
return fmt.Errorf("Expected ContainerWithMetrics, but got %T", o) return fmt.Errorf("Expected ContainerRes, but got %T", o)
} }
co, cs := oo.Container(), oo.ContainerStatus() cur, perc := gatherMetrics(co)
cur, perc := gatherMetrics(co, oo.Metrics())
ready, state, restarts := "false", MissingValue, "0" ready, state, restarts := "false", MissingValue, "0"
if cs != nil { if co.Status != nil {
ready, state, restarts = boolToStr(cs.Ready), toState(cs.State), strconv.Itoa(int(cs.RestartCount)) ready, state, restarts = boolToStr(co.Status.Ready), toState(co.Status.State), strconv.Itoa(int(co.Status.RestartCount))
} }
r.ID = co.Name r.ID = co.Container.Name
r.Fields = make(Fields, 0, len(c.Header(AllNamespaces))) r.Fields = make(Fields, 0, len(c.Header(AllNamespaces)))
r.Fields = append(r.Fields, r.Fields = append(r.Fields,
co.Name, co.Container.Name,
co.Image, co.Container.Image,
ready, ready,
state, state,
boolToStr(oo.IsInit()), boolToStr(co.IsInit),
restarts, restarts,
probe(co.LivenessProbe)+":"+probe(co.ReadinessProbe), probe(co.Container.LivenessProbe)+":"+probe(co.Container.ReadinessProbe),
cur.cpu, cur.cpu,
cur.mem, cur.mem,
perc.cpu, perc.cpu,
perc.mem, perc.mem,
toStrPorts(co.Ports), toStrPorts(co.Container.Ports),
toAge(oo.Age()), toAge(co.Age),
) )
return nil return nil
@ -118,20 +118,20 @@ func (c Container) Render(o interface{}, name string, r *Row) error {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers... // Helpers...
func gatherMetrics(co *v1.Container, mx *mv1beta1.ContainerMetrics) (c, p metric) { func gatherMetrics(co ContainerRes) (c, p metric) {
c, p = noMetric(), noMetric() c, p = noMetric(), noMetric()
if mx == nil { if co.Metrics == nil {
return return
} }
cpu := mx.Usage.Cpu().MilliValue() cpu := co.Metrics.Usage.Cpu().MilliValue()
mem := ToMB(mx.Usage.Memory().Value()) mem := ToMB(co.Metrics.Usage.Memory().Value())
c = metric{ c = metric{
cpu: ToMillicore(cpu), cpu: ToMillicore(cpu),
mem: ToMi(mem), mem: ToMi(mem),
} }
rcpu, rmem := containerResources(*co) rcpu, rmem := containerResources(co.Container)
if rcpu != nil { if rcpu != nil {
p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue()))) p.cpu = AsPerc(toPerc(float64(cpu), float64(rcpu.MilliValue())))
} }
@ -183,3 +183,22 @@ func probe(p *v1.Probe) string {
} }
return "on" return "on"
} }
// ContainerRes represents a container and its metrics.
type ContainerRes struct {
Container v1.Container
Status *v1.ContainerStatus
Metrics *mv1beta1.ContainerMetrics
IsInit bool
Age metav1.Time
}
// GetObjectKind returns a schema object.
func (c ContainerRes) GetObjectKind() schema.ObjectKind {
return nil
}
// DeepCopyObject returns a container copy.
func (c ContainerRes) DeepCopyObject() runtime.Object {
return c
}

View File

@ -16,9 +16,15 @@ import (
func TestContainer(t *testing.T) { func TestContainer(t *testing.T) {
var c render.Container var c render.Container
var cm coMX cres := render.ContainerRes{
Container: makeContainer(),
Status: makeContainerStatus(),
Metrics: makeContainerMetrics(),
IsInit: false,
Age: makeAge(),
}
var r render.Row var r render.Row
assert.Nil(t, c.Render(cm, "blee", &r)) assert.Nil(t, c.Render(cres, "blee", &r))
assert.Equal(t, "fred", r.ID) assert.Equal(t, "fred", r.ID)
assert.Equal(t, render.Fields{ assert.Equal(t, render.Fields{
"fred", "fred",
@ -47,19 +53,7 @@ func toQty(s string) resource.Quantity {
} }
type coMX struct{} func makeContainerMetrics() *mv1beta1.ContainerMetrics {
var _ render.ContainerWithMetrics = coMX{}
func (c coMX) Container() *v1.Container {
return makeContainer()
}
func (c coMX) ContainerStatus() *v1.ContainerStatus {
return makeContainerStatus()
}
func (c coMX) Metrics() *mv1beta1.ContainerMetrics {
return &mv1beta1.ContainerMetrics{ return &mv1beta1.ContainerMetrics{
Name: "fred", Name: "fred",
Usage: v1.ResourceList{ Usage: v1.ResourceList{
@ -69,16 +63,12 @@ func (c coMX) Metrics() *mv1beta1.ContainerMetrics {
} }
} }
func (c coMX) Age() metav1.Time { func makeAge() metav1.Time {
return metav1.Time{Time: testTime()} return metav1.Time{Time: testTime()}
} }
func (c coMX) IsInit() bool { func makeContainer() v1.Container {
return false return v1.Container{
}
func makeContainer() *v1.Container {
return &v1.Container{
Name: "fred", Name: "fred",
Image: "img", Image: "img",
Resources: v1.ResourceRequirements{ Resources: v1.ResourceRequirements{

View File

@ -34,7 +34,7 @@ func NewAlias(gvr client.GVR) ResourceViewer {
} }
func (a *Alias) aliasContext(ctx context.Context) context.Context { func (a *Alias) aliasContext(ctx context.Context) context.Context {
return context.WithValue(ctx, internal.KeyAliases, aliases.Alias) return context.WithValue(ctx, internal.KeyAliases, a.App().command.alias)
} }
func (a *Alias) bindKeys(aa ui.KeyActions) { func (a *Alias) bindKeys(aa ui.KeyActions) {

View File

@ -43,7 +43,6 @@ func NewApp(cfg *config.Config) *App {
} }
a.Config = cfg a.Config = cfg
a.InitBench(cfg.K9s.CurrentCluster) a.InitBench(cfg.K9s.CurrentCluster)
a.command = newCommand(&a)
a.Views()["indicator"] = ui.NewIndicatorView(a.App, a.Styles) a.Views()["indicator"] = ui.NewIndicatorView(a.App, a.Styles)
a.Views()["clusterInfo"] = newClusterInfoView(&a, client.NewMetricsServer(cfg.GetConnection())) a.Views()["clusterInfo"] = newClusterInfoView(&a, client.NewMetricsServer(cfg.GetConnection()))
@ -57,7 +56,6 @@ func (a *App) ActiveView() model.Component {
} }
func (a *App) PrevCmd(evt *tcell.EventKey) *tcell.EventKey { func (a *App) PrevCmd(evt *tcell.EventKey) *tcell.EventKey {
log.Debug().Msgf("PREVIOUS!!!")
a.Content.DumpStack() a.Content.DumpStack()
a.Content.DumpPages() a.Content.DumpPages()
if !a.Content.IsLast() { if !a.Content.IsLast() {
@ -92,6 +90,11 @@ func (a *App) Init(version string, rate int) error {
a.factory = watch.NewFactory(a.Conn()) a.factory = watch.NewFactory(a.Conn())
a.initFactory(ns) a.initFactory(ns)
a.command = newCommand(a)
if err := a.command.Init(); err != nil {
return err
}
a.clusterInfo().init(version) a.clusterInfo().init(version)
if a.Config.K9s.GetHeadless() { if a.Config.K9s.GetHeadless() {
a.refreshIndicator() a.refreshIndicator()
@ -107,10 +110,6 @@ func (a *App) Init(version string, rate int) error {
a.Main.AddPage("splash", ui.NewSplash(a.Styles, version), true, true) a.Main.AddPage("splash", ui.NewSplash(a.Styles, version), true, true)
a.toggleHeader(!a.Config.K9s.GetHeadless()) a.toggleHeader(!a.Config.K9s.GetHeadless())
if err := a.command.Init(); err != nil {
panic(err)
}
return nil return nil
} }
@ -266,6 +265,9 @@ func (a *App) switchCtx(name string, loadPods bool) error {
} }
a.initFactory(ns) a.initFactory(ns)
if err := a.command.Reset(); err != nil {
return err
}
a.Config.Reset() a.Config.Reset()
if err := a.Config.Save(); err != nil { if err := a.Config.Save(); err != nil {
log.Error().Err(err).Msg("Config save failed!") log.Error().Err(err).Msg("Config save failed!")

View File

@ -14,6 +14,7 @@ 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/dao" "github.com/derailed/k9s/internal/dao"
"github.com/derailed/k9s/internal/model"
"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/k9s/internal/ui/dialog" "github.com/derailed/k9s/internal/ui/dialog"
@ -392,7 +393,7 @@ func (b *Browser) refresh() {
if path, ok := ctx.Value(internal.KeyPath).(string); ok && path != "" { if path, ok := ctx.Value(internal.KeyPath).(string); ok && path != "" {
b.Path = path b.Path = path
} }
data, err := dao.Reconcile(ctx, b.Table.Data, b.gvr) data, err := model.Reconcile(ctx, b.Table.Data, b.gvr)
b.app.QueueUpdateDraw(func() { b.app.QueueUpdateDraw(func() {
if err != nil { if err != nil {
b.app.Flash().Err(err) b.app.Flash().Err(err)

View File

@ -15,17 +15,20 @@ var customViewers MetaViewers
type command struct { type command struct {
app *App app *App
alias *dao.Alias
} }
func newCommand(app *App) *command { func newCommand(app *App) *command {
return &command{app: app} return &command{
app: app,
}
} }
func (c *command) Init() error { func (c *command) Init() error {
if err := dao.Load(c.app.factory); err != nil { log.Debug().Msgf("COMMAND INIT")
return err c.alias = dao.NewAlias(c.app.factory)
} if _, err := c.alias.Ensure(); err != nil {
if err := loadAliases(); err != nil {
return err return err
} }
customViewers = loadCustomViewers() customViewers = loadCustomViewers()
@ -33,6 +36,16 @@ func (c *command) Init() error {
return nil return nil
} }
// Reset resets command and reload aliases.
func (c *command) Reset() error {
c.alias.Clear()
if _, err := c.alias.Ensure(); err != nil {
return err
}
return nil
}
func (c *command) defaultCmd() error { func (c *command) defaultCmd() error {
return c.run(c.app.Config.ActiveView()) return c.run(c.app.Config.ActiveView())
} }
@ -68,7 +81,7 @@ func (c *command) isK9sCmd(cmd string) bool {
} }
func (c *command) viewMetaFor(cmd string) (string, *MetaViewer, error) { func (c *command) viewMetaFor(cmd string) (string, *MetaViewer, error) {
gvr, ok := aliases.Get(cmd) gvr, ok := c.alias.Get(cmd)
if !ok { if !ok {
return "", nil, fmt.Errorf("Huh? `%s` command not found", cmd) return "", nil, fmt.Errorf("Huh? `%s` command not found", cmd)
} }

View File

@ -11,13 +11,12 @@ import (
) )
const ( const (
group = "Group" group = "Group"
user = "User" user = "User"
sa = "ServiceAccount" sa = "ServiceAccount"
allVerbs = "*"
) )
// Policy presents a RBAC rules viewer. // Policy presents a RBAC rules viewer based on what a given user/group or sa can do.
type Policy struct { type Policy struct {
ResourceViewer ResourceViewer

View File

@ -1,47 +1,7 @@
package view package view
import (
"strings"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/dao"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
)
var aliases = config.NewAliases()
func ToResource(o *unstructured.Unstructured, obj interface{}) error {
return runtime.DefaultUnstructuredConverter.FromUnstructured(o.Object, &obj)
}
func loadAliases() error {
if err := aliases.Load(); err != nil {
return err
}
for _, gvr := range dao.AllGVRs() {
meta, err := dao.MetaFor(gvr)
if err != nil {
return err
}
if _, ok := aliases.Alias[meta.Kind]; ok {
continue
}
aliases.Define(string(gvr), strings.ToLower(meta.Kind), meta.Name)
if meta.SingularName != "" {
aliases.Define(string(gvr), meta.SingularName)
}
if meta.ShortNames != nil {
aliases.Define(string(gvr), meta.ShortNames...)
}
}
return nil
}
func loadCustomViewers() MetaViewers { func loadCustomViewers() MetaViewers {
m := make(MetaViewers, 30) m := make(MetaViewers, 30)
coreRes(m) coreRes(m)
miscRes(m) miscRes(m)
appsRes(m) appsRes(m)
@ -88,12 +48,6 @@ func miscRes(vv MetaViewers) {
vv["aliases"] = MetaViewer{ vv["aliases"] = MetaViewer{
viewerFn: NewAlias, viewerFn: NewAlias,
} }
vv["users"] = MetaViewer{
viewerFn: NewUser,
}
vv["groups"] = MetaViewer{
viewerFn: NewGroup,
}
} }
func appsRes(vv MetaViewers) { func appsRes(vv MetaViewers) {
@ -118,6 +72,12 @@ func rbacRes(vv MetaViewers) {
vv["rbac"] = MetaViewer{ vv["rbac"] = MetaViewer{
enterFn: showRules, enterFn: showRules,
} }
vv["users"] = MetaViewer{
viewerFn: NewUser,
}
vv["groups"] = MetaViewer{
viewerFn: NewGroup,
}
vv["rbac.authorization.k8s.io/v1/clusterroles"] = MetaViewer{ vv["rbac.authorization.k8s.io/v1/clusterroles"] = MetaViewer{
enterFn: showRules, enterFn: showRules,
} }

View File

@ -41,30 +41,6 @@ func NewFactory(client client.Connection) *Factory {
} }
} }
func (f *Factory) Dump() {
log.Debug().Msgf("----------- FACTORIES -------------")
for ns := range f.factories {
log.Debug().Msgf(" Factory for NS %q", ns)
}
log.Debug().Msgf("-----------------------------------")
}
func (f *Factory) Debug(gvr string) {
log.Debug().Msgf("----------- DEBUG FACTORY (%s) -------------", gvr)
inf := f.factories[allNamespaces].ForResource(toGVR(gvr))
for i, k := range inf.Informer().GetStore().ListKeys() {
log.Debug().Msgf("%d -- %s", i, k)
}
}
func (f *Factory) Show(ns, gvr string) {
log.Debug().Msgf("----------- SHOW FACTORIES %q -------------", ns)
inf := f.ForResource(ns, gvr)
for _, k := range inf.Informer().GetStore().ListKeys() {
log.Debug().Msgf(" Key: %s", k)
}
}
func (f *Factory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) { func (f *Factory) List(gvr, ns string, sel labels.Selector) ([]runtime.Object, error) {
auth, err := f.Client().CanI(ns, gvr, []string{"list"}) auth, err := f.Client().CanI(ns, gvr, []string{"list"})
if err != nil { if err != nil {
@ -237,6 +213,30 @@ func (f *Factory) Client() client.Connection {
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Helpers... // Helpers...
func (f *Factory) Dump() {
log.Debug().Msgf("----------- FACTORIES -------------")
for ns := range f.factories {
log.Debug().Msgf(" Factory for NS %q", ns)
}
log.Debug().Msgf("-----------------------------------")
}
func (f *Factory) Debug(gvr string) {
log.Debug().Msgf("----------- DEBUG FACTORY (%s) -------------", gvr)
inf := f.factories[allNamespaces].ForResource(toGVR(gvr))
for i, k := range inf.Informer().GetStore().ListKeys() {
log.Debug().Msgf("%d -- %s", i, k)
}
}
func (f *Factory) Show(ns, gvr string) {
log.Debug().Msgf("----------- SHOW FACTORIES %q -------------", ns)
inf := f.ForResource(ns, gvr)
for _, k := range inf.Informer().GetStore().ListKeys() {
log.Debug().Msgf(" Key: %s", k)
}
}
func namespaced(n string) (string, string) { func namespaced(n string) (string, string) {
ns, po := path.Split(n) ns, po := path.Split(n)