fix #466
parent
92214ed3ee
commit
6b5da1091e
|
|
@ -121,6 +121,7 @@ K9s uses aliases to navigate most K8s resources.
|
||||||
| `d`,`v`, `e`, `l`,... | Key mapping to describe, view, edit, view logs,... | `d` (describes a resource) |
|
| `d`,`v`, `e`, `l`,... | Key mapping to describe, view, edit, view logs,... | `d` (describes a resource) |
|
||||||
| `:`ctx`<ENTER>` | To view and switch to another Kubernetes context | `:`+`ctx`+`<ENTER>` |
|
| `:`ctx`<ENTER>` | To view and switch to another Kubernetes context | `:`+`ctx`+`<ENTER>` |
|
||||||
| `:`ns`<ENTER>` | To view and switch to another Kubernetes namespace | `:`+`ns`+`<ENTER>` |
|
| `:`ns`<ENTER>` | To view and switch to another Kubernetes namespace | `:`+`ns`+`<ENTER>` |
|
||||||
|
| `:screendump`, `:sd` | To view all saved resources | |
|
||||||
| `Ctrl-d` | To delete a resource (TAB and ENTER to confirm) | |
|
| `Ctrl-d` | To delete a resource (TAB and ENTER to confirm) | |
|
||||||
| `Ctrl-k` | To delete a resource (no confirmation dialog) | |
|
| `Ctrl-k` | To delete a resource (no confirmation dialog) | |
|
||||||
| `:q`, `Ctrl-c` | To bail out of K9s | |
|
| `:q`, `Ctrl-c` | To bail out of K9s | |
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
|
||||||
|
|
||||||
# Release v0.10.10
|
# Release v0.11.1
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|
@ -10,15 +10,15 @@ Also if you dig this tool, please make some noise on social! [@kitesurfer](https
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Change Logs
|
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_helm.png" align="center" width="300" height="auto"/>
|
||||||
|
|
||||||
Maintenance release!
|
Maintenance Release!
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Resolved Bugs/Features
|
## Resolved Bugs/Features
|
||||||
|
|
||||||
* [Issue #463](https://github.com/derailed/k9s/issues/463)
|
* [Issue #466](https://github.com/derailed/k9s/issues/466)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -88,6 +88,14 @@ func (g GVR) AsGVR() schema.GroupVersionResource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AsGR returns a a full schema representation.
|
||||||
|
func (g GVR) AsGR() *schema.GroupResource {
|
||||||
|
return &schema.GroupResource{
|
||||||
|
Group: g.ToG(),
|
||||||
|
Resource: g.ToR(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ToV returns the resource version.
|
// ToV returns the resource version.
|
||||||
func (g GVR) ToV() string {
|
func (g GVR) ToV() string {
|
||||||
return g.v
|
return g.v
|
||||||
|
|
|
||||||
|
|
@ -21,27 +21,9 @@ type Generic struct {
|
||||||
NonResource
|
NonResource
|
||||||
}
|
}
|
||||||
|
|
||||||
// Describe describes a resource.
|
|
||||||
func (g *Generic) Describe(path string) (string, error) {
|
|
||||||
return Describe(g.Client(), g.gvr, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ToYAML returns a resource yaml.
|
|
||||||
func (g *Generic) ToYAML(path string) (string, error) {
|
|
||||||
o, err := g.Get(context.Background(), path)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
raw, err := ToYAML(o)
|
|
||||||
if err != nil {
|
|
||||||
return "", fmt.Errorf("unable to marshal resource %s", err)
|
|
||||||
}
|
|
||||||
return raw, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// List returns a collection of resources.
|
// List returns a collection of resources.
|
||||||
func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
func (g *Generic) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||||
|
log.Debug().Msgf("GENERIC LIST %q:%q", ns, g.gvr)
|
||||||
labelSel, ok := ctx.Value(internal.KeyLabels).(string)
|
labelSel, ok := ctx.Value(internal.KeyLabels).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Warn().Msgf("No label selector found in context. Listing all resources")
|
log.Warn().Msgf("No label selector found in context. Listing all resources")
|
||||||
|
|
@ -85,6 +67,25 @@ func (g *Generic) Get(ctx context.Context, path string) (runtime.Object, error)
|
||||||
return req.Namespace(ns).Get(n, opts)
|
return req.Namespace(ns).Get(n, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Describe describes a resource.
|
||||||
|
func (g *Generic) Describe(path string) (string, error) {
|
||||||
|
return Describe(g.Client(), g.gvr, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToYAML returns a resource yaml.
|
||||||
|
func (g *Generic) ToYAML(path string) (string, error) {
|
||||||
|
o, err := g.Get(context.Background(), path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := ToYAML(o)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("unable to marshal resource %s", err)
|
||||||
|
}
|
||||||
|
return raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Delete deletes a resource.
|
// Delete deletes a resource.
|
||||||
func (g *Generic) Delete(path string, cascade, force bool) error {
|
func (g *Generic) Delete(path string, cascade, force bool) error {
|
||||||
ns, n := client.Namespaced(path)
|
ns, n := client.Namespaced(path)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||||
|
"k8s.io/client-go/rest"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Table retrieves K8s resources as tabular data.
|
||||||
|
type Table struct {
|
||||||
|
Generic
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all Resources in a given namespace.
|
||||||
|
func (t *Table) List(ctx context.Context, ns string) ([]runtime.Object, error) {
|
||||||
|
a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName)
|
||||||
|
_, codec := t.codec()
|
||||||
|
|
||||||
|
c, err := t.getClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
o, err := c.Get().
|
||||||
|
SetHeader("Accept", a).
|
||||||
|
Namespace(ns).
|
||||||
|
Resource(t.gvr.ToR()).
|
||||||
|
VersionedParams(&metav1beta1.TableOptions{}, codec).
|
||||||
|
Do().Get()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return []runtime.Object{o}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Helpers...
|
||||||
|
|
||||||
|
const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json"
|
||||||
|
|
||||||
|
func (t *Table) getClient() (*rest.RESTClient, error) {
|
||||||
|
crConfig := t.Client().RestConfigOrDie()
|
||||||
|
gv := t.gvr.AsGV()
|
||||||
|
crConfig.GroupVersion = &gv
|
||||||
|
crConfig.APIPath = "/apis"
|
||||||
|
if len(t.gvr.ToG()) == 0 {
|
||||||
|
crConfig.APIPath = "/api"
|
||||||
|
}
|
||||||
|
codec, _ := t.codec()
|
||||||
|
crConfig.NegotiatedSerializer = codec.WithoutConversion()
|
||||||
|
|
||||||
|
crRestClient, err := rest.RESTClientFor(crConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crRestClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Table) codec() (serializer.CodecFactory, runtime.ParameterCodec) {
|
||||||
|
scheme := runtime.NewScheme()
|
||||||
|
gv := t.gvr.AsGV()
|
||||||
|
metav1.AddToGroupVersion(scheme, gv)
|
||||||
|
scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
|
||||||
|
scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{})
|
||||||
|
|
||||||
|
return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme)
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal/dao"
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
|
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -213,9 +214,21 @@ func (t *Table) reconcile(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
log.Debug().Msgf("LIST returned %d rows", len(oo))
|
log.Debug().Msgf("LIST returned %d rows", len(oo))
|
||||||
|
|
||||||
rows := make(render.Rows, len(oo))
|
var rows render.Rows
|
||||||
if err := hydrate(t.namespace, oo, rows, meta.Renderer); err != nil {
|
if _, ok := meta.Renderer.(*render.Generic); ok {
|
||||||
return err
|
table, ok := oo[0].(*metav1beta1.Table)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expecting a meta table but got %T", oo[0])
|
||||||
|
}
|
||||||
|
rows = make(render.Rows, len(table.Rows))
|
||||||
|
if err := tableHydrate(t.namespace, table, rows, meta.Renderer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rows = make(render.Rows, len(oo))
|
||||||
|
if err := hydrate(t.namespace, oo, rows, meta.Renderer); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t.data.Mutex.Lock()
|
t.data.Mutex.Lock()
|
||||||
|
|
@ -248,7 +261,7 @@ func (t *Table) resourceMeta() ResourceMeta {
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Debug().Msgf("Resource %s not found in registry. Going generic!", t.gvr)
|
log.Debug().Msgf("Resource %s not found in registry. Going generic!", t.gvr)
|
||||||
meta = ResourceMeta{
|
meta = ResourceMeta{
|
||||||
DAO: &dao.Generic{},
|
DAO: &dao.Table{},
|
||||||
Renderer: &render.Generic{},
|
Renderer: &render.Generic{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -282,3 +295,18 @@ func hydrate(ns string, oo []runtime.Object, rr render.Rows, re Renderer) error
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tableHydrate(ns string, table *metav1beta1.Table, rr render.Rows, re Renderer) error {
|
||||||
|
gr, ok := re.(*render.Generic)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expecting generic renderer but got %T", re)
|
||||||
|
}
|
||||||
|
gr.SetTable(table)
|
||||||
|
for i, row := range table.Rows {
|
||||||
|
if err := gr.Render(row, ns, &rr[i]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
|
||||||
"github.com/derailed/tview"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConfigMap renders a K8s ConfigMap to screen.
|
|
||||||
type ConfigMap struct{}
|
|
||||||
|
|
||||||
// ColorerFunc colors a resource row.
|
|
||||||
func (ConfigMap) ColorerFunc() ColorerFunc {
|
|
||||||
return DefaultColorer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header returns a header row.
|
|
||||||
func (ConfigMap) Header(ns string) HeaderRow {
|
|
||||||
var h HeaderRow
|
|
||||||
if client.IsAllNamespaces(ns) {
|
|
||||||
h = append(h, Header{Name: "NAMESPACE"})
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(h,
|
|
||||||
Header{Name: "NAME"},
|
|
||||||
Header{Name: "DATA", Align: tview.AlignRight},
|
|
||||||
Header{Name: "AGE", Decorator: AgeDecorator},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
|
||||||
// BOZO!! 44allocs down to 5allocs avoiding marshal??
|
|
||||||
func (c ConfigMap) Render(o interface{}, ns string, r *Row) error {
|
|
||||||
raw, ok := o.(*unstructured.Unstructured)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Expected ConfigMap, but got %T", o)
|
|
||||||
}
|
|
||||||
|
|
||||||
meta, ok := raw.Object["metadata"].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("No meta")
|
|
||||||
}
|
|
||||||
|
|
||||||
n, nss := extractMetaField(meta, "name"), extractMetaField(meta, "namespace")
|
|
||||||
r.ID = FQN(nss, n)
|
|
||||||
r.Fields = make(Fields, 0, len(c.Header(ns)))
|
|
||||||
if client.IsAllNamespaces(ns) {
|
|
||||||
r.Fields = append(r.Fields, nss)
|
|
||||||
}
|
|
||||||
|
|
||||||
var size int
|
|
||||||
data, ok := raw.Object["data"]
|
|
||||||
if ok {
|
|
||||||
d, ok := data.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("expecting map but got %T", raw.Object["data"])
|
|
||||||
}
|
|
||||||
size = len(d)
|
|
||||||
}
|
|
||||||
t, err := extractMetaTime(meta)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r.Fields = append(r.Fields,
|
|
||||||
n,
|
|
||||||
strconv.Itoa(size),
|
|
||||||
toAge(t),
|
|
||||||
)
|
|
||||||
|
|
||||||
// var cm v1.ConfigMap
|
|
||||||
// err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &cm)
|
|
||||||
// if err != nil {
|
|
||||||
// return err
|
|
||||||
// }
|
|
||||||
|
|
||||||
// r.ID = MetaFQN(cm.ObjectMeta)
|
|
||||||
// r.Fields = make(Fields, 0, len(c.Header(ns)))
|
|
||||||
// if client.IsAllNamespaces(ns) {
|
|
||||||
// r.Fields = append(r.Fields, cm.Namespace)
|
|
||||||
// }
|
|
||||||
// r.Fields = append(r.Fields,
|
|
||||||
// cm.Name,
|
|
||||||
// strconv.Itoa(len(cm.Data)),
|
|
||||||
// toAge(cm.ObjectMeta.CreationTimestamp),
|
|
||||||
// )
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractMetaTime(m map[string]interface{}) (metav1.Time, error) {
|
|
||||||
f, ok := m["creationTimestamp"]
|
|
||||||
if !ok {
|
|
||||||
return metav1.Time{}, fmt.Errorf("failed to extract time from meta")
|
|
||||||
}
|
|
||||||
|
|
||||||
t, ok := f.(string)
|
|
||||||
if !ok {
|
|
||||||
return metav1.Time{}, fmt.Errorf("failed to extract time from field")
|
|
||||||
}
|
|
||||||
|
|
||||||
ti, err := time.Parse(time.RFC3339, t)
|
|
||||||
if err != nil {
|
|
||||||
return metav1.Time{}, err
|
|
||||||
}
|
|
||||||
return metav1.Time{Time: ti}, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package render_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/render"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCmRender(t *testing.T) {
|
|
||||||
c := render.ConfigMap{}
|
|
||||||
r := render.NewRow(4)
|
|
||||||
|
|
||||||
assert.Nil(t, c.Render(load(t, "cm"), "", &r))
|
|
||||||
assert.Equal(t, "default/blee", r.ID)
|
|
||||||
assert.Equal(t, render.Fields{"default", "blee", "2"}, r.Fields[:3])
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkCmRender(b *testing.B) {
|
|
||||||
c := render.ConfigMap{}
|
|
||||||
r := render.NewRow(4)
|
|
||||||
o := load(b, "cm")
|
|
||||||
b.ResetTimer()
|
|
||||||
b.ReportAllocs()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = c.Render(o, "", &r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers...
|
|
||||||
|
|
||||||
func load(t assert.TestingT, n string) *unstructured.Unstructured {
|
|
||||||
raw, err := ioutil.ReadFile(fmt.Sprintf("assets/%s.json", n))
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
var o unstructured.Unstructured
|
|
||||||
err = json.Unmarshal(raw, &o)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
return &o
|
|
||||||
}
|
|
||||||
|
|
@ -55,7 +55,7 @@ func (g *Generic) Header(ns string) HeaderRow {
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
// Render renders a K8s resource to screen.
|
||||||
func (g *Generic) Render(o interface{}, ns string, r *Row) error {
|
func (g *Generic) Render(o interface{}, ns string, r *Row) error {
|
||||||
row, ok := o.(*metav1beta1.TableRow)
|
row, ok := o.(metav1beta1.TableRow)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("expecting a TableRow but got %T", o)
|
return fmt.Errorf("expecting a TableRow but got %T", o)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,7 @@ func TestGenericRender(t *testing.T) {
|
||||||
re.SetTable(u.table)
|
re.SetTable(u.table)
|
||||||
|
|
||||||
assert.Equal(t, u.eHeader, re.Header(u.ns))
|
assert.Equal(t, u.eHeader, re.Header(u.ns))
|
||||||
assert.Nil(t, re.Render(&u.table.Rows[0], u.ns, &r))
|
assert.Nil(t, re.Render(u.table.Rows[0], u.ns, &r))
|
||||||
assert.Equal(t, u.eID, r.ID)
|
assert.Equal(t, u.eID, r.ID)
|
||||||
assert.Equal(t, u.eFields, r.Fields)
|
assert.Equal(t, u.eFields, r.Fields)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package render_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helpers...
|
||||||
|
|
||||||
|
func load(t assert.TestingT, n string) *unstructured.Unstructured {
|
||||||
|
raw, err := ioutil.ReadFile(fmt.Sprintf("assets/%s.json", n))
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
var o unstructured.Unstructured
|
||||||
|
err = json.Unmarshal(raw, &o)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
return &o
|
||||||
|
}
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
package render
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
|
||||||
"github.com/derailed/tview"
|
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Secret renders a K8s Secret to screen.
|
|
||||||
type Secret struct{}
|
|
||||||
|
|
||||||
// ColorerFunc colors a resource row.
|
|
||||||
func (Secret) ColorerFunc() ColorerFunc {
|
|
||||||
return DefaultColorer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Header returns a header row.
|
|
||||||
func (Secret) Header(ns string) HeaderRow {
|
|
||||||
var h HeaderRow
|
|
||||||
if client.IsAllNamespaces(ns) {
|
|
||||||
h = append(h, Header{Name: "NAMESPACE"})
|
|
||||||
}
|
|
||||||
|
|
||||||
return append(h,
|
|
||||||
Header{Name: "NAME"},
|
|
||||||
Header{Name: "TYPE"},
|
|
||||||
Header{Name: "DATA", Align: tview.AlignRight},
|
|
||||||
Header{Name: "AGE", Decorator: AgeDecorator},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render renders a K8s resource to screen.
|
|
||||||
func (s Secret) Render(o interface{}, ns string, r *Row) error {
|
|
||||||
raw, ok := o.(*unstructured.Unstructured)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Expected Secret, but got %T", o)
|
|
||||||
}
|
|
||||||
var sec v1.Secret
|
|
||||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.ID = MetaFQN(sec.ObjectMeta)
|
|
||||||
r.Fields = make(Fields, 0, len(s.Header(ns)))
|
|
||||||
if client.IsAllNamespaces(ns) {
|
|
||||||
r.Fields = append(r.Fields, sec.Namespace)
|
|
||||||
}
|
|
||||||
r.Fields = append(r.Fields,
|
|
||||||
sec.Name,
|
|
||||||
string(sec.Type),
|
|
||||||
strconv.Itoa(len(sec.Data)),
|
|
||||||
toAge(sec.ObjectMeta.CreationTimestamp),
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
package render_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/render"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSecRender(t *testing.T) {
|
|
||||||
c := render.Secret{}
|
|
||||||
r := render.NewRow(4)
|
|
||||||
|
|
||||||
c.Render(load(t, "sec"), "", &r)
|
|
||||||
assert.Equal(t, "default/s1", r.ID)
|
|
||||||
assert.Equal(t, render.Fields{"default", "s1", "Opaque", "2"}, r.Fields[:4])
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue