checkpoint
parent
860728c083
commit
d45d3af116
|
|
@ -4,7 +4,7 @@ before:
|
||||||
- go mod download
|
- go mod download
|
||||||
- go generate ./...
|
- go generate ./...
|
||||||
release:
|
release:
|
||||||
prerelease: true
|
prerelease: false
|
||||||
builds:
|
builds:
|
||||||
- env:
|
- env:
|
||||||
- CGO_ENABLED=0
|
- CGO_ENABLED=0
|
||||||
|
|
@ -53,38 +53,7 @@ brews:
|
||||||
name: derailed
|
name: derailed
|
||||||
email: fernand@imhotep.io
|
email: fernand@imhotep.io
|
||||||
folder: Formula
|
folder: Formula
|
||||||
homepage: https://k9ss.io
|
homepage: https://k8sk9s.dev/
|
||||||
description: Kubernetes CLI To Manage Your Clusters In Style!
|
description: Kubernetes CLI To Manage Your Clusters In Style!
|
||||||
test: |
|
test: |
|
||||||
system "k9s version"
|
system "k9s version"
|
||||||
|
|
||||||
# Snapcraft
|
|
||||||
# snapcraft:
|
|
||||||
# name: k9s
|
|
||||||
# summary: K9s is a CLI to view and manage your Kubernetes clusters.
|
|
||||||
# description: |
|
|
||||||
# K9s is a CLI to view and manage your Kubernetes clusters.
|
|
||||||
# By leveraging a terminal UI, you can easily traverse Kubernetes resources
|
|
||||||
# and view the state of you clusters in a single powerful session.
|
|
||||||
# name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
|
|
||||||
# publish: true
|
|
||||||
# replacements:
|
|
||||||
# amd64: 64-bit
|
|
||||||
# 386: 32-bit
|
|
||||||
# darwin: macOS
|
|
||||||
# linux: Tux
|
|
||||||
# bit: Arm
|
|
||||||
# bitv6: Arm6
|
|
||||||
# bitv7: Arm7
|
|
||||||
# # grade: devel
|
|
||||||
# # confinement: devmode
|
|
||||||
# grade: stable
|
|
||||||
# confinement: strict
|
|
||||||
# apps:
|
|
||||||
# k9s:
|
|
||||||
# plugs: ["home", "network", "kube-config"]
|
|
||||||
# plugs:
|
|
||||||
# kube-config:
|
|
||||||
# interface: personal-files
|
|
||||||
# read:
|
|
||||||
# - $HOME/.kube
|
|
||||||
|
|
@ -20,6 +20,13 @@ for changes and offers subsequent commands to interact with observed Kubernetes
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Slack Channel
|
||||||
|
|
||||||
|
Wanna discuss K9s features with your fellow `K9sers` or simply show your support for this tool?
|
||||||
|
Please Dial [K9s Slack](https://k9sers.slack.com/)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
K9s is available on Linux, OSX and Windows platforms.
|
K9s is available on Linux, OSX and Windows platforms.
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,9 @@ func (g GVRs) Less(i, j int) bool {
|
||||||
|
|
||||||
// Can determines the available actions for a given resource.
|
// Can determines the available actions for a given resource.
|
||||||
func Can(verbs []string, v string) bool {
|
func Can(verbs []string, v string) bool {
|
||||||
|
if verbs == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if len(verbs) == 0 {
|
if len(verbs) == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -208,7 +208,6 @@ func (l *Log) updateLogs(ctx context.Context, c <-chan string) {
|
||||||
// AddListener adds a new model listener.
|
// AddListener adds a new model listener.
|
||||||
func (l *Log) AddListener(listener LogsListener) {
|
func (l *Log) AddListener(listener LogsListener) {
|
||||||
l.listeners = append(l.listeners, listener)
|
l.listeners = append(l.listeners, listener)
|
||||||
l.fireLogChanged(l.lines)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveListener delete a listener from the lisl.
|
// RemoveListener delete a listener from the lisl.
|
||||||
|
|
@ -265,6 +264,7 @@ func (l *Log) fireLogError(err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Log) fireLogChanged(lines []string) {
|
func (l *Log) fireLogChanged(lines []string) {
|
||||||
|
log.Debug().Msgf("FIRE LOGS CHANGED %v", lines)
|
||||||
for _, lis := range l.listeners {
|
for _, lis := range l.listeners {
|
||||||
lis.LogChanged(lines)
|
lis.LogChanged(lines)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ func TestLogFullBuffer(t *testing.T) {
|
||||||
}
|
}
|
||||||
m.Notify(false)
|
m.Notify(false)
|
||||||
|
|
||||||
assert.Equal(t, 2, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, data[4:], v.data)
|
assert.Equal(t, data[4:], v.data)
|
||||||
|
|
@ -74,13 +74,13 @@ func TestLogFilter(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
m.Notify(true)
|
m.Notify(true)
|
||||||
assert.Equal(t, 3, v.dataCalled)
|
assert.Equal(t, 2, v.dataCalled)
|
||||||
assert.Equal(t, 2, v.clearCalled)
|
assert.Equal(t, 2, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, u.e, len(v.data))
|
assert.Equal(t, u.e, len(v.data))
|
||||||
|
|
||||||
m.ClearFilter()
|
m.ClearFilter()
|
||||||
assert.Equal(t, 4, v.dataCalled)
|
assert.Equal(t, 3, v.dataCalled)
|
||||||
assert.Equal(t, 2, v.clearCalled)
|
assert.Equal(t, 2, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, size, len(v.data))
|
assert.Equal(t, size, len(v.data))
|
||||||
|
|
@ -103,7 +103,7 @@ func TestLogStartStop(t *testing.T) {
|
||||||
m.Notify(true)
|
m.Notify(true)
|
||||||
m.Stop()
|
m.Stop()
|
||||||
|
|
||||||
assert.Equal(t, 2, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 1, v.clearCalled)
|
assert.Equal(t, 1, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, 2, len(v.data))
|
assert.Equal(t, 2, len(v.data))
|
||||||
|
|
@ -125,7 +125,7 @@ func TestLogClear(t *testing.T) {
|
||||||
m.Notify(true)
|
m.Notify(true)
|
||||||
m.Clear()
|
m.Clear()
|
||||||
|
|
||||||
assert.Equal(t, 2, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 2, v.clearCalled)
|
assert.Equal(t, 2, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, 0, len(v.data))
|
assert.Equal(t, 0, len(v.data))
|
||||||
|
|
@ -141,7 +141,7 @@ func TestLogBasic(t *testing.T) {
|
||||||
data := []string{"line1", "line2"}
|
data := []string{"line1", "line2"}
|
||||||
m.Set(data)
|
m.Set(data)
|
||||||
|
|
||||||
assert.Equal(t, 2, v.dataCalled)
|
assert.Equal(t, 1, v.dataCalled)
|
||||||
assert.Equal(t, 0, v.clearCalled)
|
assert.Equal(t, 0, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, data, v.data)
|
assert.Equal(t, data, v.data)
|
||||||
|
|
@ -153,6 +153,7 @@ func TestLogAppend(t *testing.T) {
|
||||||
|
|
||||||
v := newTestView()
|
v := newTestView()
|
||||||
m.AddListener(v)
|
m.AddListener(v)
|
||||||
|
m.Set([]string{"blah blah"})
|
||||||
assert.Equal(t, []string{"blah blah"}, v.data)
|
assert.Equal(t, []string{"blah blah"}, v.data)
|
||||||
|
|
||||||
data := []string{"line1", "line2"}
|
data := []string{"line1", "line2"}
|
||||||
|
|
@ -182,7 +183,7 @@ func TestLogTimedout(t *testing.T) {
|
||||||
m.Append(d)
|
m.Append(d)
|
||||||
}
|
}
|
||||||
m.Notify(true)
|
m.Notify(true)
|
||||||
assert.Equal(t, 3, v.dataCalled)
|
assert.Equal(t, 2, v.dataCalled)
|
||||||
assert.Equal(t, 2, v.clearCalled)
|
assert.Equal(t, 2, v.clearCalled)
|
||||||
assert.Equal(t, 0, v.errCalled)
|
assert.Equal(t, 0, v.errCalled)
|
||||||
assert.Equal(t, []string{"line1"}, v.data)
|
assert.Equal(t, []string{"line1"}, v.data)
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ func (t *Table) Get(ctx context.Context, path string) (runtime.Object, error) {
|
||||||
return meta.DAO.Get(ctx, path)
|
return meta.DAO.Get(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete removes a resource.
|
// Delete deletes a resource.
|
||||||
func (t *Table) Delete(ctx context.Context, path string, cascade, force bool) error {
|
func (t *Table) Delete(ctx context.Context, path string, cascade, force bool) error {
|
||||||
meta, err := t.getMeta(ctx)
|
meta, err := t.getMeta(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ func (t *Tree) InNamespace(ns string) bool {
|
||||||
|
|
||||||
// Empty return true if no model data.
|
// Empty return true if no model data.
|
||||||
func (t *Tree) Empty() bool {
|
func (t *Tree) Empty() bool {
|
||||||
return t.root.Empty()
|
return t.root.IsLeaf()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Peek returns model data.
|
// Peek returns model data.
|
||||||
|
|
@ -124,6 +124,36 @@ func (t *Tree) Peek() *xray.TreeNode {
|
||||||
return t.root
|
return t.root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Describe describes a given resource.
|
||||||
|
func (t *Tree) Describe(ctx context.Context, gvr, path string) (string, error) {
|
||||||
|
meta, err := t.getMeta(ctx, gvr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, ok := meta.DAO.(dao.Describer)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("no describer for %q", meta.DAO.GVR())
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc.Describe(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToYAML returns a resource yaml.
|
||||||
|
func (t *Tree) ToYAML(ctx context.Context, gvr, path string) (string, error) {
|
||||||
|
meta, err := t.getMeta(ctx, gvr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
desc, ok := meta.DAO.(dao.Describer)
|
||||||
|
if !ok {
|
||||||
|
return "", fmt.Errorf("no describer for %q", meta.DAO.GVR())
|
||||||
|
}
|
||||||
|
|
||||||
|
return desc.ToYAML(path)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *Tree) updater(ctx context.Context) {
|
func (t *Tree) updater(ctx context.Context) {
|
||||||
defer log.Debug().Msgf("Model canceled -- %q", t.gvr)
|
defer log.Debug().Msgf("Model canceled -- %q", t.gvr)
|
||||||
|
|
||||||
|
|
@ -181,7 +211,7 @@ func (t *Tree) reconcile(ctx context.Context) error {
|
||||||
log.Debug().Msgf(" TREE returned %d rows", len(oo))
|
log.Debug().Msgf(" TREE returned %d rows", len(oo))
|
||||||
|
|
||||||
ns := client.CleanseNamespace(t.namespace)
|
ns := client.CleanseNamespace(t.namespace)
|
||||||
root := xray.NewTreeNode(t.gvr, client.NewGVR(t.gvr).ToR())
|
root := xray.NewTreeNode("root", client.NewGVR(t.gvr).ToR())
|
||||||
ctx = context.WithValue(ctx, xray.KeyParent, root)
|
ctx = context.WithValue(ctx, xray.KeyParent, root)
|
||||||
if _, ok := meta.TreeRenderer.(*xray.Generic); ok {
|
if _, ok := meta.TreeRenderer.(*xray.Generic); ok {
|
||||||
table, ok := oo[0].(*metav1beta1.Table)
|
table, ok := oo[0].(*metav1beta1.Table)
|
||||||
|
|
@ -212,17 +242,6 @@ func (t *Tree) reconcile(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tree) getMeta(ctx context.Context) (ResourceMeta, error) {
|
|
||||||
meta := t.resourceMeta()
|
|
||||||
factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
|
|
||||||
if !ok {
|
|
||||||
return ResourceMeta{}, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory))
|
|
||||||
}
|
|
||||||
meta.DAO.Init(factory, client.NewGVR(t.gvr))
|
|
||||||
|
|
||||||
return meta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Tree) resourceMeta() ResourceMeta {
|
func (t *Tree) resourceMeta() ResourceMeta {
|
||||||
meta, ok := Registry[t.gvr]
|
meta, ok := Registry[t.gvr]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
@ -251,6 +270,17 @@ func (t *Tree) fireTreeLoadFailed(err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *Tree) getMeta(ctx context.Context, gvr string) (ResourceMeta, error) {
|
||||||
|
meta := t.resourceMeta()
|
||||||
|
factory, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
|
||||||
|
if !ok {
|
||||||
|
return ResourceMeta{}, fmt.Errorf("expected Factory in context but got %T", ctx.Value(internal.KeyFactory))
|
||||||
|
}
|
||||||
|
meta.DAO.Init(factory, client.NewGVR(gvr))
|
||||||
|
|
||||||
|
return meta, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,18 +52,19 @@ func inScope(scopes, aliases []string) bool {
|
||||||
func hotKeyActions(r Runner, aa ui.KeyActions) {
|
func hotKeyActions(r Runner, aa ui.KeyActions) {
|
||||||
hh := config.NewHotKeys()
|
hh := config.NewHotKeys()
|
||||||
if err := hh.Load(); err != nil {
|
if err := hh.Load(); err != nil {
|
||||||
|
log.Error().Err(err).Msgf("Loading HOTKEYS")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, hk := range hh.HotKey {
|
for k, hk := range hh.HotKey {
|
||||||
key, err := asKey(hk.ShortCut)
|
key, err := asKey(hk.ShortCut)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Unable to map hotkey shortcut to a key")
|
log.Error().Err(err).Msg("HOT-KEY Unable to map hotkey shortcut to a key")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, ok := aa[key]
|
_, ok := aa[key]
|
||||||
if ok {
|
if ok {
|
||||||
log.Error().Err(fmt.Errorf("Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut")
|
log.Error().Err(fmt.Errorf("HOT-KEY Doh! you are trying to overide an existing command `%s", k)).Msg("Invalid shortcut")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
aa[key] = ui.NewSharedKeyAction(
|
aa[key] = ui.NewSharedKeyAction(
|
||||||
|
|
|
||||||
|
|
@ -44,16 +44,6 @@ func (c *Command) Init() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) xrayCmd(cmd string) error {
|
func (c *Command) xrayCmd(cmd string) error {
|
||||||
|
|
||||||
// if _, ok := c.app.Content.GetPrimitive("main").(*Xray); ok {
|
|
||||||
// return errors.New("unable to locate main panel")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if c.app.Content.Top() != nil && c.app.Content.Top().Name() == xrayTitle {
|
|
||||||
// c.app.Content.Pop()
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
tokens := strings.Split(cmd, " ")
|
tokens := strings.Split(cmd, " ")
|
||||||
if len(tokens) < 2 {
|
if len(tokens) < 2 {
|
||||||
return errors.New("You must specify a resource")
|
return errors.New("You must specify a resource")
|
||||||
|
|
@ -63,18 +53,6 @@ func (c *Command) xrayCmd(cmd string) error {
|
||||||
return fmt.Errorf("Huh? `%s` Command not found", cmd)
|
return fmt.Errorf("Huh? `%s` Command not found", cmd)
|
||||||
}
|
}
|
||||||
return c.exec(cmd, "xrays", NewXray(gvr), true)
|
return c.exec(cmd, "xrays", NewXray(gvr), true)
|
||||||
|
|
||||||
// if err := c.app.inject(NewXray(gvr)); err != nil {
|
|
||||||
// c.app.Flash().Err(err)
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
|
|
||||||
// c.app.Config.SetActiveView(cmd)
|
|
||||||
// if err := c.app.Config.Save(); err != nil {
|
|
||||||
// log.Error().Err(err).Msg("Config save failed!")
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exec the Command by showing associated display.
|
// Exec the Command by showing associated display.
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ func (v *Help) bindKeys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Help) computeMaxes(hh model.MenuHints) {
|
func (v *Help) computeMaxes(hh model.MenuHints) {
|
||||||
v.maxKey, v.maxDesc = 0, 0
|
|
||||||
for _, h := range hh {
|
for _, h := range hh {
|
||||||
if len(h.Mnemonic) > v.maxKey {
|
if len(h.Mnemonic) > v.maxKey {
|
||||||
v.maxKey = len(h.Mnemonic)
|
v.maxKey = len(h.Mnemonic)
|
||||||
|
|
@ -80,6 +79,7 @@ func (v *Help) computeMaxes(hh model.MenuHints) {
|
||||||
func (v *Help) build() {
|
func (v *Help) build() {
|
||||||
v.Clear()
|
v.Clear()
|
||||||
|
|
||||||
|
v.maxRows = len(v.showGeneral())
|
||||||
ff := []HelpFunc{v.app.Content.Top().Hints, v.showGeneral, v.showNav, v.showHelp}
|
ff := []HelpFunc{v.app.Content.Top().Hints, v.showGeneral, v.showNav, v.showHelp}
|
||||||
var col int
|
var col int
|
||||||
for i, section := range []string{"RESOURCE", "GENERAL", "NAVIGATION", "HELP"} {
|
for i, section := range []string{"RESOURCE", "GENERAL", "NAVIGATION", "HELP"} {
|
||||||
|
|
@ -197,7 +197,7 @@ func (v *Help) showGeneral() model.MenuHints {
|
||||||
Description: "Clear command",
|
Description: "Clear command",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Mnemonic: "h",
|
Mnemonic: "Ctrl-h",
|
||||||
Description: "Toggle Header",
|
Description: "Toggle Header",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package view
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -10,13 +11,16 @@ 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/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/model"
|
"github.com/derailed/k9s/internal/model"
|
||||||
"github.com/derailed/k9s/internal/ui"
|
"github.com/derailed/k9s/internal/ui"
|
||||||
|
"github.com/derailed/k9s/internal/ui/dialog"
|
||||||
"github.com/derailed/k9s/internal/xray"
|
"github.com/derailed/k9s/internal/xray"
|
||||||
"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"
|
||||||
"github.com/sahilm/fuzzy"
|
"github.com/sahilm/fuzzy"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const xrayTitle = "Xray"
|
const xrayTitle = "Xray"
|
||||||
|
|
@ -33,6 +37,9 @@ type Xray struct {
|
||||||
cancelFn context.CancelFunc
|
cancelFn context.CancelFunc
|
||||||
cmdBuff *ui.CmdBuff
|
cmdBuff *ui.CmdBuff
|
||||||
expandNodes bool
|
expandNodes bool
|
||||||
|
meta metav1.APIResource
|
||||||
|
count int
|
||||||
|
envFn EnvFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ ResourceViewer = (*Xray)(nil)
|
var _ ResourceViewer = (*Xray)(nil)
|
||||||
|
|
@ -40,6 +47,7 @@ var _ ResourceViewer = (*Xray)(nil)
|
||||||
// NewXray returns a new view.
|
// NewXray returns a new view.
|
||||||
func NewXray(gvr client.GVR) ResourceViewer {
|
func NewXray(gvr client.GVR) ResourceViewer {
|
||||||
a := Xray{
|
a := Xray{
|
||||||
|
gvr: gvr,
|
||||||
TreeView: tview.NewTreeView(),
|
TreeView: tview.NewTreeView(),
|
||||||
model: model.NewTree(gvr.String()),
|
model: model.NewTree(gvr.String()),
|
||||||
expandNodes: true,
|
expandNodes: true,
|
||||||
|
|
@ -53,6 +61,11 @@ func NewXray(gvr client.GVR) ResourceViewer {
|
||||||
// Init initializes the view
|
// Init initializes the view
|
||||||
func (x *Xray) Init(ctx context.Context) error {
|
func (x *Xray) Init(ctx context.Context) error {
|
||||||
var err error
|
var err error
|
||||||
|
x.meta, err = dao.MetaFor(x.gvr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
if x.app, err = extractApp(ctx); err != nil {
|
if x.app, err = extractApp(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -64,7 +77,7 @@ func (x *Xray) Init(ctx context.Context) error {
|
||||||
x.SetBackgroundColor(config.AsColor(x.app.Styles.GetTable().BgColor))
|
x.SetBackgroundColor(config.AsColor(x.app.Styles.GetTable().BgColor))
|
||||||
x.SetBorderColor(config.AsColor(x.app.Styles.GetTable().FgColor))
|
x.SetBorderColor(config.AsColor(x.app.Styles.GetTable().FgColor))
|
||||||
x.SetBorderFocusColor(config.AsColor(x.app.Styles.Frame().Border.FocusColor))
|
x.SetBorderFocusColor(config.AsColor(x.app.Styles.Frame().Border.FocusColor))
|
||||||
x.SetTitle(" Xray ")
|
x.SetTitle(fmt.Sprintf(" %s-%s ", xrayTitle, strings.Title(x.gvr.ToR())))
|
||||||
x.SetGraphics(true)
|
x.SetGraphics(true)
|
||||||
x.SetGraphicsColor(tcell.ColorDimGray)
|
x.SetGraphicsColor(tcell.ColorDimGray)
|
||||||
x.SetInputCapture(x.keyboard)
|
x.SetInputCapture(x.keyboard)
|
||||||
|
|
@ -80,7 +93,9 @@ func (x *Xray) Init(ctx context.Context) error {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
x.selectedNode = ref.Path
|
x.selectedNode = ref.Path
|
||||||
|
x.refreshActions()
|
||||||
})
|
})
|
||||||
|
x.refreshActions()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -101,9 +116,7 @@ func (x *Xray) Hints() model.MenuHints {
|
||||||
func (x *Xray) bindKeys() {
|
func (x *Xray) bindKeys() {
|
||||||
x.Actions().Add(ui.KeyActions{
|
x.Actions().Add(ui.KeyActions{
|
||||||
ui.KeySpace: ui.NewKeyAction("Expand/Collapse", x.noopCmd, true),
|
ui.KeySpace: ui.NewKeyAction("Expand/Collapse", x.noopCmd, true),
|
||||||
ui.KeyE: ui.NewKeyAction("Expand/Collapse All", x.toggleCollapseCmd, true),
|
ui.KeyX: ui.NewKeyAction("Expand/Collapse All", x.toggleCollapseCmd, true),
|
||||||
ui.KeyV: ui.NewKeyAction("Goto", x.gotoCmd, true),
|
|
||||||
tcell.KeyEnter: ui.NewKeyAction("Goto", x.gotoCmd, true),
|
|
||||||
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", x.activateCmd, false),
|
ui.KeySlash: ui.NewSharedKeyAction("Filter Mode", x.activateCmd, false),
|
||||||
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
|
tcell.KeyBackspace2: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
|
||||||
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
|
tcell.KeyBackspace: ui.NewSharedKeyAction("Erase", x.eraseCmd, false),
|
||||||
|
|
@ -134,6 +147,244 @@ func (x *Xray) keyboard(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Xray) refreshActions() {
|
||||||
|
aa := make(ui.KeyActions)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
pluginActions(x, aa)
|
||||||
|
hotKeyActions(x, aa)
|
||||||
|
|
||||||
|
x.actions.Add(aa)
|
||||||
|
x.app.Menu().HydrateMenu(x.Hints())
|
||||||
|
}()
|
||||||
|
|
||||||
|
x.actions.Clear()
|
||||||
|
x.bindKeys()
|
||||||
|
|
||||||
|
ref := x.selectedSpec()
|
||||||
|
if ref == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
x.meta, err = dao.MetaFor(client.NewGVR(ref.GVR))
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Msgf("NO meta for %q -- %s", ref.GVR, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.Can(x.meta.Verbs, "edit") {
|
||||||
|
aa[ui.KeyE] = ui.NewKeyAction("Edit", x.editCmd, true)
|
||||||
|
}
|
||||||
|
if client.Can(x.meta.Verbs, "delete") {
|
||||||
|
aa[tcell.KeyCtrlD] = ui.NewKeyAction("Delete", x.deleteCmd, true)
|
||||||
|
}
|
||||||
|
if client.Can(x.meta.Verbs, "view") {
|
||||||
|
aa[tcell.KeyEnter] = ui.NewKeyAction("Goto", x.gotoCmd, true)
|
||||||
|
}
|
||||||
|
if !dao.IsK9sMeta(x.meta) {
|
||||||
|
aa[ui.KeyY] = ui.NewKeyAction("YAML", x.viewCmd, true)
|
||||||
|
aa[ui.KeyD] = ui.NewKeyAction("Describe", x.describeCmd, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref.GVR == "containers" {
|
||||||
|
aa[ui.KeyS] = ui.NewKeyAction("Shell", x.shellCmd, true)
|
||||||
|
aa[ui.KeyL] = ui.NewKeyAction("Logs", x.logsCmd(false), true)
|
||||||
|
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", x.logsCmd(true), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
x.actions.Add(aa)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) GetSelectedItem() string {
|
||||||
|
ref := x.selectedSpec()
|
||||||
|
if ref == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return ref.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnvFn returns an plugin env function if available.
|
||||||
|
func (x *Xray) EnvFn() EnvFunc {
|
||||||
|
return x.envFn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aliases returns all available aliases.
|
||||||
|
func (x *Xray) Aliases() []string {
|
||||||
|
return append(x.meta.ShortNames, x.meta.SingularName, x.meta.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) selectedSpec() *xray.NodeSpec {
|
||||||
|
node := x.GetCurrentNode()
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, ok := node.GetReference().(xray.NodeSpec)
|
||||||
|
if !ok {
|
||||||
|
log.Error().Msgf("Expecting a NodeSpec!")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ref
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) logsCmd(prev bool) func(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
return func(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
ref := x.selectedSpec()
|
||||||
|
if ref == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref.Parent != nil {
|
||||||
|
x.showLogs(ref.Parent, ref, prev)
|
||||||
|
} else {
|
||||||
|
log.Error().Msgf("No parent found for container %q", ref.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) showLogs(pod, co *xray.NodeSpec, prev bool) {
|
||||||
|
log.Debug().Msgf("SHOWING LOGS path %q", co.Path)
|
||||||
|
// Need to load and wait for pods
|
||||||
|
ns, _ := client.Namespaced(pod.Path)
|
||||||
|
_, err := x.app.factory.CanForResource(ns, "v1/pods", client.MonitorAccess)
|
||||||
|
if err != nil {
|
||||||
|
x.app.Flash().Err(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := x.app.inject(NewLog(client.NewGVR(co.GVR), pod.Path, co.Path, prev)); err != nil {
|
||||||
|
x.app.Flash().Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
ref := x.selectedSpec()
|
||||||
|
if ref == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Msgf("STATUS %q", ref.Status)
|
||||||
|
if ref.Status != "" {
|
||||||
|
x.app.Flash().Errf("%s is not in a running state", ref.Path)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ref.Parent != nil {
|
||||||
|
x.shellIn(ref.Parent.Path, ref.Path)
|
||||||
|
} else {
|
||||||
|
log.Error().Msgf("No parent found on container node %q", ref.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) shellIn(path, co string) {
|
||||||
|
x.Stop()
|
||||||
|
shellIn(x.app, path, co)
|
||||||
|
x.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) viewCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
ref := x.selectedSpec()
|
||||||
|
if ref == nil {
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := x.defaultContext()
|
||||||
|
raw, err := x.model.ToYAML(ctx, ref.GVR, ref.Path)
|
||||||
|
if err != nil {
|
||||||
|
x.App().Flash().Errf("unable to get resource %q -- %s", ref.GVR, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
details := NewDetails(x.app, "YAML", ref.Path).Update(raw)
|
||||||
|
if err := x.app.inject(details); err != nil {
|
||||||
|
x.app.Flash().Err(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
ref := x.selectedSpec()
|
||||||
|
if ref == nil {
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
|
||||||
|
x.Stop()
|
||||||
|
defer x.Start()
|
||||||
|
{
|
||||||
|
gvr := client.NewGVR(ref.GVR)
|
||||||
|
meta, err := dao.MetaFor(gvr)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn().Msgf("NO meta for %q -- %s", ref.GVR, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
x.resourceDelete(gvr, ref, fmt.Sprintf("Delete %s %s?", meta.SingularName, ref.Path))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) describeCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
ref := x.selectedSpec()
|
||||||
|
if ref == nil {
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
|
||||||
|
x.describe(ref.GVR, ref.Path)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) describe(gvr, path string) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ctx = context.WithValue(ctx, internal.KeyFactory, x.app.factory)
|
||||||
|
|
||||||
|
yaml, err := x.model.Describe(ctx, gvr, path)
|
||||||
|
if err != nil {
|
||||||
|
x.app.Flash().Errf("Describe command failed: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
details := NewDetails(x.app, "Describe", path).Update(yaml)
|
||||||
|
if err := x.app.inject(details); err != nil {
|
||||||
|
x.app.Flash().Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *Xray) editCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
|
ref := x.selectedSpec()
|
||||||
|
if ref == nil {
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
|
||||||
|
x.Stop()
|
||||||
|
defer x.Start()
|
||||||
|
{
|
||||||
|
ns, n := client.Namespaced(ref.Path)
|
||||||
|
args := make([]string, 0, 10)
|
||||||
|
args = append(args, "edit")
|
||||||
|
args = append(args, client.NewGVR(ref.GVR).ToR())
|
||||||
|
args = append(args, "-n", ns)
|
||||||
|
args = append(args, "--context", x.app.Config.K9s.CurrentContext)
|
||||||
|
if cfg := x.app.Conn().Config().Flags().KubeConfig; cfg != nil && *cfg != "" {
|
||||||
|
args = append(args, "--kubeconfig", *cfg)
|
||||||
|
}
|
||||||
|
if !runK(true, x.app, append(args, n)...) {
|
||||||
|
x.app.Flash().Err(errors.New("Edit exec failed"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return evt
|
||||||
|
}
|
||||||
|
|
||||||
func (x *Xray) noopCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (x *Xray) noopCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return evt
|
return evt
|
||||||
}
|
}
|
||||||
|
|
@ -168,19 +419,6 @@ func (x *Xray) eraseCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Xray) filterCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
||||||
if !x.cmdBuff.IsActive() {
|
|
||||||
return evt
|
|
||||||
}
|
|
||||||
x.cmdBuff.SetActive(false)
|
|
||||||
|
|
||||||
cmd := x.cmdBuff.String()
|
|
||||||
x.model.SetFilter(cmd)
|
|
||||||
x.Start()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *Xray) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (x *Xray) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if !x.cmdBuff.InCmdMode() {
|
if !x.cmdBuff.InCmdMode() {
|
||||||
x.cmdBuff.Reset()
|
x.cmdBuff.Reset()
|
||||||
|
|
@ -199,8 +437,9 @@ func (x *Xray) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if x.cmdBuff.IsActive() {
|
if x.cmdBuff.IsActive() {
|
||||||
if ui.IsLabelSelector(x.cmdBuff.String()) {
|
if ui.IsLabelSelector(x.cmdBuff.String()) {
|
||||||
x.Start()
|
x.Start()
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
x.cmdBuff.SetActive(false)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
n := x.GetCurrentNode()
|
n := x.GetCurrentNode()
|
||||||
if n == nil {
|
if n == nil {
|
||||||
|
|
@ -287,7 +526,11 @@ func (x *Xray) update(node *xray.TreeNode) {
|
||||||
x.app.QueueUpdateDraw(func() {
|
x.app.QueueUpdateDraw(func() {
|
||||||
x.SetRoot(root)
|
x.SetRoot(root)
|
||||||
root.Walk(func(node, parent *tview.TreeNode) bool {
|
root.Walk(func(node, parent *tview.TreeNode) bool {
|
||||||
ref := node.GetReference().(xray.NodeSpec)
|
ref, ok := node.GetReference().(xray.NodeSpec)
|
||||||
|
if !ok {
|
||||||
|
log.Error().Msgf("Expeting a NodeSpec but got %T", node.GetReference())
|
||||||
|
return false
|
||||||
|
}
|
||||||
// BOZO!! Figure this out expand/collapse but the root
|
// BOZO!! Figure this out expand/collapse but the root
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
node.SetExpanded(x.expandNodes)
|
node.SetExpanded(x.expandNodes)
|
||||||
|
|
@ -295,11 +538,6 @@ func (x *Xray) update(node *xray.TreeNode) {
|
||||||
node.SetExpanded(true)
|
node.SetExpanded(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
ref, ok := node.GetReference().(xray.NodeSpec)
|
|
||||||
if !ok {
|
|
||||||
log.Error().Msgf("No ref found on node %s", node.GetText())
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if ref.Path == x.selectedNode {
|
if ref.Path == x.selectedNode {
|
||||||
node.SetExpanded(true).SetSelectable(true)
|
node.SetExpanded(true).SetSelectable(true)
|
||||||
x.SetCurrentNode(node)
|
x.SetCurrentNode(node)
|
||||||
|
|
@ -312,6 +550,7 @@ func (x *Xray) update(node *xray.TreeNode) {
|
||||||
// XrayDataChanged notifies the model data changed.
|
// XrayDataChanged notifies the model data changed.
|
||||||
func (x *Xray) TreeChanged(node *xray.TreeNode) {
|
func (x *Xray) TreeChanged(node *xray.TreeNode) {
|
||||||
log.Debug().Msgf("Tree Changed %d", len(node.Children))
|
log.Debug().Msgf("Tree Changed %d", len(node.Children))
|
||||||
|
x.count = node.Count(x.gvr.String())
|
||||||
x.update(x.filter(node))
|
x.update(x.filter(node))
|
||||||
x.UpdateTitle()
|
x.UpdateTitle()
|
||||||
}
|
}
|
||||||
|
|
@ -358,7 +597,7 @@ func (x *Xray) Start() {
|
||||||
log.Debug().Msgf("XRAY STARTING! -- %q", x.selectedNode)
|
log.Debug().Msgf("XRAY STARTING! -- %q", x.selectedNode)
|
||||||
x.cmdBuff.AddListener(x.app.Cmd())
|
x.cmdBuff.AddListener(x.app.Cmd())
|
||||||
x.cmdBuff.AddListener(x)
|
x.cmdBuff.AddListener(x)
|
||||||
x.app.SetFocus(x)
|
// x.app.SetFocus(x)
|
||||||
|
|
||||||
ctx := x.defaultContext()
|
ctx := x.defaultContext()
|
||||||
ctx, x.cancelFn = context.WithCancel(ctx)
|
ctx, x.cancelFn = context.WithCancel(ctx)
|
||||||
|
|
@ -405,12 +644,7 @@ func (x *Xray) UpdateTitle() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Xray) styleTitle() string {
|
func (x *Xray) styleTitle() string {
|
||||||
rc := x.GetRowCount()
|
base := fmt.Sprintf("%s-%s", xrayTitle, strings.Title(x.gvr.ToR()))
|
||||||
if rc > 0 {
|
|
||||||
rc--
|
|
||||||
}
|
|
||||||
|
|
||||||
base := strings.Title(xrayTitle)
|
|
||||||
ns := x.model.GetNamespace()
|
ns := x.model.GetNamespace()
|
||||||
if client.IsAllNamespaces(ns) {
|
if client.IsAllNamespaces(ns) {
|
||||||
ns = client.NamespaceAll
|
ns = client.NamespaceAll
|
||||||
|
|
@ -419,9 +653,9 @@ func (x *Xray) styleTitle() string {
|
||||||
buff := x.cmdBuff.String()
|
buff := x.cmdBuff.String()
|
||||||
var title string
|
var title string
|
||||||
if ns == client.ClusterScope {
|
if ns == client.ClusterScope {
|
||||||
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, rc), x.app.Styles.Frame())
|
title = ui.SkinTitle(fmt.Sprintf(ui.TitleFmt, base, x.count), x.app.Styles.Frame())
|
||||||
} else {
|
} else {
|
||||||
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, rc), x.app.Styles.Frame())
|
title = ui.SkinTitle(fmt.Sprintf(ui.NSTitleFmt, base, ns, x.count), x.app.Styles.Frame())
|
||||||
}
|
}
|
||||||
if buff == "" {
|
if buff == "" {
|
||||||
return title
|
return title
|
||||||
|
|
@ -434,6 +668,30 @@ func (x *Xray) styleTitle() string {
|
||||||
return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), x.app.Styles.Frame())
|
return title + ui.SkinTitle(fmt.Sprintf(ui.SearchFmt, buff), x.app.Styles.Frame())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Xray) resourceDelete(gvr client.GVR, ref *xray.NodeSpec, msg string) {
|
||||||
|
dialog.ShowDelete(x.app.Content.Pages, msg, func(cascade, force bool) {
|
||||||
|
x.app.Flash().Infof("Delete resource %s %s", ref.GVR, ref.Path)
|
||||||
|
accessor, err := dao.AccessorFor(x.app.factory, gvr)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("No accessor")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
nuker, ok := accessor.(dao.Nuker)
|
||||||
|
if !ok {
|
||||||
|
x.app.Flash().Errf("Invalid nuker %T", accessor)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := nuker.Delete(ref.Path, true, true); err != nil {
|
||||||
|
x.app.Flash().Errf("Delete failed with `%s", err)
|
||||||
|
} else {
|
||||||
|
x.app.Flash().Infof("%s `%s deleted successfully", x.GVR(), ref.Path)
|
||||||
|
x.app.factory.DeleteForwarder(ref.Path)
|
||||||
|
}
|
||||||
|
x.Refresh()
|
||||||
|
}, func() {})
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Helpers...
|
// Helpers...
|
||||||
|
|
||||||
|
|
@ -448,23 +706,19 @@ func mapKey(evt *tcell.EventKey) tcell.Key {
|
||||||
func fuzzyFilter(q, path string) bool {
|
func fuzzyFilter(q, path string) bool {
|
||||||
q = strings.TrimSpace(q[2:])
|
q = strings.TrimSpace(q[2:])
|
||||||
mm := fuzzy.Find(q, []string{path})
|
mm := fuzzy.Find(q, []string{path})
|
||||||
log.Debug().Msgf("%#v", mm)
|
|
||||||
if len(mm) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
return len(mm) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func rxFilter(q, path string) bool {
|
func rxFilter(q, path string) bool {
|
||||||
rx := regexp.MustCompile(`(?i)` + q)
|
rx := regexp.MustCompile(`(?i)` + q)
|
||||||
|
|
||||||
tokens := strings.Split(path, xray.PathSeparator)
|
tokens := strings.Split(path, xray.PathSeparator)
|
||||||
for _, t := range tokens {
|
for _, t := range tokens {
|
||||||
if rx.MatchString(t) {
|
if rx.MatchString(t) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -472,7 +726,15 @@ func makeTreeNode(node *xray.TreeNode, expanded bool, styles *config.Styles) *tv
|
||||||
n := tview.NewTreeNode("No data...")
|
n := tview.NewTreeNode("No data...")
|
||||||
if node != nil {
|
if node != nil {
|
||||||
n.SetText(node.Title())
|
n.SetText(node.Title())
|
||||||
n.SetReference(xray.NodeSpec{GVR: node.GVR, Path: node.ID})
|
spec := xray.NodeSpec{}
|
||||||
|
if p := node.Parent; p != nil {
|
||||||
|
spec.GVR, spec.Path = p.GVR, p.ID
|
||||||
|
}
|
||||||
|
n.SetReference(xray.NodeSpec{
|
||||||
|
GVR: node.GVR,
|
||||||
|
Path: node.ID,
|
||||||
|
Parent: &spec,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
n.SetSelectable(true)
|
n.SetSelectable(true)
|
||||||
n.SetExpanded(expanded)
|
n.SetExpanded(expanded)
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,16 @@ func (c *Container) Render(ctx context.Context, ns string, o interface{}) error
|
||||||
}
|
}
|
||||||
|
|
||||||
root := NewTreeNode("containers", client.FQN(ns, co.Container.Name))
|
root := NewTreeNode("containers", client.FQN(ns, co.Container.Name))
|
||||||
parent := ctx.Value(KeyParent).(*TreeNode)
|
parent, ok := ctx.Value(KeyParent).(*TreeNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||||
}
|
}
|
||||||
pns, _ := client.Namespaced(parent.ID)
|
pns, _ := client.Namespaced(parent.ID)
|
||||||
c.envRefs(f, root, pns, co.Container)
|
c.envRefs(f, root, pns, co.Container)
|
||||||
if !root.Empty() {
|
if !root.IsLeaf() {
|
||||||
parent.Add(root)
|
parent.Add(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,11 +52,11 @@ func (c *Container) envRefs(f dao.Factory, parent *TreeNode, ns string, co *v1.C
|
||||||
for _, e := range co.EnvFrom {
|
for _, e := range co.EnvFrom {
|
||||||
if e.ConfigMapRef != nil {
|
if e.ConfigMapRef != nil {
|
||||||
gvr, id := "v1/configmaps", client.FQN(ns, e.ConfigMapRef.Name)
|
gvr, id := "v1/configmaps", client.FQN(ns, e.ConfigMapRef.Name)
|
||||||
c.addRef(f, parent, gvr, id, e.ConfigMapRef.Optional)
|
addRef(f, parent, gvr, id, e.ConfigMapRef.Optional)
|
||||||
}
|
}
|
||||||
if e.SecretRef != nil {
|
if e.SecretRef != nil {
|
||||||
gvr, id := "v1/secrets", client.FQN(ns, e.SecretRef.Name)
|
gvr, id := "v1/secrets", client.FQN(ns, e.SecretRef.Name)
|
||||||
c.addRef(f, parent, gvr, id, e.SecretRef.Optional)
|
addRef(f, parent, gvr, id, e.SecretRef.Optional)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -65,7 +66,7 @@ func (c *Container) secretRefs(f dao.Factory, parent *TreeNode, ns string, ref *
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gvr, id := "v1/secrets", client.FQN(ns, ref.LocalObjectReference.Name)
|
gvr, id := "v1/secrets", client.FQN(ns, ref.LocalObjectReference.Name)
|
||||||
c.addRef(f, parent, id, gvr, ref.Optional)
|
addRef(f, parent, id, gvr, ref.Optional)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) configMapRefs(f dao.Factory, parent *TreeNode, ns string, ref *v1.ConfigMapKeySelector) {
|
func (c *Container) configMapRefs(f dao.Factory, parent *TreeNode, ns string, ref *v1.ConfigMapKeySelector) {
|
||||||
|
|
@ -73,10 +74,13 @@ func (c *Container) configMapRefs(f dao.Factory, parent *TreeNode, ns string, re
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gvr, id := "v1/configmaps", client.FQN(ns, ref.LocalObjectReference.Name)
|
gvr, id := "v1/configmaps", client.FQN(ns, ref.LocalObjectReference.Name)
|
||||||
c.addRef(f, parent, gvr, id, ref.Optional)
|
addRef(f, parent, gvr, id, ref.Optional)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) addRef(f dao.Factory, parent *TreeNode, gvr, id string, optional *bool) {
|
// ----------------------------------------------------------------------------
|
||||||
|
// Helpers...
|
||||||
|
|
||||||
|
func addRef(f dao.Factory, parent *TreeNode, gvr, id string, optional *bool) {
|
||||||
if parent.Find(gvr, id) == nil {
|
if parent.Find(gvr, id) == nil {
|
||||||
n := NewTreeNode(gvr, id)
|
n := NewTreeNode(gvr, id)
|
||||||
validate(f, n, optional)
|
validate(f, n, optional)
|
||||||
|
|
@ -84,13 +88,7 @@ func (c *Container) addRef(f dao.Factory, parent *TreeNode, gvr, id string, opti
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helpers...
|
func validate(f dao.Factory, n *TreeNode, _ *bool) {
|
||||||
|
|
||||||
func validate(f dao.Factory, n *TreeNode, optional *bool) {
|
|
||||||
if optional == nil || *optional {
|
|
||||||
n.Extras[StatusKey] = OkStatus
|
|
||||||
return
|
|
||||||
}
|
|
||||||
res, err := f.Get(n.GVR, n.ID, false, labels.Everything())
|
res, err := f.Get(n.GVR, n.ID, false, labels.Everything())
|
||||||
if err != nil || res == nil {
|
if err != nil || res == nil {
|
||||||
log.Debug().Msgf("Fail to located ref %q::%q -- %#v-%#v", n.GVR, n.ID, err, res)
|
log.Debug().Msgf("Fail to located ref %q::%q -- %#v-%#v", n.GVR, n.ID, err, res)
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ func TestCORefs(t *testing.T) {
|
||||||
co: render.ContainerRes{Container: makeCMContainer("c1", true)},
|
co: render.ContainerRes{Container: makeCMContainer("c1", true)},
|
||||||
level1: 1,
|
level1: 1,
|
||||||
level2: 1,
|
level2: 1,
|
||||||
e: xray.OkStatus,
|
e: xray.MissingRefStatus,
|
||||||
},
|
},
|
||||||
"cm_doubleRef": {
|
"cm_doubleRef": {
|
||||||
co: render.ContainerRes{Container: makeDoubleCMKeysContainer("c1", false)},
|
co: render.ContainerRes{Container: makeDoubleCMKeysContainer("c1", false)},
|
||||||
|
|
@ -72,7 +72,7 @@ func TestCORefs(t *testing.T) {
|
||||||
co: render.ContainerRes{Container: makeSecContainer("c1", true)},
|
co: render.ContainerRes{Container: makeSecContainer("c1", true)},
|
||||||
level1: 1,
|
level1: 1,
|
||||||
level2: 1,
|
level2: 1,
|
||||||
e: xray.OkStatus,
|
e: xray.MissingRefStatus,
|
||||||
},
|
},
|
||||||
"envFrom_optional": {
|
"envFrom_optional": {
|
||||||
co: render.ContainerRes{Container: makeCMEnvFromContainer("c1", false)},
|
co: render.ContainerRes{Container: makeCMEnvFromContainer("c1", false)},
|
||||||
|
|
@ -91,8 +91,8 @@ func TestCORefs(t *testing.T) {
|
||||||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
||||||
|
|
||||||
assert.Nil(t, re.Render(ctx, "", u.co))
|
assert.Nil(t, re.Render(ctx, "", u.co))
|
||||||
assert.Equal(t, u.level1, root.Size())
|
assert.Equal(t, u.level1, root.CountChildren())
|
||||||
assert.Equal(t, u.level2, root.Children[0].Size())
|
assert.Equal(t, u.level2, root.Children[0].CountChildren())
|
||||||
assert.Equal(t, u.e, root.Children[0].Children[0].Extras[xray.StatusKey])
|
assert.Equal(t, u.e, root.Children[0].Children[0].Extras[xray.StatusKey])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,7 @@ func (d *Deployment) Render(ctx context.Context, ns string, o interface{}) error
|
||||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||||
}
|
}
|
||||||
|
|
||||||
nsID, gvr := client.FQN(client.ClusterScope, dp.Namespace), "v1/namespaces"
|
|
||||||
nsn := parent.Find(gvr, nsID)
|
|
||||||
if nsn == nil {
|
|
||||||
nsn = NewTreeNode(gvr, nsID)
|
|
||||||
parent.Add(nsn)
|
|
||||||
}
|
|
||||||
root := NewTreeNode("apps/v1/deployments", client.FQN(dp.Namespace, dp.Name))
|
root := NewTreeNode("apps/v1/deployments", client.FQN(dp.Namespace, dp.Name))
|
||||||
nsn.Add(root)
|
|
||||||
|
|
||||||
oo, err := locatePods(ctx, dp.Namespace, dp.Spec.Selector)
|
oo, err := locatePods(ctx, dp.Namespace, dp.Spec.Selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -50,12 +42,26 @@ func (d *Deployment) Render(ctx context.Context, ns string, o interface{}) error
|
||||||
ctx = context.WithValue(ctx, KeyParent, root)
|
ctx = context.WithValue(ctx, KeyParent, root)
|
||||||
var re Pod
|
var re Pod
|
||||||
for _, o := range oo {
|
for _, o := range oo {
|
||||||
p := o.(*unstructured.Unstructured)
|
p, ok := o.(*unstructured.Unstructured)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expecting *Unstructured but got %T", o)
|
||||||
|
}
|
||||||
if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
|
if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if root.IsLeaf() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, dp.Namespace)
|
||||||
|
nsn := parent.Find(gvr, nsID)
|
||||||
|
if nsn == nil {
|
||||||
|
nsn = NewTreeNode(gvr, nsID)
|
||||||
|
parent.Add(nsn)
|
||||||
|
}
|
||||||
|
nsn.Add(root)
|
||||||
|
|
||||||
return d.validate(root, dp)
|
return d.validate(root, dp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/xray"
|
"github.com/derailed/k9s/internal/xray"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDeployRender(t *testing.T) {
|
func TestDeployRender(t *testing.T) {
|
||||||
|
|
@ -25,16 +26,19 @@ func TestDeployRender(t *testing.T) {
|
||||||
|
|
||||||
var re xray.Deployment
|
var re xray.Deployment
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
|
f := makeFactory()
|
||||||
|
f.rows = []runtime.Object{load(t, "po")}
|
||||||
|
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
o := load(t, u.file)
|
o := load(t, u.file)
|
||||||
root := xray.NewTreeNode("deployments", "deployments")
|
root := xray.NewTreeNode("deployments", "deployments")
|
||||||
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
|
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
|
||||||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
ctx = context.WithValue(ctx, internal.KeyFactory, f)
|
||||||
|
|
||||||
assert.Nil(t, re.Render(ctx, "", o))
|
assert.Nil(t, re.Render(ctx, "", o))
|
||||||
assert.Equal(t, u.level1, root.Size())
|
assert.Equal(t, u.level1, root.CountChildren())
|
||||||
assert.Equal(t, u.level2, root.Children[0].Size())
|
assert.Equal(t, u.level2, root.Children[0].CountChildren())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,29 +29,34 @@ func (d *DaemonSet) Render(ctx context.Context, ns string, o interface{}) error
|
||||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||||
}
|
}
|
||||||
|
|
||||||
nsID, gvr := client.FQN(client.ClusterScope, ds.Namespace), "v1/namespaces"
|
root := NewTreeNode("apps/v1/daemonsets", client.FQN(ds.Namespace, ds.Name))
|
||||||
|
oo, err := locatePods(ctx, ds.Namespace, ds.Spec.Selector)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx = context.WithValue(ctx, KeyParent, root)
|
||||||
|
var re Pod
|
||||||
|
for _, o := range oo {
|
||||||
|
p, ok := o.(*unstructured.Unstructured)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expecting *Unstructured but got %T", o)
|
||||||
|
}
|
||||||
|
if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if root.IsLeaf() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, ds.Namespace)
|
||||||
nsn := parent.Find(gvr, nsID)
|
nsn := parent.Find(gvr, nsID)
|
||||||
if nsn == nil {
|
if nsn == nil {
|
||||||
nsn = NewTreeNode(gvr, nsID)
|
nsn = NewTreeNode(gvr, nsID)
|
||||||
parent.Add(nsn)
|
parent.Add(nsn)
|
||||||
}
|
}
|
||||||
root := NewTreeNode("apps/v1/daemonset", client.FQN(ds.Namespace, ds.Name))
|
|
||||||
nsn.Add(root)
|
nsn.Add(root)
|
||||||
|
|
||||||
oo, err := locatePods(ctx, ds.Namespace, ds.Spec.Selector)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, KeyParent, root)
|
|
||||||
var re Pod
|
|
||||||
for _, o := range oo {
|
|
||||||
p := o.(*unstructured.Unstructured)
|
|
||||||
if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.validate(root, ds)
|
return d.validate(root, ds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/xray"
|
"github.com/derailed/k9s/internal/xray"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDaemonSetRender(t *testing.T) {
|
func TestDaemonSetRender(t *testing.T) {
|
||||||
|
|
@ -25,16 +26,18 @@ func TestDaemonSetRender(t *testing.T) {
|
||||||
|
|
||||||
var re xray.DaemonSet
|
var re xray.DaemonSet
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
|
f := makeFactory()
|
||||||
|
f.rows = []runtime.Object{load(t, "po")}
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
o := load(t, u.file)
|
o := load(t, u.file)
|
||||||
root := xray.NewTreeNode("daemonsets", "daemonsets")
|
root := xray.NewTreeNode("daemonsets", "daemonsets")
|
||||||
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
|
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
|
||||||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
ctx = context.WithValue(ctx, internal.KeyFactory, f)
|
||||||
|
|
||||||
assert.Nil(t, re.Render(ctx, "", o))
|
assert.Nil(t, re.Render(ctx, "", o))
|
||||||
assert.Equal(t, u.level1, root.Size())
|
assert.Equal(t, u.level1, root.CountChildren())
|
||||||
assert.Equal(t, u.level2, root.Children[0].Size())
|
assert.Equal(t, u.level2, root.Children[0].CountChildren())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,6 @@ package xray
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
|
@ -33,35 +31,11 @@ func (g *Generic) Render(ctx context.Context, ns string, o interface{}) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
root := NewTreeNode("generic", client.FQN(ns, n))
|
root := NewTreeNode("generic", client.FQN(ns, n))
|
||||||
parent := ctx.Value(KeyParent).(*TreeNode)
|
parent, ok := ctx.Value(KeyParent).(*TreeNode)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expecting TreeNode but got %T", ctx.Value(KeyParent))
|
||||||
|
}
|
||||||
parent.Add(root)
|
parent.Add(root)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Helpers...
|
|
||||||
|
|
||||||
func resourceNS(raw []byte) (bool, string, error) {
|
|
||||||
var obj map[string]interface{}
|
|
||||||
err := json.Unmarshal(raw, &obj)
|
|
||||||
if err != nil {
|
|
||||||
return false, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
meta, ok := obj["metadata"].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return false, "", errors.New("no metadata found on generic resource")
|
|
||||||
}
|
|
||||||
|
|
||||||
ns, ok := meta["namespace"]
|
|
||||||
if !ok {
|
|
||||||
return true, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
nns, ok := ns.(string)
|
|
||||||
if !ok {
|
|
||||||
return false, "", fmt.Errorf("expecting namespace string type but got %T", ns)
|
|
||||||
}
|
|
||||||
return false, nns, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ func TestGenericRender(t *testing.T) {
|
||||||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
||||||
|
|
||||||
assert.Nil(t, re.Render(ctx, "", makeTable()))
|
assert.Nil(t, re.Render(ctx, "", makeTable()))
|
||||||
assert.Equal(t, u.level1, root.Size())
|
assert.Equal(t, u.level1, root.CountChildren())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
|
|
||||||
type Namespace struct{}
|
type Namespace struct{}
|
||||||
|
|
||||||
func (p *Namespace) Render(ctx context.Context, ns string, o interface{}) error {
|
func (n *Namespace) Render(ctx context.Context, ns string, o interface{}) error {
|
||||||
raw, ok := o.(*unstructured.Unstructured)
|
raw, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected NamespaceWithMetrics, but got %T", o)
|
return fmt.Errorf("Expected NamespaceWithMetrics, but got %T", o)
|
||||||
|
|
@ -31,5 +31,14 @@ func (p *Namespace) Render(ctx context.Context, ns string, o interface{}) error
|
||||||
}
|
}
|
||||||
parent.Add(root)
|
parent.Add(root)
|
||||||
|
|
||||||
|
return n.validate(root, nss)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Namespace) validate(root *TreeNode, ns v1.Namespace) error {
|
||||||
|
root.Extras[StatusKey] = OkStatus
|
||||||
|
if ns.Status.Phase == v1.NamespaceTerminating {
|
||||||
|
root.Extras[StatusKey] = ToastStatus
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ func TestNamespaceRender(t *testing.T) {
|
||||||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
||||||
|
|
||||||
assert.Nil(t, re.Render(ctx, "", o))
|
assert.Nil(t, re.Render(ctx, "", o))
|
||||||
assert.Equal(t, u.level1, root.Size())
|
assert.Equal(t, u.level1, root.CountChildren())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
|
"github.com/derailed/k9s/internal/dao"
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
|
@ -30,6 +32,11 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("no factory found in context")
|
||||||
|
}
|
||||||
|
|
||||||
phase := p.phase(&po)
|
phase := p.phase(&po)
|
||||||
ss := po.Status.ContainerStatuses
|
ss := po.Status.ContainerStatuses
|
||||||
cr, _, _ := p.statuses(ss)
|
cr, _, _ := p.statuses(ss)
|
||||||
|
|
@ -41,16 +48,16 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
|
||||||
status = CompletedStatus
|
status = CompletedStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
root := NewTreeNode("v1/pods", client.FQN(po.Namespace, po.Name))
|
node := NewTreeNode("v1/pods", client.FQN(po.Namespace, po.Name))
|
||||||
root.Extras[StatusKey] = status
|
node.Extras[StatusKey] = status
|
||||||
root.Extras[StateKey] = strconv.Itoa(cr) + "/" + strconv.Itoa(len(ss))
|
node.Extras[StateKey] = strconv.Itoa(cr) + "/" + strconv.Itoa(len(ss))
|
||||||
parent, ok := ctx.Value(KeyParent).(*TreeNode)
|
parent, ok := ctx.Value(KeyParent).(*TreeNode)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||||
}
|
}
|
||||||
parent.Add(root)
|
parent.Add(node)
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, KeyParent, root)
|
ctx = context.WithValue(ctx, KeyParent, node)
|
||||||
var cre Container
|
var cre Container
|
||||||
for i := 0; i < len(po.Spec.InitContainers); i++ {
|
for i := 0; i < len(po.Spec.InitContainers); i++ {
|
||||||
if err := cre.Render(ctx, ns, render.ContainerRes{Container: &po.Spec.InitContainers[i]}); err != nil {
|
if err := cre.Render(ctx, ns, render.ContainerRes{Container: &po.Spec.InitContainers[i]}); err != nil {
|
||||||
|
|
@ -62,22 +69,28 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.podVolumeRefs(root, po.Namespace, po.Spec.Volumes)
|
p.podVolumeRefs(f, node, po.Namespace, po.Spec.Volumes)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*Pod) podVolumeRefs(parent *TreeNode, ns string, vv []v1.Volume) {
|
func (*Pod) podVolumeRefs(f dao.Factory, parent *TreeNode, ns string, vv []v1.Volume) {
|
||||||
for _, v := range vv {
|
for _, v := range vv {
|
||||||
sv := v.VolumeSource.Secret
|
sec := v.VolumeSource.Secret
|
||||||
if sv != nil {
|
if sec != nil {
|
||||||
parent.Add(NewTreeNode("v1/secrets", client.FQN(ns, sv.SecretName)))
|
addRef(f, parent, "v1/secrets", client.FQN(ns, sec.SecretName), nil)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cmv := v.VolumeSource.ConfigMap
|
cm := v.VolumeSource.ConfigMap
|
||||||
if cmv != nil {
|
if cm != nil {
|
||||||
parent.Add(NewTreeNode("v1/configmaps", client.FQN(ns, cmv.LocalObjectReference.Name)))
|
addRef(f, parent, "v1/configmaps", client.FQN(ns, cm.LocalObjectReference.Name), nil)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pvc := v.VolumeSource.PersistentVolumeClaim
|
||||||
|
if pvc != nil {
|
||||||
|
addRef(f, parent, "v1/persistentvolumeclaims", client.FQN(ns, pvc.ClaimName), nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
"github.com/derailed/k9s/internal/xray"
|
"github.com/derailed/k9s/internal/xray"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
v1 "k8s.io/api/core/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPodRender(t *testing.T) {
|
func TestPodRender(t *testing.T) {
|
||||||
|
|
@ -21,7 +19,7 @@ func TestPodRender(t *testing.T) {
|
||||||
"plain": {
|
"plain": {
|
||||||
file: "po",
|
file: "po",
|
||||||
level1: 1,
|
level1: 1,
|
||||||
level2: 1,
|
level2: 2,
|
||||||
status: xray.OkStatus,
|
status: xray.OkStatus,
|
||||||
},
|
},
|
||||||
"withInit": {
|
"withInit": {
|
||||||
|
|
@ -42,153 +40,8 @@ func TestPodRender(t *testing.T) {
|
||||||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
||||||
|
|
||||||
assert.Nil(t, re.Render(ctx, "", &render.PodWithMetrics{Raw: o}))
|
assert.Nil(t, re.Render(ctx, "", &render.PodWithMetrics{Raw: o}))
|
||||||
assert.Equal(t, u.level1, root.Size())
|
assert.Equal(t, u.level1, root.CountChildren())
|
||||||
assert.Equal(t, u.level2, root.Children[0].Size())
|
assert.Equal(t, u.level2, root.Children[0].CountChildren())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Helpers...
|
|
||||||
|
|
||||||
func makePod(n string) v1.Pod {
|
|
||||||
return v1.Pod{
|
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
|
||||||
Name: n,
|
|
||||||
Namespace: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func makePodEnv(n, ref string, optional bool) v1.Pod {
|
|
||||||
po := makePod(n)
|
|
||||||
po.Spec.Containers = []v1.Container{
|
|
||||||
{
|
|
||||||
Name: "c1",
|
|
||||||
Env: []v1.EnvVar{
|
|
||||||
{
|
|
||||||
Name: "e1",
|
|
||||||
ValueFrom: &v1.EnvVarSource{
|
|
||||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
|
||||||
Name: "cm1",
|
|
||||||
},
|
|
||||||
Key: "k1",
|
|
||||||
Optional: &optional,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "c2",
|
|
||||||
Env: []v1.EnvVar{
|
|
||||||
{
|
|
||||||
Name: "e2",
|
|
||||||
ValueFrom: &v1.EnvVarSource{
|
|
||||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
|
||||||
Name: "cm2",
|
|
||||||
},
|
|
||||||
Key: "k2",
|
|
||||||
Optional: &optional,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
po.Spec.InitContainers = []v1.Container{
|
|
||||||
{
|
|
||||||
Name: "ic1",
|
|
||||||
Env: []v1.EnvVar{
|
|
||||||
{
|
|
||||||
Name: "e1",
|
|
||||||
ValueFrom: &v1.EnvVarSource{
|
|
||||||
SecretKeyRef: &v1.SecretKeySelector{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{Name: "sec2"},
|
|
||||||
Key: "k2",
|
|
||||||
Optional: &optional,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return po
|
|
||||||
}
|
|
||||||
|
|
||||||
func makePodStatus(n, ref string, optional bool) v1.Pod {
|
|
||||||
po := makePod(n)
|
|
||||||
po.Status = v1.PodStatus{
|
|
||||||
Phase: v1.PodRunning,
|
|
||||||
Conditions: []v1.PodCondition{
|
|
||||||
{
|
|
||||||
Type: v1.PodReady,
|
|
||||||
Status: v1.ConditionTrue,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
ContainerStatuses: []v1.ContainerStatus{
|
|
||||||
{
|
|
||||||
Name: "c1",
|
|
||||||
State: v1.ContainerState{Running: &v1.ContainerStateRunning{}},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
po.Spec.Containers = []v1.Container{
|
|
||||||
{
|
|
||||||
Name: "c1",
|
|
||||||
Env: []v1.EnvVar{
|
|
||||||
{
|
|
||||||
Name: "e1",
|
|
||||||
ValueFrom: &v1.EnvVarSource{
|
|
||||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
|
||||||
Name: "cm1",
|
|
||||||
},
|
|
||||||
Key: "k1",
|
|
||||||
Optional: &optional,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "c2",
|
|
||||||
Env: []v1.EnvVar{
|
|
||||||
{
|
|
||||||
Name: "e2",
|
|
||||||
ValueFrom: &v1.EnvVarSource{
|
|
||||||
ConfigMapKeyRef: &v1.ConfigMapKeySelector{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{
|
|
||||||
Name: "cm2",
|
|
||||||
},
|
|
||||||
Key: "k2",
|
|
||||||
Optional: &optional,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
po.Spec.InitContainers = []v1.Container{
|
|
||||||
{
|
|
||||||
Name: "ic1",
|
|
||||||
Env: []v1.EnvVar{
|
|
||||||
{
|
|
||||||
Name: "e1",
|
|
||||||
ValueFrom: &v1.EnvVarSource{
|
|
||||||
SecretKeyRef: &v1.SecretKeySelector{
|
|
||||||
LocalObjectReference: v1.LocalObjectReference{Name: "sec2"},
|
|
||||||
Key: "k2",
|
|
||||||
Optional: &optional,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return po
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,26 +4,20 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal"
|
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/client"
|
"github.com/derailed/k9s/internal/client"
|
||||||
"github.com/derailed/k9s/internal/dao"
|
|
||||||
"github.com/derailed/k9s/internal/render"
|
"github.com/derailed/k9s/internal/render"
|
||||||
appsv1 "k8s.io/api/apps/v1"
|
appsv1 "k8s.io/api/apps/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/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatefulSet struct{}
|
type StatefulSet struct{}
|
||||||
|
|
||||||
func (p *StatefulSet) Render(ctx context.Context, ns string, o interface{}) error {
|
func (s *StatefulSet) Render(ctx context.Context, ns string, o interface{}) error {
|
||||||
raw, ok := o.(*unstructured.Unstructured)
|
raw, ok := o.(*unstructured.Unstructured)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Expected Unstructured, but got %T", o)
|
return fmt.Errorf("Expected Unstructured, but got %T", o)
|
||||||
}
|
}
|
||||||
|
|
||||||
var sts appsv1.StatefulSet
|
var sts appsv1.StatefulSet
|
||||||
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sts)
|
err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &sts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -35,31 +29,8 @@ func (p *StatefulSet) Render(ctx context.Context, ns string, o interface{}) erro
|
||||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||||
}
|
}
|
||||||
|
|
||||||
nsID, gvr := client.FQN(client.ClusterScope, sts.Namespace), "v1/namespaces"
|
root := NewTreeNode("apps/v1/statefulsets", client.FQN(sts.Namespace, sts.Name))
|
||||||
nsn := parent.Find(gvr, nsID)
|
oo, err := locatePods(ctx, sts.Namespace, sts.Spec.Selector)
|
||||||
if nsn == nil {
|
|
||||||
nsn = NewTreeNode(gvr, nsID)
|
|
||||||
parent.Add(nsn)
|
|
||||||
}
|
|
||||||
root := NewTreeNode("apps/v1/deployments", client.FQN(sts.Namespace, sts.Name))
|
|
||||||
nsn.Add(root)
|
|
||||||
|
|
||||||
l, err := metav1.LabelSelectorAsSelector(sts.Spec.Selector)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, ok := ctx.Value(internal.KeyFactory).(dao.Factory)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("Expecting a factory but got %T", ctx.Value(internal.KeyFactory))
|
|
||||||
}
|
|
||||||
|
|
||||||
fsel, err := labels.ConvertSelectorToLabelsMap(l.String())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
oo, err := f.List("v1/pods", sts.Namespace, false, fsel.AsSelector())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -67,12 +38,31 @@ func (p *StatefulSet) Render(ctx context.Context, ns string, o interface{}) erro
|
||||||
ctx = context.WithValue(ctx, KeyParent, root)
|
ctx = context.WithValue(ctx, KeyParent, root)
|
||||||
var re Pod
|
var re Pod
|
||||||
for _, o := range oo {
|
for _, o := range oo {
|
||||||
p := o.(*unstructured.Unstructured)
|
p, ok := o.(*unstructured.Unstructured)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expecting *Unstructured but got %T", o)
|
||||||
|
}
|
||||||
if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
|
if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if root.IsLeaf() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, sts.Namespace)
|
||||||
|
nsn := parent.Find(gvr, nsID)
|
||||||
|
if nsn == nil {
|
||||||
|
nsn = NewTreeNode(gvr, nsID)
|
||||||
|
parent.Add(nsn)
|
||||||
|
}
|
||||||
|
nsn.Add(root)
|
||||||
|
|
||||||
|
return s.validate(root, sts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*StatefulSet) validate(root *TreeNode, sts appsv1.StatefulSet) error {
|
||||||
root.Extras[StatusKey] = OkStatus
|
root.Extras[StatusKey] = OkStatus
|
||||||
var r int32
|
var r int32
|
||||||
if sts.Spec.Replicas != nil {
|
if sts.Spec.Replicas != nil {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/xray"
|
"github.com/derailed/k9s/internal/xray"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestStatefulSetRender(t *testing.T) {
|
func TestStatefulSetRender(t *testing.T) {
|
||||||
|
|
@ -27,14 +28,17 @@ func TestStatefulSetRender(t *testing.T) {
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
|
f := makeFactory()
|
||||||
|
f.rows = []runtime.Object{load(t, "po")}
|
||||||
|
|
||||||
o := load(t, u.file)
|
o := load(t, u.file)
|
||||||
root := xray.NewTreeNode("statefulsets", "statefulsets")
|
root := xray.NewTreeNode("statefulsets", "statefulsets")
|
||||||
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
|
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
|
||||||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
ctx = context.WithValue(ctx, internal.KeyFactory, f)
|
||||||
|
|
||||||
assert.Nil(t, re.Render(ctx, "", o))
|
assert.Nil(t, re.Render(ctx, "", o))
|
||||||
assert.Equal(t, u.level1, root.Size())
|
assert.Equal(t, u.level1, root.CountChildren())
|
||||||
assert.Equal(t, u.level2, root.Children[0].Size())
|
assert.Equal(t, u.level2, root.Children[0].CountChildren())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,30 +35,35 @@ func (s *Service) Render(ctx context.Context, ns string, o interface{}) error {
|
||||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||||
}
|
}
|
||||||
|
|
||||||
nsID, gvr := client.FQN(client.ClusterScope, svc.Namespace), "v1/namespaces"
|
|
||||||
nsn := parent.Find(gvr, nsID)
|
|
||||||
if nsn == nil {
|
|
||||||
nsn = NewTreeNode(gvr, nsID)
|
|
||||||
parent.Add(nsn)
|
|
||||||
}
|
|
||||||
root := NewTreeNode("apps/v1/services", client.FQN(svc.Namespace, svc.Name))
|
root := NewTreeNode("apps/v1/services", client.FQN(svc.Namespace, svc.Name))
|
||||||
nsn.Add(root)
|
|
||||||
|
|
||||||
oo, err := s.locatePods(ctx, svc.Namespace, svc.Spec.Selector)
|
oo, err := s.locatePods(ctx, svc.Namespace, svc.Spec.Selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, KeyParent, root)
|
ctx = context.WithValue(ctx, KeyParent, root)
|
||||||
var re Pod
|
var re Pod
|
||||||
for _, o := range oo {
|
for _, o := range oo {
|
||||||
p := o.(*unstructured.Unstructured)
|
p, ok := o.(*unstructured.Unstructured)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("expecting *Unstructured but got %T", o)
|
||||||
|
}
|
||||||
if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
|
if err := re.Render(ctx, ns, &render.PodWithMetrics{Raw: p}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root.Extras[StatusKey] = OkStatus
|
root.Extras[StatusKey] = OkStatus
|
||||||
|
|
||||||
|
if root.IsLeaf() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, svc.Namespace)
|
||||||
|
nsn := parent.Find(gvr, nsID)
|
||||||
|
if nsn == nil {
|
||||||
|
nsn = NewTreeNode(gvr, nsID)
|
||||||
|
parent.Add(nsn)
|
||||||
|
}
|
||||||
|
nsn.Add(root)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/derailed/k9s/internal"
|
"github.com/derailed/k9s/internal"
|
||||||
"github.com/derailed/k9s/internal/xray"
|
"github.com/derailed/k9s/internal/xray"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestServiceRender(t *testing.T) {
|
func TestServiceRender(t *testing.T) {
|
||||||
|
|
@ -25,16 +26,19 @@ func TestServiceRender(t *testing.T) {
|
||||||
|
|
||||||
var re xray.Service
|
var re xray.Service
|
||||||
for k := range uu {
|
for k := range uu {
|
||||||
|
f := makeFactory()
|
||||||
|
f.rows = []runtime.Object{load(t, "po")}
|
||||||
|
|
||||||
u := uu[k]
|
u := uu[k]
|
||||||
t.Run(k, func(t *testing.T) {
|
t.Run(k, func(t *testing.T) {
|
||||||
o := load(t, u.file)
|
o := load(t, u.file)
|
||||||
root := xray.NewTreeNode("services", "services")
|
root := xray.NewTreeNode("services", "services")
|
||||||
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
|
ctx := context.WithValue(context.Background(), xray.KeyParent, root)
|
||||||
ctx = context.WithValue(ctx, internal.KeyFactory, makeFactory())
|
ctx = context.WithValue(ctx, internal.KeyFactory, f)
|
||||||
|
|
||||||
assert.Nil(t, re.Render(ctx, "", o))
|
assert.Nil(t, re.Render(ctx, "", o))
|
||||||
assert.Equal(t, u.level1, root.Size())
|
assert.Equal(t, u.level1, root.CountChildren())
|
||||||
assert.Equal(t, u.level2, root.Children[0].Size())
|
assert.Equal(t, u.level2, root.Children[0].CountChildren())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,11 @@ import (
|
||||||
"vbom.ml/util/sortorder"
|
"vbom.ml/util/sortorder"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TreeRef namespaces tree context values.
|
|
||||||
type TreeRef string
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// KeyParent indicates a parent node context key.
|
// KeyParent indicates a parent node context key.
|
||||||
KeyParent TreeRef = "parent"
|
KeyParent TreeRef = "parent"
|
||||||
|
|
||||||
// PathSeparator represents a node path separator.
|
// PathSeparator represents a node path separatot.
|
||||||
PathSeparator = "::"
|
PathSeparator = "::"
|
||||||
|
|
||||||
// StatusKey status map key.
|
// StatusKey status map key.
|
||||||
|
|
@ -41,6 +38,22 @@ const (
|
||||||
MissingRefStatus = "noref"
|
MissingRefStatus = "noref"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// TreeRef namespaces tree context values.
|
||||||
|
type TreeRef string
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// NodeSpec represents a node resource specification.
|
||||||
|
type NodeSpec struct {
|
||||||
|
GVR, Path, Status string
|
||||||
|
Parent *NodeSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Childrens represents a collection of children nodes.
|
||||||
type Childrens []*TreeNode
|
type Childrens []*TreeNode
|
||||||
|
|
||||||
// Len returns the list size.
|
// Len returns the list size.
|
||||||
|
|
@ -60,6 +73,9 @@ func (c Childrens) Less(i, j int) bool {
|
||||||
return sortorder.NaturalLess(id1, id2)
|
return sortorder.NaturalLess(id1, id2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// TreeNode represents a resource tree node.
|
||||||
type TreeNode struct {
|
type TreeNode struct {
|
||||||
GVR, ID string
|
GVR, ID string
|
||||||
Children Childrens
|
Children Childrens
|
||||||
|
|
@ -67,31 +83,39 @@ type TreeNode struct {
|
||||||
Extras map[string]string
|
Extras map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewTreeNode returns a new instance.
|
||||||
func NewTreeNode(gvr, id string) *TreeNode {
|
func NewTreeNode(gvr, id string) *TreeNode {
|
||||||
return &TreeNode{
|
return &TreeNode{
|
||||||
GVR: gvr,
|
GVR: gvr,
|
||||||
ID: id,
|
ID: id,
|
||||||
Extras: make(map[string]string),
|
Extras: map[string]string{StatusKey: OkStatus},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeNode) Size() int {
|
// CountChildren returns the children count.
|
||||||
|
func (t *TreeNode) CountChildren() int {
|
||||||
return len(t.Children)
|
return len(t.Children)
|
||||||
}
|
}
|
||||||
|
|
||||||
func count(t *TreeNode, counter int) int {
|
// Count all the nodes from this node
|
||||||
|
func (t *TreeNode) Count(gvr string) int {
|
||||||
|
counter := 0
|
||||||
|
if t.GVR == gvr || gvr == "" {
|
||||||
|
counter++
|
||||||
|
}
|
||||||
for _, c := range t.Children {
|
for _, c := range t.Children {
|
||||||
counter += count(c, counter)
|
counter += c.Count(gvr)
|
||||||
}
|
}
|
||||||
return counter
|
return counter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Diff computes a tree diff.
|
||||||
func (t *TreeNode) Diff(d *TreeNode) bool {
|
func (t *TreeNode) Diff(d *TreeNode) bool {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return d != nil
|
return d != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if t.Size() != d.Size() {
|
if t.CountChildren() != d.CountChildren() {
|
||||||
log.Debug().Msgf("SIZE-DIFF")
|
log.Debug().Msgf("SIZE-DIFF")
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -109,36 +133,33 @@ func (t *TreeNode) Diff(d *TreeNode) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sort sorts the tree nodes.
|
||||||
func (t *TreeNode) Sort() {
|
func (t *TreeNode) Sort() {
|
||||||
sortChildren(t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortChildren(t *TreeNode) {
|
|
||||||
sort.Sort(t.Children)
|
sort.Sort(t.Children)
|
||||||
for _, c := range t.Children {
|
for _, c := range t.Children {
|
||||||
sortChildren(c)
|
c.Sort()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeSpec struct {
|
// Spec returns this node specification.
|
||||||
GVR, Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TreeNode) Spec() NodeSpec {
|
func (t *TreeNode) Spec() NodeSpec {
|
||||||
parent := t
|
parent := t
|
||||||
var gvr, path []string
|
var gvr, path, status []string
|
||||||
for parent != nil {
|
for parent != nil {
|
||||||
gvr = append(gvr, parent.GVR)
|
gvr = append(gvr, parent.GVR)
|
||||||
path = append(path, parent.ID)
|
path = append(path, parent.ID)
|
||||||
|
status = append(status, parent.Extras[StatusKey])
|
||||||
parent = parent.Parent
|
parent = parent.Parent
|
||||||
}
|
}
|
||||||
|
|
||||||
return NodeSpec{
|
return NodeSpec{
|
||||||
GVR: strings.Join(gvr, PathSeparator),
|
GVR: strings.Join(gvr, PathSeparator),
|
||||||
Path: strings.Join(path, PathSeparator),
|
Path: strings.Join(path, PathSeparator),
|
||||||
|
Status: strings.Join(status, PathSeparator),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Flatten returns a collection of node specs.
|
||||||
func (t *TreeNode) Flatten() []NodeSpec {
|
func (t *TreeNode) Flatten() []NodeSpec {
|
||||||
var refs []NodeSpec
|
var refs []NodeSpec
|
||||||
for _, c := range t.Children {
|
for _, c := range t.Children {
|
||||||
|
|
@ -151,23 +172,27 @@ func (t *TreeNode) Flatten() []NodeSpec {
|
||||||
return refs
|
return refs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blank returns true if this node is unset.
|
||||||
func (t *TreeNode) Blank() bool {
|
func (t *TreeNode) Blank() bool {
|
||||||
return t.GVR == "" && t.ID == ""
|
return t.GVR == "" && t.ID == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hydrate hydrates a full tree bases on a collection of specifications.
|
||||||
func Hydrate(refs []NodeSpec) *TreeNode {
|
func Hydrate(refs []NodeSpec) *TreeNode {
|
||||||
root := NewTreeNode("", "")
|
root := NewTreeNode("", "")
|
||||||
nav := root
|
nav := root
|
||||||
for _, ref := range refs {
|
for _, ref := range refs {
|
||||||
ids := strings.Split(ref.Path, PathSeparator)
|
|
||||||
gvrs := strings.Split(ref.GVR, PathSeparator)
|
gvrs := strings.Split(ref.GVR, PathSeparator)
|
||||||
for i := len(ids) - 1; i >= 0; i-- {
|
paths := strings.Split(ref.Path, PathSeparator)
|
||||||
|
statuses := strings.Split(ref.Status, PathSeparator)
|
||||||
|
for i := len(paths) - 1; i >= 0; i-- {
|
||||||
if nav.Blank() {
|
if nav.Blank() {
|
||||||
nav.GVR, nav.ID = gvrs[i], ids[i]
|
nav.GVR, nav.ID, nav.Extras[StatusKey] = gvrs[i], paths[i], statuses[i]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c := NewTreeNode(gvrs[i], ids[i])
|
c := NewTreeNode(gvrs[i], paths[i])
|
||||||
if n := nav.Find(gvrs[i], ids[i]); n == nil {
|
c.Extras[StatusKey] = statuses[i]
|
||||||
|
if n := nav.Find(gvrs[i], paths[i]); n == nil {
|
||||||
nav.Add(c)
|
nav.Add(c)
|
||||||
nav = c
|
nav = c
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -180,6 +205,7 @@ func Hydrate(refs []NodeSpec) *TreeNode {
|
||||||
return root
|
return root
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Level computes the current node level.
|
||||||
func (t *TreeNode) Level() int {
|
func (t *TreeNode) Level() int {
|
||||||
var level int
|
var level int
|
||||||
p := t
|
p := t
|
||||||
|
|
@ -190,6 +216,7 @@ func (t *TreeNode) Level() int {
|
||||||
return level - 1
|
return level - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MaxDepth computes the max tree depth.
|
||||||
func (t *TreeNode) MaxDepth(depth int) int {
|
func (t *TreeNode) MaxDepth(depth int) int {
|
||||||
max := depth
|
max := depth
|
||||||
for _, c := range t.Children {
|
for _, c := range t.Children {
|
||||||
|
|
@ -201,10 +228,7 @@ func (t *TreeNode) MaxDepth(depth int) int {
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeSpacer(d int) string {
|
// Root returns the current tree root node.
|
||||||
return strings.Repeat(" ", d)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TreeNode) Root() *TreeNode {
|
func (t *TreeNode) Root() *TreeNode {
|
||||||
for p := t; p != nil; p = p.Parent {
|
for p := t; p != nil; p = p.Parent {
|
||||||
if p.Parent == nil {
|
if p.Parent == nil {
|
||||||
|
|
@ -214,23 +238,27 @@ func (t *TreeNode) Root() *TreeNode {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TreeNode) IsLeaf() bool {
|
// IsLeaf returns true if node has no children.
|
||||||
return r.Empty()
|
func (t *TreeNode) IsLeaf() bool {
|
||||||
|
return t.CountChildren() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TreeNode) IsRoot() bool {
|
// IsRoot returns true if node is top node.
|
||||||
return r.Parent == nil
|
func (t *TreeNode) IsRoot() bool {
|
||||||
|
return t.Parent == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TreeNode) ShallowClone() *TreeNode {
|
// ShallowClone performs a shallow node clone.
|
||||||
return &TreeNode{GVR: r.GVR, ID: r.ID, Extras: r.Extras}
|
func (t *TreeNode) ShallowClone() *TreeNode {
|
||||||
|
return &TreeNode{GVR: t.GVR, ID: t.ID, Extras: t.Extras}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TreeNode) Filter(q string, filter func(q, path string) bool) *TreeNode {
|
// Filter filters the node based on query.
|
||||||
specs := r.Flatten()
|
func (t *TreeNode) Filter(q string, filter func(q, path string) bool) *TreeNode {
|
||||||
|
specs := t.Flatten()
|
||||||
matches := make([]NodeSpec, 0, len(specs))
|
matches := make([]NodeSpec, 0, len(specs))
|
||||||
for _, s := range specs {
|
for _, s := range specs {
|
||||||
if filter(q, s.Path) {
|
if filter(q, s.Path+s.Status) {
|
||||||
matches = append(matches, s)
|
matches = append(matches, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -241,6 +269,18 @@ func (r *TreeNode) Filter(q string, filter func(q, path string) bool) *TreeNode
|
||||||
return Hydrate(matches)
|
return Hydrate(matches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add adds a new child node.
|
||||||
|
func (t *TreeNode) Add(c *TreeNode) {
|
||||||
|
c.Parent = t
|
||||||
|
t.Children = append(t.Children, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear delete all descendant nodes.
|
||||||
|
func (t *TreeNode) Clear() {
|
||||||
|
t.Children = []*TreeNode{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find locates a node given a gvr/id spec.
|
||||||
func (t *TreeNode) Find(gvr, id string) *TreeNode {
|
func (t *TreeNode) Find(gvr, id string) *TreeNode {
|
||||||
if t.GVR == gvr && t.ID == id {
|
if t.GVR == gvr && t.ID == id {
|
||||||
return t
|
return t
|
||||||
|
|
@ -253,26 +293,23 @@ func (t *TreeNode) Find(gvr, id string) *TreeNode {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Title computes the node title.
|
||||||
func (t *TreeNode) Title() string {
|
func (t *TreeNode) Title() string {
|
||||||
const withNS = "[white::b]%s[-::d]"
|
const withNS = "[white::b]%s[-::d]"
|
||||||
|
|
||||||
title := fmt.Sprintf(withNS, t.colorize())
|
title := fmt.Sprintf(withNS, t.colorize())
|
||||||
|
|
||||||
if t.Size() > 0 {
|
if t.CountChildren() > 0 {
|
||||||
title += fmt.Sprintf("([white::d]%d[-::d])[-::-]", t.Size())
|
title += fmt.Sprintf("([white::d]%d[-::d])[-::-]", t.CountChildren())
|
||||||
}
|
}
|
||||||
|
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeNode) Empty() bool {
|
// ----------------------------------------------------------------------------
|
||||||
return len(t.Children) == 0
|
// Helpers...
|
||||||
}
|
|
||||||
|
|
||||||
func (t *TreeNode) Clear() {
|
|
||||||
t.Children = []*TreeNode{}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Dump for debug...
|
||||||
func (t *TreeNode) Dump() {
|
func (t *TreeNode) Dump() {
|
||||||
dump(t, 0)
|
dump(t, 0)
|
||||||
}
|
}
|
||||||
|
|
@ -288,6 +325,7 @@ func dump(n *TreeNode, level int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dump to stdout for debug.
|
||||||
func (t *TreeNode) DumpStdOut() {
|
func (t *TreeNode) DumpStdOut() {
|
||||||
dumpStdOut(t, 0)
|
dumpStdOut(t, 0)
|
||||||
}
|
}
|
||||||
|
|
@ -303,26 +341,6 @@ func dumpStdOut(n *TreeNode, level int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TreeNode) Add(c *TreeNode) {
|
|
||||||
c.Parent = t
|
|
||||||
t.Children = append(t.Children, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers...
|
|
||||||
|
|
||||||
func statusEmoji(s string) string {
|
|
||||||
switch s {
|
|
||||||
case "ok":
|
|
||||||
return "[green::b]✔︎"
|
|
||||||
case "done":
|
|
||||||
return "[gray::b]🏁"
|
|
||||||
case "bad":
|
|
||||||
return "[red::b]𐄂"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 😡👎💥🧨💣🎭 🟥🟩✅✔︎☑️✔️✓
|
// 😡👎💥🧨💣🎭 🟥🟩✅✔︎☑️✔️✓
|
||||||
func toEmoji(gvr string) string {
|
func toEmoji(gvr string) string {
|
||||||
switch gvr {
|
switch gvr {
|
||||||
|
|
@ -361,7 +379,7 @@ func (t TreeNode) colorize() string {
|
||||||
case ToastStatus:
|
case ToastStatus:
|
||||||
color, flag = "orangered", "[red::b]TOAST"
|
color, flag = "orangered", "[red::b]TOAST"
|
||||||
case MissingRefStatus:
|
case MissingRefStatus:
|
||||||
color, flag = "orange", "[orange::b]MISSING_REF"
|
color, flag = "orange", "[orange::b]TOAST_REF"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,29 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestTreeNodeCount(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
root *xray.TreeNode
|
||||||
|
e int
|
||||||
|
}{
|
||||||
|
"simple": {
|
||||||
|
root: root1(),
|
||||||
|
e: 3,
|
||||||
|
},
|
||||||
|
"complex": {
|
||||||
|
root: root3(),
|
||||||
|
e: 26,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range uu {
|
||||||
|
u := uu[k]
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, u.root.Count(""))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestTreeNodeFilter(t *testing.T) {
|
func TestTreeNodeFilter(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
q string
|
q string
|
||||||
|
|
@ -63,6 +86,9 @@ func TestTreeNodeFilter(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTreeNodeHydrate(t *testing.T) {
|
func TestTreeNodeHydrate(t *testing.T) {
|
||||||
|
threeOK := strings.Join([]string{"ok", "ok", "ok"}, xray.PathSeparator)
|
||||||
|
fiveOK := strings.Join([]string{"ok", "ok", "ok", "ok", "ok"}, xray.PathSeparator)
|
||||||
|
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
spec []xray.NodeSpec
|
spec []xray.NodeSpec
|
||||||
e *xray.TreeNode
|
e *xray.TreeNode
|
||||||
|
|
@ -70,12 +96,14 @@ func TestTreeNodeHydrate(t *testing.T) {
|
||||||
"flat_simple": {
|
"flat_simple": {
|
||||||
spec: []xray.NodeSpec{
|
spec: []xray.NodeSpec{
|
||||||
{
|
{
|
||||||
GVR: "containers::v1/pods",
|
GVR: "containers::v1/pods",
|
||||||
Path: "c1::default/p1",
|
Path: "c1::default/p1",
|
||||||
|
Status: threeOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "containers::v1/pods",
|
GVR: "containers::v1/pods",
|
||||||
Path: "c2::default/p1",
|
Path: "c2::default/p1",
|
||||||
|
Status: threeOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
e: root1(),
|
e: root1(),
|
||||||
|
|
@ -83,12 +111,14 @@ func TestTreeNodeHydrate(t *testing.T) {
|
||||||
"flat_complex": {
|
"flat_complex": {
|
||||||
spec: []xray.NodeSpec{
|
spec: []xray.NodeSpec{
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::containers::v1/pods",
|
GVR: "v1/secrets::containers::v1/pods",
|
||||||
Path: "s1::c1::default/p1",
|
Path: "s1::c1::default/p1",
|
||||||
|
Status: threeOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::containers::v1/pods",
|
GVR: "v1/secrets::containers::v1/pods",
|
||||||
Path: "s2::c2::default/p1",
|
Path: "s2::c2::default/p1",
|
||||||
|
Status: threeOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
e: root2(),
|
e: root2(),
|
||||||
|
|
@ -96,40 +126,49 @@ func TestTreeNodeHydrate(t *testing.T) {
|
||||||
"complex1": {
|
"complex1": {
|
||||||
spec: []xray.NodeSpec{
|
spec: []xray.NodeSpec{
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "default/default-token-rr22g::default/nginx-6b866d578b-c6tcn::default/nginx::-/default::deployments",
|
Path: "default/default-token-rr22g::default/nginx-6b866d578b-c6tcn::default/nginx::-/default::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/configmaps::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/configmaps::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "kube-system/coredns::kube-system/coredns-6955765f44-89q2p::kube-system/coredns::-/kube-system::deployments",
|
Path: "kube-system/coredns::kube-system/coredns-6955765f44-89q2p::kube-system/coredns::-/kube-system::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "kube-system/coredns-token-5cq9j::kube-system/coredns-6955765f44-89q2p::kube-system/coredns::-/kube-system::deployments",
|
Path: "kube-system/coredns-token-5cq9j::kube-system/coredns-6955765f44-89q2p::kube-system/coredns::-/kube-system::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/configmaps::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/configmaps::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "kube-system/coredns::kube-system/coredns-6955765f44-r9j9t::kube-system/coredns::-/kube-system::deployments",
|
Path: "kube-system/coredns::kube-system/coredns-6955765f44-r9j9t::kube-system/coredns::-/kube-system::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "kube-system/coredns-token-5cq9j::kube-system/coredns-6955765f44-r9j9t::kube-system/coredns::-/kube-system::deployments",
|
Path: "kube-system/coredns-token-5cq9j::kube-system/coredns-6955765f44-r9j9t::kube-system/coredns::-/kube-system::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "kube-system/default-token-thzt8::kube-system/metrics-server-6754dbc9df-88bk4::kube-system/metrics-server::-/kube-system::deployments",
|
Path: "kube-system/default-token-thzt8::kube-system/metrics-server-6754dbc9df-88bk4::kube-system/metrics-server::-/kube-system::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "kube-system/nginx-ingress-token-kff5q::kube-system/nginx-ingress-controller-6fc5bcc8c9-cwp55::kube-system/nginx-ingress-controller::-/kube-system::deployments",
|
Path: "kube-system/nginx-ingress-token-kff5q::kube-system/nginx-ingress-controller-6fc5bcc8c9-cwp55::kube-system/nginx-ingress-controller::-/kube-system::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4::kubernetes-dashboard/dashboard-metrics-scraper-7b64584c5c-c7b56::kubernetes-dashboard/dashboard-metrics-scraper::-/kubernetes-dashboard::deployments",
|
Path: "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4::kubernetes-dashboard/dashboard-metrics-scraper-7b64584c5c-c7b56::kubernetes-dashboard/dashboard-metrics-scraper::-/kubernetes-dashboard::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
GVR: "v1/secrets::v1/pods::apps/v1/deployments::v1/namespaces::apps/v1/deployments",
|
||||||
Path: "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4::kubernetes-dashboard/kubernetes-dashboard-79d9cd965-b4c7d::kubernetes-dashboard/kubernetes-dashboard::-/kubernetes-dashboard::deployments",
|
Path: "kubernetes-dashboard/kubernetes-dashboard-token-d6rt4::kubernetes-dashboard/kubernetes-dashboard-79d9cd965-b4c7d::kubernetes-dashboard/kubernetes-dashboard::-/kubernetes-dashboard::deployments",
|
||||||
|
Status: fiveOK,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
e: root3(),
|
e: root3(),
|
||||||
|
|
@ -154,12 +193,14 @@ func TestTreeNodeFlatten(t *testing.T) {
|
||||||
root: root1(),
|
root: root1(),
|
||||||
e: []xray.NodeSpec{
|
e: []xray.NodeSpec{
|
||||||
{
|
{
|
||||||
GVR: "containers::v1/pods",
|
GVR: "containers::v1/pods",
|
||||||
Path: "c1::default/p1",
|
Path: "c1::default/p1",
|
||||||
|
Status: strings.Join([]string{"ok", "ok"}, xray.PathSeparator),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "containers::v1/pods",
|
GVR: "containers::v1/pods",
|
||||||
Path: "c2::default/p1",
|
Path: "c2::default/p1",
|
||||||
|
Status: strings.Join([]string{"ok", "ok"}, xray.PathSeparator),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -167,12 +208,14 @@ func TestTreeNodeFlatten(t *testing.T) {
|
||||||
root: root2(),
|
root: root2(),
|
||||||
e: []xray.NodeSpec{
|
e: []xray.NodeSpec{
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::containers::v1/pods",
|
GVR: "v1/secrets::containers::v1/pods",
|
||||||
Path: "s1::c1::default/p1",
|
Path: "s1::c1::default/p1",
|
||||||
|
Status: strings.Join([]string{"ok", "ok", "ok"}, xray.PathSeparator),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
GVR: "v1/secrets::containers::v1/pods",
|
GVR: "v1/secrets::containers::v1/pods",
|
||||||
Path: "s2::c2::default/p1",
|
Path: "s2::c2::default/p1",
|
||||||
|
Status: strings.Join([]string{"ok", "ok", "ok"}, xray.PathSeparator),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -226,7 +269,7 @@ func TestTreeNodeRoot(t *testing.T) {
|
||||||
n.Add(c1)
|
n.Add(c1)
|
||||||
n.Add(c2)
|
n.Add(c2)
|
||||||
|
|
||||||
assert.Equal(t, 2, n.Size())
|
assert.Equal(t, 2, n.CountChildren())
|
||||||
assert.Equal(t, n, n.Root())
|
assert.Equal(t, n, n.Root())
|
||||||
assert.True(t, n.IsRoot())
|
assert.True(t, n.IsRoot())
|
||||||
assert.False(t, n.IsLeaf())
|
assert.False(t, n.IsLeaf())
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue