checkpoint
parent
4d557fb813
commit
990d58e585
|
|
@ -95,6 +95,7 @@ K9s is available on Linux, OSX and Windows platforms.
|
|||
|
||||
## Demo Video
|
||||
|
||||
* [K9s v0.13.0](https://www.youtube.com/watch?v=qaeR2iK7U0o&t=15s)
|
||||
* [K9s v0.9.0](https://www.youtube.com/watch?v=bxKfqumjW4I)
|
||||
* [K9s v0.7.0 Features](https://youtu.be/83jYehwlql8)
|
||||
* [K9s v0 Demo](https://youtu.be/k7zseUhaXeU)
|
||||
|
|
@ -434,6 +435,10 @@ roleRef:
|
|||
|
||||
## Skins
|
||||
|
||||
Example: Dracula Skin ;)
|
||||
|
||||
<img src="assets/skins/dracula.png">
|
||||
|
||||
You can style K9s based on your own sense of look and style. Skins are YAML files, that enable a user to change the K9s presentation layer. K9s skins are loaded from `$HOME/.k9s/skin.yml`. If a skin file is detected then the skin would be loaded if not the current stock skin remains in effect.
|
||||
|
||||
You can also change K9s skins based on the cluster you are connecting too. In this case, you can specify the skin file name as `$HOME/.k9s/mycluster_skin.yml`
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 731 KiB After Width: | Height: | Size: 560 KiB |
|
|
@ -10,17 +10,16 @@ Also if you dig this tool, please make some noise on social! [@kitesurfer](https
|
|||
|
||||
---
|
||||
|
||||
### GH Sponsor
|
||||
|
||||
I know a lot of you have voiced in the past for other ways to contribute to this project ie liquids budget or prozac supplies whichever best applies here... So I've enabled github sponsors and the button should now be available on this repo.
|
||||
### GitHub Sponsors
|
||||
|
||||
I'd like to personally thank the following folks for their support and efforts with this project as I know some of you have been around since it's inception almost a year ago!
|
||||
|
||||
* [Norbert Csibra](https://github.com/ncsibra)
|
||||
* [Andrew Roth](https://github.com/RothAndrew)
|
||||
* [James Smith](https://github.com/sedders123)
|
||||
* [Daniel Koopmans](https://github.com/fsdaniel)
|
||||
|
||||
Big thanks in full effect to you all, I am so humbled and honored by your gesture!
|
||||
Big thanks in full effect to you all, I am so humbled and honored by your kind actions!
|
||||
|
||||
### Dracula Skin
|
||||
|
||||
|
|
@ -32,7 +31,7 @@ Since we're in the thank you phase, might as well lasso in `Josh Symmonds` for c
|
|||
|
||||
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_xray.png"/>
|
||||
|
||||
Since we've launched K9s, we've longed for a view that would display the relationships among resources. For instance, pods may reference configmaps/secrets directly via volumes or indirectly with containers referencing configmaps/secrets via say env vars. Having the ability to know which pods/deployments use a given configmap may involve some serious `kubectl` wizardry. K9s now has xray vision which allows one to view and traverse these relationships/associations.
|
||||
Since we've launched K9s, we've longed for a view that would display the relationships among resources. For instance, pods may reference configmaps/secrets directly via volumes or indirectly with containers referencing configmaps/secrets via say env vars. Having the ability to know which pods/deployments use a given configmap may involve some serious `kubectl` wizardry. K9s now has xray vision which allows one to view and traverse these relationships/associations as well as check for referential integrity.
|
||||
|
||||
For this, we are introducing a new command aka `xray`. Xray initally supports the following resources (more to come later...)
|
||||
|
||||
|
|
@ -45,7 +44,7 @@ To enable cluster xray vision for deployments simply type `:xray deploy`. You ca
|
|||
|
||||
Xray not only will tell you when a resource is considered `TOAST` ie the resource is in a bad state, but also will tell you if a dependency is actually broken via `TOAST_REF` status. For example a pod referencing a configmap that has been deleted from the cluster.
|
||||
|
||||
Xray view also supports for filtering the resources by leveraging regex, labels or fuzzy filters. This affords for getting more of an application view across several resources.
|
||||
Xray view also supports for filtering the resources by leveraging regex, labels or fuzzy filters. This affords for getting more of an application `cross-cut` among several resources.
|
||||
|
||||
As it stands Xray will check for following resource dependencies:
|
||||
|
||||
|
|
@ -56,7 +55,7 @@ As it stands Xray will check for following resource dependencies:
|
|||
* serviceaccounts
|
||||
* persistentvolumeclaims
|
||||
|
||||
Keep in mind these can be expensive traversals and the view is eventually consistent as dependent resources would be lazy loaded.
|
||||
Keep in mind these can be expensive traversals and the view is eventually consistent as dependent resources will be lazy loaded.
|
||||
|
||||
We hope you'll find this feature useful? Keep in mind this is an initial drop and more will be coming in this area in subsequent releases. As always, your comments/suggestions are encouraged and welcomed.
|
||||
|
||||
|
|
@ -17,7 +17,7 @@ import (
|
|||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
)
|
||||
|
||||
const dialTimeout = 1 * time.Second
|
||||
const dialTimeout = 5 * time.Second
|
||||
|
||||
// Config tracks a kubernetes configuration.
|
||||
type Config struct {
|
||||
|
|
|
|||
|
|
@ -40,63 +40,64 @@ func NewHelp() *Help {
|
|||
}
|
||||
|
||||
// Init initializes the component.
|
||||
func (v *Help) Init(ctx context.Context) error {
|
||||
if err := v.Table.Init(ctx); err != nil {
|
||||
func (h *Help) Init(ctx context.Context) error {
|
||||
if err := h.Table.Init(ctx); err != nil {
|
||||
return nil
|
||||
}
|
||||
v.SetSelectable(false, false)
|
||||
v.resetTitle()
|
||||
v.SetBorder(true)
|
||||
v.SetBorderPadding(0, 0, 1, 1)
|
||||
v.bindKeys()
|
||||
v.build()
|
||||
v.SetBackgroundColor(v.App().Styles.BgColor())
|
||||
h.SetSelectable(false, false)
|
||||
h.resetTitle()
|
||||
h.SetBorder(true)
|
||||
h.SetBorderPadding(0, 0, 1, 1)
|
||||
h.bindKeys()
|
||||
h.build()
|
||||
h.SetBackgroundColor(h.App().Styles.BgColor())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Help) bindKeys() {
|
||||
v.Actions().Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlS)
|
||||
v.Actions().Set(ui.KeyActions{
|
||||
tcell.KeyEsc: ui.NewKeyAction("Back", v.app.PrevCmd, false),
|
||||
ui.KeyHelp: ui.NewKeyAction("Back", v.app.PrevCmd, false),
|
||||
tcell.KeyEnter: ui.NewKeyAction("Back", v.app.PrevCmd, false),
|
||||
func (h *Help) bindKeys() {
|
||||
h.Actions().Delete(ui.KeySpace, tcell.KeyCtrlSpace, tcell.KeyCtrlS)
|
||||
h.Actions().Set(ui.KeyActions{
|
||||
tcell.KeyEsc: ui.NewKeyAction("Back", h.app.PrevCmd, false),
|
||||
ui.KeyHelp: ui.NewKeyAction("Back", h.app.PrevCmd, false),
|
||||
tcell.KeyEnter: ui.NewKeyAction("Back", h.app.PrevCmd, false),
|
||||
})
|
||||
}
|
||||
|
||||
func (v *Help) computeMaxes(hh model.MenuHints) {
|
||||
for _, h := range hh {
|
||||
if len(h.Mnemonic) > v.maxKey {
|
||||
v.maxKey = len(h.Mnemonic)
|
||||
func (h *Help) computeMaxes(hh model.MenuHints) {
|
||||
h.maxKey, h.maxDesc = 0, 0
|
||||
for _, hint := range hh {
|
||||
if len(hint.Mnemonic) > h.maxKey {
|
||||
h.maxKey = len(hint.Mnemonic)
|
||||
}
|
||||
if len(h.Description) > v.maxDesc {
|
||||
v.maxDesc = len(h.Description)
|
||||
if len(hint.Description) > h.maxDesc {
|
||||
h.maxDesc = len(hint.Description)
|
||||
}
|
||||
}
|
||||
v.maxKey += 2
|
||||
h.maxKey += 2
|
||||
}
|
||||
|
||||
func (v *Help) build() {
|
||||
v.Clear()
|
||||
func (h *Help) build() {
|
||||
h.Clear()
|
||||
|
||||
v.maxRows = len(v.showGeneral())
|
||||
ff := []HelpFunc{v.app.Content.Top().Hints, v.showGeneral, v.showNav, v.showHelp}
|
||||
h.maxRows = len(h.showGeneral())
|
||||
ff := []HelpFunc{h.app.Content.Top().Hints, h.showGeneral, h.showNav, h.showHelp}
|
||||
var col int
|
||||
for i, section := range []string{"RESOURCE", "GENERAL", "NAVIGATION", "HELP"} {
|
||||
hh := ff[i]()
|
||||
sort.Sort(hh)
|
||||
v.computeMaxes(hh)
|
||||
v.addSection(col, section, hh)
|
||||
h.computeMaxes(hh)
|
||||
h.addSection(col, section, hh)
|
||||
col += 2
|
||||
}
|
||||
|
||||
if h, err := v.showHotKeys(); err == nil {
|
||||
v.computeMaxes(h)
|
||||
v.addSection(col, "HOTKEYS", h)
|
||||
if hh, err := h.showHotKeys(); err == nil {
|
||||
h.computeMaxes(hh)
|
||||
h.addSection(col, "HOTKEYS", hh)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Help) showHelp() model.MenuHints {
|
||||
func (h *Help) showHelp() model.MenuHints {
|
||||
return model.MenuHints{
|
||||
{
|
||||
Mnemonic: "?",
|
||||
|
|
@ -109,7 +110,7 @@ func (v *Help) showHelp() model.MenuHints {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *Help) showNav() model.MenuHints {
|
||||
func (h *Help) showNav() model.MenuHints {
|
||||
return model.MenuHints{
|
||||
{
|
||||
Mnemonic: "g",
|
||||
|
|
@ -145,7 +146,7 @@ func (v *Help) showNav() model.MenuHints {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *Help) showHotKeys() (model.MenuHints, error) {
|
||||
func (h *Help) showHotKeys() (model.MenuHints, error) {
|
||||
hh := config.NewHotKeys()
|
||||
if err := hh.Load(); err != nil {
|
||||
return nil, fmt.Errorf("no hotkey configuration found")
|
||||
|
|
@ -166,7 +167,7 @@ func (v *Help) showHotKeys() (model.MenuHints, error) {
|
|||
return mm, nil
|
||||
}
|
||||
|
||||
func (v *Help) showGeneral() model.MenuHints {
|
||||
func (h *Help) showGeneral() model.MenuHints {
|
||||
return model.MenuHints{
|
||||
{
|
||||
Mnemonic: ":cmd",
|
||||
|
|
@ -219,20 +220,20 @@ func (v *Help) showGeneral() model.MenuHints {
|
|||
}
|
||||
}
|
||||
|
||||
func (v *Help) resetTitle() {
|
||||
v.SetTitle(fmt.Sprintf(helpTitleFmt, helpTitle))
|
||||
func (h *Help) resetTitle() {
|
||||
h.SetTitle(fmt.Sprintf(helpTitleFmt, helpTitle))
|
||||
}
|
||||
|
||||
func (v *Help) addSpacer(c int) {
|
||||
cell := tview.NewTableCell(render.Pad("", v.maxKey))
|
||||
cell.SetBackgroundColor(v.App().Styles.BgColor())
|
||||
func (h *Help) addSpacer(c int) {
|
||||
cell := tview.NewTableCell(render.Pad("", h.maxKey))
|
||||
cell.SetBackgroundColor(h.App().Styles.BgColor())
|
||||
cell.SetExpansion(1)
|
||||
v.SetCell(0, c, cell)
|
||||
h.SetCell(0, c, cell)
|
||||
}
|
||||
|
||||
func (v *Help) addSection(c int, title string, hh model.MenuHints) {
|
||||
if len(hh) > v.maxRows {
|
||||
v.maxRows = len(hh)
|
||||
func (h *Help) addSection(c int, title string, hh model.MenuHints) {
|
||||
if len(hh) > h.maxRows {
|
||||
h.maxRows = len(hh)
|
||||
}
|
||||
row := 0
|
||||
cell := tview.NewTableCell(title)
|
||||
|
|
@ -240,40 +241,43 @@ func (v *Help) addSection(c int, title string, hh model.MenuHints) {
|
|||
cell.SetAttributes(tcell.AttrBold)
|
||||
cell.SetExpansion(1)
|
||||
cell.SetAlign(tview.AlignLeft)
|
||||
v.SetCell(row, c, cell)
|
||||
v.addSpacer(c + 1)
|
||||
h.SetCell(row, c, cell)
|
||||
h.addSpacer(c + 1)
|
||||
row++
|
||||
|
||||
for _, h := range hh {
|
||||
for _, hint := range hh {
|
||||
col := c
|
||||
cell := tview.NewTableCell(render.Pad(toMnemonic(h.Mnemonic), v.maxKey))
|
||||
if _, err := strconv.Atoi(h.Mnemonic); err != nil {
|
||||
cell := tview.NewTableCell(render.Pad(toMnemonic(hint.Mnemonic), h.maxKey))
|
||||
if _, err := strconv.Atoi(hint.Mnemonic); err != nil {
|
||||
cell.SetTextColor(tcell.ColorDodgerBlue)
|
||||
} else {
|
||||
cell.SetTextColor(tcell.ColorFuchsia)
|
||||
}
|
||||
cell.SetAttributes(tcell.AttrBold)
|
||||
v.SetCell(row, col, cell)
|
||||
h.SetCell(row, col, cell)
|
||||
col++
|
||||
cell = tview.NewTableCell(render.Pad(h.Description, v.maxDesc))
|
||||
cell = tview.NewTableCell(render.Pad(hint.Description, h.maxDesc))
|
||||
cell.SetTextColor(tcell.ColorWhite)
|
||||
v.SetCell(row, col, cell)
|
||||
h.SetCell(row, col, cell)
|
||||
row++
|
||||
}
|
||||
|
||||
if len(hh) < v.maxRows {
|
||||
for i := v.maxRows - len(hh); i > 0; i-- {
|
||||
if len(hh) < h.maxRows {
|
||||
for i := h.maxRows - len(hh); i > 0; i-- {
|
||||
col := c
|
||||
cell := tview.NewTableCell(render.Pad("", v.maxKey))
|
||||
v.SetCell(row, col, cell)
|
||||
cell := tview.NewTableCell(render.Pad("", h.maxKey))
|
||||
h.SetCell(row, col, cell)
|
||||
col++
|
||||
cell = tview.NewTableCell(render.Pad("", v.maxDesc))
|
||||
v.SetCell(row, col, cell)
|
||||
cell = tview.NewTableCell(render.Pad("", h.maxDesc))
|
||||
h.SetCell(row, col, cell)
|
||||
row++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers...
|
||||
|
||||
func toMnemonic(s string) string {
|
||||
if len(s) == 0 {
|
||||
return s
|
||||
|
|
|
|||
|
|
@ -72,9 +72,11 @@ func (*Deployment) validate(root *TreeNode, dp appsv1.Deployment) error {
|
|||
r = int32(*dp.Spec.Replicas)
|
||||
}
|
||||
a := dp.Status.AvailableReplicas
|
||||
if a != r {
|
||||
if a != r || dp.Status.UnavailableReplicas != 0 {
|
||||
root.Extras[StatusKey] = ToastStatus
|
||||
}
|
||||
root.Extras[InfoKey] = fmt.Sprintf("%d/%d/%d", a, r, dp.Status.UnavailableReplicas)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ func (*DaemonSet) validate(root *TreeNode, ds appsv1.DaemonSet) error {
|
|||
if d != a {
|
||||
root.Extras[StatusKey] = ToastStatus
|
||||
}
|
||||
root.Extras[InfoKey] = fmt.Sprintf("%d/%d", a, d)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ func (p *Pod) Render(ctx context.Context, ns string, o interface{}) error {
|
|||
return err
|
||||
}
|
||||
p.podVolumeRefs(f, node, po.Namespace, po.Spec.Volumes)
|
||||
if err := p.serviceAccountRef(f, ctx, node, po.Namespace, po.Spec.ServiceAccountName); err != nil {
|
||||
if err := p.serviceAccountRef(f, ctx, node, po.Namespace, po.Spec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ func (p *Pod) validate(node *TreeNode, po v1.Pod) error {
|
|||
}
|
||||
|
||||
node.Extras[StatusKey] = status
|
||||
node.Extras[StateKey] = strconv.Itoa(cr) + "/" + strconv.Itoa(len(ss))
|
||||
node.Extras[InfoKey] = strconv.Itoa(cr) + "/" + strconv.Itoa(len(ss))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -87,12 +87,12 @@ func (*Pod) containerRefs(ctx context.Context, parent *TreeNode, ns string, spec
|
|||
return nil
|
||||
}
|
||||
|
||||
func (*Pod) serviceAccountRef(f dao.Factory, ctx context.Context, parent *TreeNode, ns, sa string) error {
|
||||
if sa == "" {
|
||||
func (*Pod) serviceAccountRef(f dao.Factory, ctx context.Context, parent *TreeNode, ns string, spec v1.PodSpec) error {
|
||||
if spec.ServiceAccountName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
id := client.FQN(ns, sa)
|
||||
id := client.FQN(ns, spec.ServiceAccountName)
|
||||
o, err := f.Get("v1/serviceaccounts", id, false, labels.Everything())
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -104,7 +104,7 @@ func (*Pod) serviceAccountRef(f dao.Factory, ctx context.Context, parent *TreeNo
|
|||
|
||||
var saRE ServiceAccount
|
||||
ctx = context.WithValue(ctx, KeyParent, parent)
|
||||
|
||||
ctx = context.WithValue(ctx, KeySAAutomount, spec.AutomountServiceAccountToken)
|
||||
return saRE.Render(ctx, ns, o)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,9 +30,8 @@ func (s *ServiceAccount) Render(ctx context.Context, ns string, o interface{}) e
|
|||
if !ok {
|
||||
return fmt.Errorf("no factory found in context")
|
||||
}
|
||||
|
||||
node := NewTreeNode("v1/serviceaccounts", client.FQN(sa.Namespace, sa.Name))
|
||||
node.Extras[StatusKey] = OkStatus
|
||||
|
||||
parent, ok := ctx.Value(KeyParent).(*TreeNode)
|
||||
if !ok {
|
||||
return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent))
|
||||
|
|
@ -46,5 +45,18 @@ func (s *ServiceAccount) Render(ctx context.Context, ns string, o interface{}) e
|
|||
addRef(f, node, "v1/secrets", client.FQN(sa.Namespace, sec.Name), nil)
|
||||
}
|
||||
|
||||
auto, _ := ctx.Value(KeySAAutomount).(*bool)
|
||||
return s.validate(node, sa, auto)
|
||||
}
|
||||
|
||||
func (*ServiceAccount) validate(node *TreeNode, sa v1.ServiceAccount, auto *bool) error {
|
||||
node.Extras[StatusKey] = OkStatus
|
||||
if sa.AutomountServiceAccountToken != nil {
|
||||
node.Extras[InfoKey] = fmt.Sprintf("automount=%t", *sa.AutomountServiceAccountToken)
|
||||
}
|
||||
if auto != nil {
|
||||
node.Extras[InfoKey] = fmt.Sprintf("automount=%t", *auto)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,10 +68,11 @@ func (*StatefulSet) validate(root *TreeNode, sts appsv1.StatefulSet) error {
|
|||
if sts.Spec.Replicas != nil {
|
||||
r = int32(*sts.Spec.Replicas)
|
||||
}
|
||||
a := sts.Status.Replicas
|
||||
a := sts.Status.CurrentReplicas
|
||||
if a != r {
|
||||
root.Extras[StatusKey] = ToastStatus
|
||||
}
|
||||
root.Extras[InfoKey] = fmt.Sprintf("%d/%d", a, r)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,14 +15,17 @@ const (
|
|||
// KeyParent indicates a parent node context key.
|
||||
KeyParent TreeRef = "parent"
|
||||
|
||||
// KeySAAutomount indicates whether an automount sa token is active or not.
|
||||
KeySAAutomount TreeRef = "automount"
|
||||
|
||||
// PathSeparator represents a node path separatot.
|
||||
PathSeparator = "::"
|
||||
|
||||
// StatusKey status map key.
|
||||
StatusKey = "status"
|
||||
|
||||
// StateKey state map key.
|
||||
StateKey = "state"
|
||||
// InfoKey state map key.
|
||||
InfoKey = "info"
|
||||
|
||||
// OkStatus stands for all is cool.
|
||||
OkStatus = "ok"
|
||||
|
|
@ -294,7 +297,7 @@ func (t *TreeNode) Find(gvr, id string) *TreeNode {
|
|||
func (t *TreeNode) Title() string {
|
||||
const withNS = "[white::b]%s[-::d]"
|
||||
|
||||
title := fmt.Sprintf(withNS, t.colorize())
|
||||
title := fmt.Sprintf(withNS, t.AsString())
|
||||
|
||||
if t.CountChildren() > 0 {
|
||||
title += fmt.Sprintf("([white::d]%d[-::d])[-::-]", t.CountChildren())
|
||||
|
|
@ -366,7 +369,7 @@ func toEmoji(gvr string) string {
|
|||
}
|
||||
}
|
||||
|
||||
func (t TreeNode) colorize() string {
|
||||
func (t TreeNode) AsString() string {
|
||||
const colorFmt = "%s [gray::-][%s[gray::-]] [%s::b]%s[::]"
|
||||
|
||||
_, n := client.Namespaced(t.ID)
|
||||
|
|
@ -379,6 +382,12 @@ func (t TreeNode) colorize() string {
|
|||
color, flag = "orange", "[orange::b]TOAST_REF"
|
||||
}
|
||||
}
|
||||
str := fmt.Sprintf(colorFmt, toEmoji(t.GVR), flag, color, n)
|
||||
|
||||
return fmt.Sprintf(colorFmt, toEmoji(t.GVR), flag, color, n)
|
||||
i, ok := t.Extras[InfoKey]
|
||||
if !ok {
|
||||
return str
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s [antiquewhite::][%s][::] ", str, i)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue