misc bug fixes
parent
921a39f897
commit
c61365a9c9
|
|
@ -10,6 +10,7 @@ for changes and offers subsequent commands to interact with observed Kubernetes
|
||||||
---
|
---
|
||||||
|
|
||||||
[](https://goreportcard.com/report/github.com/derailed/k9s)
|
[](https://goreportcard.com/report/github.com/derailed/k9s)
|
||||||
|
[](https://codebeat.co/projects/github-com-derailed-k9s-master)
|
||||||
[](https://travis-ci.com/derailed/k9s)
|
[](https://travis-ci.com/derailed/k9s)
|
||||||
[](https://github.com/derailed/k9s/releases)
|
[](https://github.com/derailed/k9s/releases)
|
||||||
[](https://github.com/mum4k/termdash/blob/master/LICENSE)
|
[](https://github.com/mum4k/termdash/blob/master/LICENSE)
|
||||||
|
|
|
||||||
3
go.mod
3
go.mod
|
|
@ -20,7 +20,6 @@ require (
|
||||||
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
|
github.com/elazarl/goproxy/ext v0.0.0-20190421051319-9d40249d3c2f // indirect
|
||||||
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
|
github.com/evanphx/json-patch v4.1.0+incompatible // indirect
|
||||||
github.com/fatih/camelcase v1.0.0 // indirect
|
github.com/fatih/camelcase v1.0.0 // indirect
|
||||||
github.com/fatih/color v1.7.0 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/gdamore/tcell v1.1.1
|
github.com/gdamore/tcell v1.1.1
|
||||||
github.com/gogo/protobuf v1.2.1 // indirect
|
github.com/gogo/protobuf v1.2.1 // indirect
|
||||||
|
|
@ -34,7 +33,6 @@ require (
|
||||||
github.com/imdario/mergo v0.3.7 // indirect
|
github.com/imdario/mergo v0.3.7 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.6 // indirect
|
github.com/json-iterator/go v1.1.6 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.2 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.4
|
github.com/mattn/go-runewidth v0.0.4
|
||||||
github.com/onsi/ginkgo v1.8.0 // indirect
|
github.com/onsi/ginkgo v1.8.0 // indirect
|
||||||
github.com/onsi/gomega v1.5.0 // indirect
|
github.com/onsi/gomega v1.5.0 // indirect
|
||||||
|
|
@ -45,7 +43,6 @@ require (
|
||||||
github.com/spf13/cobra v0.0.3
|
github.com/spf13/cobra v0.0.3
|
||||||
github.com/spf13/pflag v1.0.3 // indirect
|
github.com/spf13/pflag v1.0.3 // indirect
|
||||||
github.com/stretchr/testify v1.3.0
|
github.com/stretchr/testify v1.3.0
|
||||||
github.com/wercker/stern v0.0.0-20181017112310-807830e57719
|
|
||||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 // indirect
|
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 // indirect
|
||||||
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 // indirect
|
golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 // indirect
|
||||||
|
|
|
||||||
13
go.sum
13
go.sum
|
|
@ -33,10 +33,6 @@ github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/derailed/tview v0.1.8 h1:HjkCCTzgZWkkyUtJMGxhRJtvIvcRLuPCb9Uh11KRoC4=
|
|
||||||
github.com/derailed/tview v0.1.8/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
|
||||||
github.com/derailed/tview v0.1.9 h1:CYyGBvhJ4VenoRlUE1NDstyv4kayjQVnidSDAwuemdk=
|
|
||||||
github.com/derailed/tview v0.1.9/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
|
||||||
github.com/derailed/tview v0.1.10 h1:QWjK82ccTl3C7Tfyfmv765eRqEt/T3aXp40464cfnlw=
|
github.com/derailed/tview v0.1.10 h1:QWjK82ccTl3C7Tfyfmv765eRqEt/T3aXp40464cfnlw=
|
||||||
github.com/derailed/tview v0.1.10/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
github.com/derailed/tview v0.1.10/go.mod h1:g+ZyIsV5osK+lQ6LajiGQeLW10BQLJ6aMvy8Ldt2oa0=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
|
|
@ -55,8 +51,6 @@ github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7Vpz
|
||||||
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
|
||||||
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
|
||||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||||
|
|
@ -129,10 +123,6 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
|
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
|
||||||
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
|
||||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
|
||||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
|
|
@ -193,8 +183,6 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/wercker/stern v0.0.0-20181017112310-807830e57719 h1:x9/CGytbUciiGoBHn04xsJJR/Lsg4b3qQlKJSZY5Gzw=
|
|
||||||
github.com/wercker/stern v0.0.0-20181017112310-807830e57719/go.mod h1:+72MfLYlS87s4tqq+eVDANQ9GdILz0lkpAFlX/1+WWY=
|
|
||||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
|
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1 h1:j2hhcujLRHAg872RWAV5yaUrEjHEObwDv3aImCaNLek=
|
||||||
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8=
|
||||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
|
@ -243,7 +231,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA=
|
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA=
|
||||||
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
|
||||||
|
|
@ -22,19 +22,18 @@ type (
|
||||||
|
|
||||||
// Style tracks K9s styles.
|
// Style tracks K9s styles.
|
||||||
Style struct {
|
Style struct {
|
||||||
FgColor string `yaml:"fgColor"`
|
FgColor string `yaml:"fgColor"`
|
||||||
BgColor string `yaml:"bgColor"`
|
BgColor string `yaml:"bgColor"`
|
||||||
LogoColor string `yaml:"logoColor"`
|
LogoColor string `yaml:"logoColor"`
|
||||||
|
Title *Title `yaml:"title"`
|
||||||
Info *Info `yaml:"info"`
|
Border *Border `yaml:"border"`
|
||||||
Border *Border `yaml:"border"`
|
Info *Info `yaml:"info"`
|
||||||
Menu *Menu `yaml:"menu"`
|
Menu *Menu `yaml:"menu"`
|
||||||
Crumb *Crumb `yaml:"crumb"`
|
Crumb *Crumb `yaml:"crumb"`
|
||||||
Table *Table `yaml:"table"`
|
Table *Table `yaml:"table"`
|
||||||
Status *Status `yaml:"status"`
|
Status *Status `yaml:"status"`
|
||||||
Title *Title `yaml:"title"`
|
Yaml *Yaml `yaml:"yaml"`
|
||||||
Yaml *Yaml `yaml:"yaml"`
|
Log *Log `yaml:"logs"`
|
||||||
Log *Log `yaml:"logs"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status tracks resource status styles.
|
// Status tracks resource status styles.
|
||||||
|
|
@ -117,13 +116,13 @@ func newStyle() *Style {
|
||||||
FgColor: "cadetblue",
|
FgColor: "cadetblue",
|
||||||
BgColor: "black",
|
BgColor: "black",
|
||||||
LogoColor: "orange",
|
LogoColor: "orange",
|
||||||
Info: newInfo(),
|
|
||||||
Border: newBorder(),
|
Border: newBorder(),
|
||||||
|
Title: newTitle(),
|
||||||
|
Info: newInfo(),
|
||||||
Menu: newMenu(),
|
Menu: newMenu(),
|
||||||
Crumb: newCrumb(),
|
Crumb: newCrumb(),
|
||||||
Table: newTable(),
|
Table: newTable(),
|
||||||
Status: newStatus(),
|
Status: newStatus(),
|
||||||
Title: newTitle(),
|
|
||||||
Yaml: newYaml(),
|
Yaml: newYaml(),
|
||||||
Log: newLog(),
|
Log: newLog(),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ package resource
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/derailed/k9s/internal/k8s"
|
"github.com/derailed/k9s/internal/k8s"
|
||||||
"github.com/derailed/k9s/internal/watch"
|
"github.com/derailed/k9s/internal/watch"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/api/meta"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
|
"k8s.io/cli-runtime/pkg/genericclioptions/printers"
|
||||||
|
|
@ -127,14 +129,23 @@ func (b *Base) List(ns string) (Columnars, error) {
|
||||||
|
|
||||||
// Describe a given resource.
|
// Describe a given resource.
|
||||||
func (b *Base) Describe(kind, pa string) (string, error) {
|
func (b *Base) Describe(kind, pa string) (string, error) {
|
||||||
ns, n := namespaced(pa)
|
|
||||||
|
|
||||||
mapping, err := k8s.RestMapping.Find(kind)
|
mapping, err := k8s.RestMapping.Find(kind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug().Msgf("Unable to find mapper for %s %s", kind, pa)
|
g, v, n := b.Resource.(*k8s.Resource).GetInfo()
|
||||||
return "", err
|
mapper := k8s.RestMapper{b.Connection}
|
||||||
|
var e error
|
||||||
|
mapping, e = mapper.ResourceFor(fmt.Sprintf("%s.%s.%s", n, v, g))
|
||||||
|
if e != nil {
|
||||||
|
log.Debug().Err(err).Msgf("Unable to find mapper for %s %s", kind, pa)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return b.doDescribe(pa, mapping)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Base) doDescribe(pa string, mapping *meta.RESTMapping) (string, error) {
|
||||||
|
ns, n := namespaced(pa)
|
||||||
d, err := versioned.Describer(b.Connection.Config().Flags(), mapping)
|
d, err := versioned.Describer(b.Connection.Config().Flags(), mapping)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)
|
log.Error().Err(err).Msgf("Unable to find describer for %#v", mapping)
|
||||||
|
|
|
||||||
|
|
@ -39,32 +39,23 @@ func (v *aliasView) init(context.Context, string) {
|
||||||
v.update(v.hydrate())
|
v.update(v.hydrate())
|
||||||
v.app.SetFocus(v)
|
v.app.SetFocus(v)
|
||||||
v.resetTitle()
|
v.resetTitle()
|
||||||
|
v.app.setHints(v.hints())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *aliasView) registerActions() {
|
func (v *aliasView) registerActions() {
|
||||||
|
delete(v.actions, KeyShiftA)
|
||||||
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true)
|
v.actions[tcell.KeyEnter] = newKeyAction("Goto", v.gotoCmd, true)
|
||||||
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
|
v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false)
|
||||||
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
|
v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false)
|
||||||
v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortResourceCmd, true)
|
v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortColCmd(1), true)
|
||||||
v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortGroupCmd, true)
|
v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortColCmd(2), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *aliasView) getTitle() string {
|
func (v *aliasView) getTitle() string {
|
||||||
return aliasTitle
|
return aliasTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *aliasView) sortResourceCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
||||||
v.sortCol.index, v.sortCol.asc = 1, true
|
|
||||||
v.refresh()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *aliasView) sortGroupCmd(evt *tcell.EventKey) *tcell.EventKey {
|
|
||||||
v.sortCol.index, v.sortCol.asc = 2, true
|
|
||||||
v.refresh()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (v *aliasView) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if !v.cmdBuff.empty() {
|
if !v.cmdBuff.empty() {
|
||||||
v.cmdBuff.reset()
|
v.cmdBuff.reset()
|
||||||
|
|
@ -115,7 +106,8 @@ func (v *aliasView) hints() hints {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *aliasView) hydrate() resource.TableData {
|
func (v *aliasView) hydrate() resource.TableData {
|
||||||
cmds := helpCmds(v.app.conn())
|
cmds := make(map[string]resCmd, 40)
|
||||||
|
aliasCmds(v.app.conn(), cmds)
|
||||||
|
|
||||||
data := resource.TableData{
|
data := resource.TableData{
|
||||||
Header: resource.Row{"NAME", "RESOURCE", "APIGROUP"},
|
Header: resource.Row{"NAME", "RESOURCE", "APIGROUP"},
|
||||||
|
|
|
||||||
|
|
@ -99,8 +99,10 @@ func (a *appView) registerActions() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *appView) Init(version string, rate int) {
|
func (a *appView) Init(version string, rate int) {
|
||||||
a.startInformer()
|
if a.conn() != nil {
|
||||||
a.clusterInfo().init(version)
|
a.startInformer()
|
||||||
|
a.clusterInfo().init(version)
|
||||||
|
}
|
||||||
a.cmdBuff.addListener(a.cmd())
|
a.cmdBuff.addListener(a.cmd())
|
||||||
|
|
||||||
header := tview.NewFlex()
|
header := tview.NewFlex()
|
||||||
|
|
@ -333,6 +335,7 @@ func (a *appView) helpCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
func (a *appView) currentView() igniter {
|
func (a *appView) currentView() igniter {
|
||||||
return a.content.GetPrimitive("main").(igniter)
|
return a.content.GetPrimitive("main").(igniter)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
|
func (a *appView) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
|
||||||
if a.inCmdMode() {
|
if a.inCmdMode() {
|
||||||
return evt
|
return evt
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,16 @@
|
||||||
package views
|
package views
|
||||||
|
|
||||||
// import (
|
import (
|
||||||
// "testing"
|
"testing"
|
||||||
|
|
||||||
// "github.com/derailed/k9s/internal/config"
|
"github.com/derailed/k9s/internal/config"
|
||||||
// "github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
// )
|
)
|
||||||
|
|
||||||
// func TestNewApp(t *testing.T) {
|
func TestNewApp(t *testing.T) {
|
||||||
// mk := NewMockKubeSettings()
|
a := NewApp(config.NewConfig(ks{}))
|
||||||
// cfg := config.NewConfig(mk)
|
a.Init("blee", 10)
|
||||||
// a := NewApp(cfg)
|
|
||||||
// a.Init("blee", 10)
|
|
||||||
|
|
||||||
// assert.Equal(t, 10, len(a.actions))
|
assert.Equal(t, 10, len(a.actions))
|
||||||
// assert.Equal(t, false, a.hasSkins)
|
assert.Equal(t, false, a.hasSkins)
|
||||||
// }
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,13 +23,11 @@ const (
|
||||||
// K9sBenchDir directory to store K9s benchmark files.
|
// K9sBenchDir directory to store K9s benchmark files.
|
||||||
var K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser()))
|
var K9sBenchDir = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-bench-%s", config.MustK9sUser()))
|
||||||
|
|
||||||
type (
|
type benchmark struct {
|
||||||
benchmark struct {
|
canceled bool
|
||||||
canceled bool
|
config config.BenchConfig
|
||||||
config config.BenchConfig
|
worker *requester.Work
|
||||||
worker *requester.Work
|
}
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newBenchmark(base string, cfg config.BenchConfig) (*benchmark, error) {
|
func newBenchmark(base string, cfg config.BenchConfig) (*benchmark, error) {
|
||||||
b := benchmark{config: cfg}
|
b := benchmark{config: cfg}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,9 @@ func (c *command) run(cmd string) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if res, ok := resourceViews(c.app.conn())[cmd]; ok {
|
cmds := make(map[string]resCmd, 30)
|
||||||
|
resourceViews(c.app.conn(), cmds)
|
||||||
|
if res, ok := cmds[cmd]; ok {
|
||||||
var r resource.List
|
var r resource.List
|
||||||
if res.listFn != nil {
|
if res.listFn != nil {
|
||||||
r = res.listFn(c.app.conn(), resource.DefaultNamespace)
|
r = res.listFn(c.app.conn(), resource.DefaultNamespace)
|
||||||
|
|
@ -91,20 +93,22 @@ func (c *command) run(cmd string) bool {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res, ok := allCRDs(c.app.conn())[cmd]
|
cmds := make(map[string]resCmd, 30)
|
||||||
|
allCRDs(c.app.conn(), cmds)
|
||||||
|
res, ok := cmds[cmd]
|
||||||
if !ok {
|
if !ok {
|
||||||
c.app.flash().warnf("Huh? `%s` command not found", cmd)
|
c.app.flash().warnf("Huh? `%s` command not found", cmd)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
name := res.Plural
|
name := res.plural
|
||||||
if len(name) == 0 {
|
if name == "" {
|
||||||
name = res.Singular
|
name = res.singular
|
||||||
}
|
}
|
||||||
v = newResourceView(
|
v = newResourceView(
|
||||||
res.Kind,
|
res.title,
|
||||||
c.app,
|
c.app,
|
||||||
resource.NewCustomList(c.app.conn(), "", res.Group, res.Version, name),
|
resource.NewCustomList(c.app.conn(), "", res.api, res.version, name),
|
||||||
)
|
)
|
||||||
v.setColorerFn(defaultColorer)
|
v.setColorerFn(defaultColorer)
|
||||||
c.exec(cmd, v)
|
c.exec(cmd, v)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommandPush(t *testing.T) {
|
||||||
|
c := newCommand(NewApp(config.NewConfig(ks{})))
|
||||||
|
c.pushCmd("fred")
|
||||||
|
c.pushCmd("blee")
|
||||||
|
p, top := c.previousCmd()
|
||||||
|
|
||||||
|
assert.Equal(t, "fred", p)
|
||||||
|
assert.True(t, top)
|
||||||
|
assert.True(t, c.lastCmd())
|
||||||
|
}
|
||||||
|
|
@ -10,8 +10,8 @@ type contextView struct {
|
||||||
*resourceView
|
*resourceView
|
||||||
}
|
}
|
||||||
|
|
||||||
func newContextView(t string, app *appView, list resource.List) resourceViewer {
|
func newContextView(title string, app *appView, list resource.List) resourceViewer {
|
||||||
v := contextView{newResourceView(t, app, list).(*resourceView)}
|
v := contextView{newResourceView(title, app, list).(*resourceView)}
|
||||||
v.extraActionsFn = v.extraActions
|
v.extraActionsFn = v.extraActions
|
||||||
v.enterFn = v.useCtx
|
v.enterFn = v.useCtx
|
||||||
v.getTV().cleanseFn = v.cleanser
|
v.getTV().cleanseFn = v.cleanser
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/derailed/k9s/internal/resource"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContextView(t *testing.T) {
|
||||||
|
l := resource.NewContextList(nil, "fred")
|
||||||
|
v := newContextView("blee", NewApp(config.NewConfig(ks{})), l)
|
||||||
|
|
||||||
|
assert.Equal(t, "blee", v.getTitle())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleaner(t *testing.T) {
|
||||||
|
uu := map[string]struct {
|
||||||
|
s, e string
|
||||||
|
}{
|
||||||
|
"normal": {"fred", "fred"},
|
||||||
|
"default": {"fred*", "fred"},
|
||||||
|
"delta": {"fred(𝜟)", "fred"},
|
||||||
|
}
|
||||||
|
|
||||||
|
v := contextView{}
|
||||||
|
for k, u := range uu {
|
||||||
|
t.Run(k, func(t *testing.T) {
|
||||||
|
assert.Equal(t, u.e, v.cleanser(u.s))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,8 +12,8 @@ type deployView struct {
|
||||||
*logResourceView
|
*logResourceView
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDeployView(ns string, app *appView, list resource.List) resourceViewer {
|
func newDeployView(title string, app *appView, list resource.List) resourceViewer {
|
||||||
v := deployView{newLogResourceView(ns, app, list)}
|
v := deployView{newLogResourceView(title, app, list)}
|
||||||
v.extraActionsFn = v.extraActions
|
v.extraActionsFn = v.extraActions
|
||||||
v.enterFn = v.showPods
|
v.enterFn = v.showPods
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/derailed/k9s/internal/resource"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDeployView(t *testing.T) {
|
||||||
|
l := resource.NewDeploymentList(nil, "fred")
|
||||||
|
v := newDeployView("blee", NewApp(config.NewConfig(ks{})), l)
|
||||||
|
|
||||||
|
assert.Equal(t, "blee", v.getTitle())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package views
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
|
"github.com/derailed/k9s/internal/resource"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDaemonSetView(t *testing.T) {
|
||||||
|
l := resource.NewDaemonSetList(nil, "fred")
|
||||||
|
v := newDaemonSetView("blee", NewApp(config.NewConfig(ks{})), l)
|
||||||
|
|
||||||
|
assert.Equal(t, "blee", v.getTitle())
|
||||||
|
}
|
||||||
|
|
@ -18,6 +18,9 @@ type (
|
||||||
resCmd struct {
|
resCmd struct {
|
||||||
title string
|
title string
|
||||||
api string
|
api string
|
||||||
|
version string
|
||||||
|
plural string
|
||||||
|
singular string
|
||||||
viewFn viewFn
|
viewFn viewFn
|
||||||
listFn listFn
|
listFn listFn
|
||||||
enterFn enterFn
|
enterFn enterFn
|
||||||
|
|
@ -26,25 +29,14 @@ type (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func helpCmds(c k8s.Connection) map[string]resCmd {
|
func aliasCmds(c k8s.Connection, m map[string]resCmd) {
|
||||||
cmdMap := resourceViews(c)
|
resourceViews(c, m)
|
||||||
cmds := make(map[string]resCmd, len(cmdMap))
|
if c != nil {
|
||||||
for k, v := range cmdMap {
|
allCRDs(c, m)
|
||||||
cmds[k] = v
|
|
||||||
}
|
}
|
||||||
for k, v := range allCRDs(c) {
|
|
||||||
cmds[k] = resCmd{title: v.Kind, api: v.Group}
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmds
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func allCRDs(c k8s.Connection) map[string]k8s.APIGroup {
|
func allCRDs(c k8s.Connection, m map[string]resCmd) {
|
||||||
m := map[string]k8s.APIGroup{}
|
|
||||||
if c == nil {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
crds, _ := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces).
|
crds, _ := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces).
|
||||||
Resource().
|
Resource().
|
||||||
List(resource.AllNamespaces)
|
List(resource.AllNamespaces)
|
||||||
|
|
@ -58,24 +50,23 @@ func allCRDs(c k8s.Connection) map[string]k8s.APIGroup {
|
||||||
Version: ff["version"].(string),
|
Version: ff["version"].(string),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res := resCmd{title: grp.Kind, api: grp.Group, version: grp.Version}
|
||||||
if p, ok := ff["plural"].(string); ok {
|
if p, ok := ff["plural"].(string); ok {
|
||||||
grp.Plural = p
|
res.plural = p
|
||||||
m[p] = grp
|
m[p] = res
|
||||||
}
|
}
|
||||||
|
|
||||||
if s, ok := ff["singular"].(string); ok {
|
if s, ok := ff["singular"].(string); ok {
|
||||||
grp.Singular = s
|
res.singular = s
|
||||||
m[s] = grp
|
m[s] = res
|
||||||
}
|
}
|
||||||
|
|
||||||
if aa, ok := ff["aliases"].([]interface{}); ok {
|
if aa, ok := ff["aliases"].([]interface{}); ok {
|
||||||
for _, a := range aa {
|
for _, a := range aa {
|
||||||
m[a.(string)] = grp
|
m[a.(string)] = res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func showRBAC(app *appView, ns, resource, selection string) {
|
func showRBAC(app *appView, ns, resource, selection string) {
|
||||||
|
|
@ -110,220 +101,254 @@ func showSAPolicy(app *appView, _, _, selection string) {
|
||||||
app.inject(newPolicyView(app, mapFuSubject("ServiceAccount"), n))
|
app.inject(newPolicyView(app, mapFuSubject("ServiceAccount"), n))
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceViews(c k8s.Connection) map[string]resCmd {
|
func resourceViews(c k8s.Connection, m map[string]resCmd) {
|
||||||
cmds := map[string]resCmd{
|
coreRes(m)
|
||||||
"cm": {
|
rbacRes(m)
|
||||||
title: "ConfigMaps",
|
apiExtRes(m)
|
||||||
api: "",
|
batchRes(m)
|
||||||
viewFn: newResourceView,
|
appsRes(m)
|
||||||
listFn: resource.NewConfigMapList,
|
extRes(m)
|
||||||
},
|
v1beta1Res(m)
|
||||||
"cr": {
|
custRes(m)
|
||||||
title: "ClusterRoles",
|
|
||||||
api: "rbac.authorization.k8s.io",
|
if c != nil {
|
||||||
viewFn: newResourceView,
|
hpaRes(c, m)
|
||||||
listFn: resource.NewClusterRoleList,
|
}
|
||||||
enterFn: showRBAC,
|
}
|
||||||
},
|
|
||||||
"crb": {
|
func coreRes(m map[string]resCmd) {
|
||||||
title: "ClusterRoleBindings",
|
m["cm"] = resCmd{
|
||||||
api: "rbac.authorization.k8s.io",
|
title: "ConfigMaps",
|
||||||
viewFn: newResourceView,
|
api: "",
|
||||||
listFn: resource.NewClusterRoleBindingList,
|
viewFn: newResourceView,
|
||||||
enterFn: showClusterRole,
|
listFn: resource.NewConfigMapList,
|
||||||
},
|
}
|
||||||
"crd": {
|
m["ctx"] = resCmd{
|
||||||
title: "CustomResourceDefinitions",
|
title: "Contexts",
|
||||||
api: "apiextensions.k8s.io",
|
api: "",
|
||||||
viewFn: newResourceView,
|
viewFn: newContextView,
|
||||||
listFn: resource.NewCustomResourceDefinitionList,
|
listFn: resource.NewContextList,
|
||||||
},
|
colorerFn: ctxColorer,
|
||||||
"cj": {
|
}
|
||||||
title: "CronJobs",
|
m["ds"] = resCmd{
|
||||||
api: "batch",
|
title: "DaemonSets",
|
||||||
viewFn: newCronJobView,
|
api: "",
|
||||||
listFn: resource.NewCronJobList,
|
viewFn: newDaemonSetView,
|
||||||
},
|
listFn: resource.NewDaemonSetList,
|
||||||
"ctx": {
|
colorerFn: dpColorer,
|
||||||
title: "Contexts",
|
}
|
||||||
api: "",
|
m["ep"] = resCmd{
|
||||||
viewFn: newContextView,
|
title: "EndPoints",
|
||||||
listFn: resource.NewContextList,
|
api: "",
|
||||||
colorerFn: ctxColorer,
|
viewFn: newResourceView,
|
||||||
},
|
listFn: resource.NewEndpointsList,
|
||||||
"ds": {
|
}
|
||||||
title: "DaemonSets",
|
m["ev"] = resCmd{
|
||||||
api: "",
|
title: "Events",
|
||||||
viewFn: newDaemonSetView,
|
api: "",
|
||||||
listFn: resource.NewDaemonSetList,
|
viewFn: newResourceView,
|
||||||
colorerFn: dpColorer,
|
listFn: resource.NewEventList,
|
||||||
},
|
colorerFn: evColorer,
|
||||||
"dp": {
|
}
|
||||||
title: "Deployments",
|
m["no"] = resCmd{
|
||||||
api: "apps",
|
title: "Nodes",
|
||||||
viewFn: newDeployView,
|
api: "",
|
||||||
listFn: resource.NewDeploymentList,
|
viewFn: newNodeView,
|
||||||
colorerFn: dpColorer,
|
listFn: resource.NewNodeList,
|
||||||
},
|
colorerFn: nsColorer,
|
||||||
"ep": {
|
}
|
||||||
title: "EndPoints",
|
m["ns"] = resCmd{
|
||||||
api: "",
|
title: "Namespaces",
|
||||||
viewFn: newResourceView,
|
api: "",
|
||||||
listFn: resource.NewEndpointsList,
|
viewFn: newNamespaceView,
|
||||||
},
|
listFn: resource.NewNamespaceList,
|
||||||
"ev": {
|
colorerFn: nsColorer,
|
||||||
title: "Events",
|
}
|
||||||
api: "",
|
m["po"] = resCmd{
|
||||||
viewFn: newResourceView,
|
title: "Pods",
|
||||||
listFn: resource.NewEventList,
|
api: "",
|
||||||
colorerFn: evColorer,
|
viewFn: newPodView,
|
||||||
},
|
listFn: resource.NewPodList,
|
||||||
"ing": {
|
colorerFn: podColorer,
|
||||||
title: "Ingress",
|
}
|
||||||
api: "extensions",
|
m["pv"] = resCmd{
|
||||||
viewFn: newResourceView,
|
title: "PersistentVolumes",
|
||||||
listFn: resource.NewIngressList,
|
api: "",
|
||||||
},
|
viewFn: newResourceView,
|
||||||
"jo": {
|
listFn: resource.NewPersistentVolumeList,
|
||||||
title: "Jobs",
|
colorerFn: pvColorer,
|
||||||
api: "batch",
|
}
|
||||||
viewFn: newJobView,
|
m["pvc"] = resCmd{
|
||||||
listFn: resource.NewJobList,
|
title: "PersistentVolumeClaims",
|
||||||
},
|
api: "",
|
||||||
"no": {
|
viewFn: newResourceView,
|
||||||
title: "Nodes",
|
listFn: resource.NewPersistentVolumeClaimList,
|
||||||
api: "",
|
colorerFn: pvcColorer,
|
||||||
viewFn: newNodeView,
|
}
|
||||||
listFn: resource.NewNodeList,
|
m["rc"] = resCmd{
|
||||||
colorerFn: nsColorer,
|
title: "ReplicationControllers",
|
||||||
},
|
api: "",
|
||||||
"ns": {
|
viewFn: newResourceView,
|
||||||
title: "Namespaces",
|
listFn: resource.NewReplicationControllerList,
|
||||||
api: "",
|
colorerFn: rsColorer,
|
||||||
viewFn: newNamespaceView,
|
}
|
||||||
listFn: resource.NewNamespaceList,
|
m["sa"] = resCmd{
|
||||||
colorerFn: nsColorer,
|
title: "ServiceAccounts",
|
||||||
},
|
api: "",
|
||||||
"pdb": {
|
viewFn: newResourceView,
|
||||||
title: "PodDisruptionBudgets",
|
listFn: resource.NewServiceAccountList,
|
||||||
api: "v1.beta1",
|
enterFn: showSAPolicy,
|
||||||
viewFn: newResourceView,
|
}
|
||||||
listFn: resource.NewPDBList,
|
m["sec"] = resCmd{
|
||||||
colorerFn: pdbColorer,
|
title: "Secrets",
|
||||||
},
|
api: "",
|
||||||
"po": {
|
viewFn: newSecretView,
|
||||||
title: "Pods",
|
listFn: resource.NewSecretList,
|
||||||
api: "",
|
}
|
||||||
viewFn: newPodView,
|
m["svc"] = resCmd{
|
||||||
listFn: resource.NewPodList,
|
title: "Services",
|
||||||
colorerFn: podColorer,
|
api: "",
|
||||||
},
|
viewFn: newSvcView,
|
||||||
"pv": {
|
listFn: resource.NewServiceList,
|
||||||
title: "PersistentVolumes",
|
}
|
||||||
api: "",
|
}
|
||||||
viewFn: newResourceView,
|
|
||||||
listFn: resource.NewPersistentVolumeList,
|
func custRes(m map[string]resCmd) {
|
||||||
colorerFn: pvColorer,
|
m["usr"] = resCmd{
|
||||||
},
|
title: "Users",
|
||||||
"pvc": {
|
api: "",
|
||||||
title: "PersistentVolumeClaims",
|
viewFn: newSubjectView,
|
||||||
api: "",
|
}
|
||||||
viewFn: newResourceView,
|
m["grp"] = resCmd{
|
||||||
listFn: resource.NewPersistentVolumeClaimList,
|
title: "Groups",
|
||||||
colorerFn: pvcColorer,
|
api: "",
|
||||||
},
|
viewFn: newSubjectView,
|
||||||
"rb": {
|
}
|
||||||
title: "RoleBindings",
|
m["pf"] = resCmd{
|
||||||
api: "rbac.authorization.k8s.io",
|
title: "PortForward",
|
||||||
viewFn: newResourceView,
|
api: "",
|
||||||
listFn: resource.NewRoleBindingList,
|
viewFn: newForwardView,
|
||||||
enterFn: showRole,
|
}
|
||||||
},
|
m["be"] = resCmd{
|
||||||
"rc": {
|
title: "Benchmark",
|
||||||
title: "ReplicationControllers",
|
api: "",
|
||||||
api: "",
|
viewFn: newBenchView,
|
||||||
viewFn: newResourceView,
|
}
|
||||||
listFn: resource.NewReplicationControllerList,
|
m["sd"] = resCmd{
|
||||||
colorerFn: rsColorer,
|
title: "ScreenDumps",
|
||||||
},
|
api: "",
|
||||||
"ro": {
|
viewFn: newDumpView,
|
||||||
title: "Roles",
|
}
|
||||||
api: "rbac.authorization.k8s.io",
|
}
|
||||||
viewFn: newResourceView,
|
|
||||||
listFn: resource.NewRoleList,
|
func rbacRes(m map[string]resCmd) {
|
||||||
enterFn: showRBAC,
|
m["cr"] = resCmd{
|
||||||
},
|
title: "ClusterRoles",
|
||||||
"rs": {
|
api: "rbac.authorization.k8s.io",
|
||||||
title: "ReplicaSets",
|
viewFn: newResourceView,
|
||||||
api: "apps",
|
listFn: resource.NewClusterRoleList,
|
||||||
viewFn: newReplicaSetView,
|
enterFn: showRBAC,
|
||||||
listFn: resource.NewReplicaSetList,
|
}
|
||||||
colorerFn: rsColorer,
|
m["crb"] = resCmd{
|
||||||
},
|
title: "ClusterRoleBindings",
|
||||||
"sa": {
|
api: "rbac.authorization.k8s.io",
|
||||||
title: "ServiceAccounts",
|
viewFn: newResourceView,
|
||||||
api: "",
|
listFn: resource.NewClusterRoleBindingList,
|
||||||
viewFn: newResourceView,
|
enterFn: showClusterRole,
|
||||||
listFn: resource.NewServiceAccountList,
|
|
||||||
enterFn: showSAPolicy,
|
|
||||||
},
|
|
||||||
"sec": {
|
|
||||||
title: "Secrets",
|
|
||||||
api: "",
|
|
||||||
viewFn: newSecretView,
|
|
||||||
listFn: resource.NewSecretList,
|
|
||||||
},
|
|
||||||
"sts": {
|
|
||||||
title: "StatefulSets",
|
|
||||||
api: "apps",
|
|
||||||
viewFn: newStatefulSetView,
|
|
||||||
listFn: resource.NewStatefulSetList,
|
|
||||||
colorerFn: stsColorer,
|
|
||||||
},
|
|
||||||
"svc": {
|
|
||||||
title: "Services",
|
|
||||||
api: "",
|
|
||||||
viewFn: newSvcView,
|
|
||||||
listFn: resource.NewServiceList,
|
|
||||||
},
|
|
||||||
"usr": {
|
|
||||||
title: "Users",
|
|
||||||
api: "",
|
|
||||||
viewFn: newSubjectView,
|
|
||||||
},
|
|
||||||
"grp": {
|
|
||||||
title: "Groups",
|
|
||||||
api: "",
|
|
||||||
viewFn: newSubjectView,
|
|
||||||
},
|
|
||||||
"pf": {
|
|
||||||
title: "PortForward",
|
|
||||||
api: "",
|
|
||||||
viewFn: newForwardView,
|
|
||||||
},
|
|
||||||
"be": {
|
|
||||||
title: "Benchmark",
|
|
||||||
api: "",
|
|
||||||
viewFn: newBenchView,
|
|
||||||
},
|
|
||||||
"sd": {
|
|
||||||
title: "ScreenDumps",
|
|
||||||
api: "",
|
|
||||||
viewFn: newDumpView,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if c == nil {
|
m["rb"] = resCmd{
|
||||||
return cmds
|
title: "RoleBindings",
|
||||||
|
api: "rbac.authorization.k8s.io",
|
||||||
|
viewFn: newResourceView,
|
||||||
|
listFn: resource.NewRoleBindingList,
|
||||||
|
enterFn: showRole,
|
||||||
}
|
}
|
||||||
|
m["ro"] = resCmd{
|
||||||
|
title: "Roles",
|
||||||
|
api: "rbac.authorization.k8s.io",
|
||||||
|
viewFn: newResourceView,
|
||||||
|
listFn: resource.NewRoleList,
|
||||||
|
enterFn: showRBAC,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiExtRes(m map[string]resCmd) {
|
||||||
|
m["crd"] = resCmd{
|
||||||
|
title: "CustomResourceDefinitions",
|
||||||
|
api: "apiextensions.k8s.io",
|
||||||
|
viewFn: newResourceView,
|
||||||
|
listFn: resource.NewCustomResourceDefinitionList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func batchRes(m map[string]resCmd) {
|
||||||
|
m["cj"] = resCmd{
|
||||||
|
title: "CronJobs",
|
||||||
|
api: "batch",
|
||||||
|
viewFn: newCronJobView,
|
||||||
|
listFn: resource.NewCronJobList,
|
||||||
|
}
|
||||||
|
m["jo"] = resCmd{
|
||||||
|
title: "Jobs",
|
||||||
|
api: "batch",
|
||||||
|
viewFn: newJobView,
|
||||||
|
listFn: resource.NewJobList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appsRes(m map[string]resCmd) {
|
||||||
|
m["dp"] = resCmd{
|
||||||
|
title: "Deployments",
|
||||||
|
api: "apps",
|
||||||
|
viewFn: newDeployView,
|
||||||
|
listFn: resource.NewDeploymentList,
|
||||||
|
colorerFn: dpColorer,
|
||||||
|
}
|
||||||
|
m["rs"] = resCmd{
|
||||||
|
title: "ReplicaSets",
|
||||||
|
api: "apps",
|
||||||
|
viewFn: newReplicaSetView,
|
||||||
|
listFn: resource.NewReplicaSetList,
|
||||||
|
colorerFn: rsColorer,
|
||||||
|
}
|
||||||
|
m["sts"] = resCmd{
|
||||||
|
title: "StatefulSets",
|
||||||
|
api: "apps",
|
||||||
|
viewFn: newStatefulSetView,
|
||||||
|
listFn: resource.NewStatefulSetList,
|
||||||
|
colorerFn: stsColorer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func extRes(m map[string]resCmd) {
|
||||||
|
m["ing"] = resCmd{
|
||||||
|
title: "Ingress",
|
||||||
|
api: "extensions",
|
||||||
|
viewFn: newResourceView,
|
||||||
|
listFn: resource.NewIngressList,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func v1beta1Res(m map[string]resCmd) {
|
||||||
|
m["pdb"] = resCmd{
|
||||||
|
title: "PodDisruptionBudgets",
|
||||||
|
api: "v1.beta1",
|
||||||
|
viewFn: newResourceView,
|
||||||
|
listFn: resource.NewPDBList,
|
||||||
|
colorerFn: pdbColorer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hpaRes(c k8s.Connection, cmds map[string]resCmd) {
|
||||||
rev, ok, err := c.SupportsRes("autoscaling", []string{"v1", "v2beta1", "v2beta2"})
|
rev, ok, err := c.SupportsRes("autoscaling", []string{"v1", "v2beta1", "v2beta2"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Checking HPA")
|
log.Error().Err(err).Msg("Checking HPA")
|
||||||
return cmds
|
return
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Error().Msg("HPA are not supported on this cluster")
|
log.Error().Msg("HPA are not supported on this cluster")
|
||||||
return cmds
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch rev {
|
switch rev {
|
||||||
|
|
@ -351,6 +376,4 @@ func resourceViews(c k8s.Connection) map[string]resCmd {
|
||||||
default:
|
default:
|
||||||
log.Panic().Msgf("K9s unsupported HPA version. Exiting!")
|
log.Panic().Msgf("K9s unsupported HPA version. Exiting!")
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmds
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,14 +49,14 @@ type (
|
||||||
app *appView
|
app *appView
|
||||||
baseTitle string
|
baseTitle string
|
||||||
currentNS string
|
currentNS string
|
||||||
|
data resource.TableData
|
||||||
actions keyActions
|
actions keyActions
|
||||||
|
cmdBuff *cmdBuff
|
||||||
colorerFn colorerFn
|
colorerFn colorerFn
|
||||||
sortFn sortFn
|
sortFn sortFn
|
||||||
cleanseFn cleanseFn
|
cleanseFn cleanseFn
|
||||||
data resource.TableData
|
|
||||||
cmdBuff *cmdBuff
|
|
||||||
sortCol sortColumn
|
|
||||||
filterFn func(string)
|
filterFn func(string)
|
||||||
|
sortCol sortColumn
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -406,7 +406,15 @@ func (v *tableView) doUpdate(data resource.TableData) {
|
||||||
fgColor = v.colorerFn(data.Namespace, data.Rows[sk])
|
fgColor = v.colorerFn(data.Namespace, data.Rows[sk])
|
||||||
}
|
}
|
||||||
for col, field := range data.Rows[sk].Fields {
|
for col, field := range data.Rows[sk].Fields {
|
||||||
v.addBodyCell(data.NumCols, data.Header[col], row, col, field, data.Rows[sk].Deltas[col], fgColor, pads)
|
header := data.Header[col]
|
||||||
|
field, align := v.formatCell(data.NumCols[header], header, field, pads[col])
|
||||||
|
c := tview.NewTableCell(field + deltas(data.Rows[sk].Deltas[col], field))
|
||||||
|
{
|
||||||
|
c.SetExpansion(1)
|
||||||
|
c.SetAlign(align)
|
||||||
|
c.SetTextColor(fgColor)
|
||||||
|
}
|
||||||
|
v.SetCell(row, col, c)
|
||||||
}
|
}
|
||||||
row++
|
row++
|
||||||
}
|
}
|
||||||
|
|
@ -447,7 +455,7 @@ func (v *tableView) addHeaderCell(numCols map[string]bool, col int, name string,
|
||||||
v.SetCell(0, col, c)
|
v.SetCell(0, col, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *tableView) addBodyCell(numCols map[string]bool, header string, row, col int, field, delta string, color tcell.Color, pads maxyPad) {
|
func (v *tableView) formatCell(numerical bool, header, field string, padding int) (string, int) {
|
||||||
if header == "AGE" {
|
if header == "AGE" {
|
||||||
dur, err := time.ParseDuration(field)
|
dur, err := time.ParseDuration(field)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -455,21 +463,16 @@ func (v *tableView) addBodyCell(numCols map[string]bool, header string, row, col
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
field += deltas(delta, field)
|
if numerical || cpuRX.MatchString(header) || memRX.MatchString(header) {
|
||||||
align := tview.AlignLeft
|
return field, tview.AlignRight
|
||||||
if numCols[header] || cpuRX.MatchString(header) || memRX.MatchString(header) {
|
|
||||||
align = tview.AlignRight
|
|
||||||
} else if isASCII(field) {
|
|
||||||
field = pad(field, pads[col])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c := tview.NewTableCell(field)
|
align := tview.AlignLeft
|
||||||
{
|
if isASCII(field) {
|
||||||
c.SetExpansion(1)
|
return pad(field, padding), align
|
||||||
c.SetAlign(align)
|
|
||||||
c.SetTextColor(color)
|
|
||||||
}
|
}
|
||||||
v.SetCell(row, col, c)
|
|
||||||
|
return field, align
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *tableView) defaultSort(rows resource.Rows, sortCol sortColumn) {
|
func (v *tableView) defaultSort(rows resource.Rows, sortCol sortColumn) {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,116 @@ package views
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/derailed/k9s/internal/config"
|
||||||
"github.com/derailed/k9s/internal/resource"
|
"github.com/derailed/k9s/internal/resource"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestTableViewSave(t *testing.T) {
|
||||||
|
v := newTableView(NewApp(config.NewConfig(ks{})), "test")
|
||||||
|
v.baseTitle = "k9s-test"
|
||||||
|
dir := filepath.Join(config.K9sDumpDir, v.app.config.K9s.CurrentCluster)
|
||||||
|
c1, _ := ioutil.ReadDir(dir)
|
||||||
|
v.saveCmd(nil)
|
||||||
|
c2, _ := ioutil.ReadDir(dir)
|
||||||
|
assert.Equal(t, len(c2), len(c1)+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableViewNew(t *testing.T) {
|
||||||
|
v := newTableView(NewApp(config.NewConfig(ks{})), "test")
|
||||||
|
|
||||||
|
data := resource.TableData{
|
||||||
|
Header: resource.Row{"NAMESPACE", "NAME", "FRED", "AGE"},
|
||||||
|
Rows: resource.RowEvents{
|
||||||
|
"ns1/a": &resource.RowEvent{
|
||||||
|
Action: watch.Added,
|
||||||
|
Fields: resource.Row{"ns1", "a", "10", "3m"},
|
||||||
|
Deltas: resource.Row{"", "", "", ""},
|
||||||
|
},
|
||||||
|
"ns1/b": &resource.RowEvent{
|
||||||
|
Action: watch.Added,
|
||||||
|
Fields: resource.Row{"ns1", "b", "15", "1m"},
|
||||||
|
Deltas: resource.Row{"", "", "20", ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumCols: map[string]bool{
|
||||||
|
"FRED": true,
|
||||||
|
},
|
||||||
|
Namespace: "",
|
||||||
|
}
|
||||||
|
v.update(data)
|
||||||
|
assert.Equal(t, 3, v.GetRowCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableViewFilter(t *testing.T) {
|
||||||
|
v := newTableView(NewApp(config.NewConfig(ks{})), "test")
|
||||||
|
|
||||||
|
data := resource.TableData{
|
||||||
|
Header: resource.Row{"NAMESPACE", "NAME", "FRED", "AGE"},
|
||||||
|
Rows: resource.RowEvents{
|
||||||
|
"ns1/blee": &resource.RowEvent{
|
||||||
|
Action: watch.Added,
|
||||||
|
Fields: resource.Row{"ns1", "blee", "10", "3m"},
|
||||||
|
Deltas: resource.Row{"", "", "", ""},
|
||||||
|
},
|
||||||
|
"ns1/fred": &resource.RowEvent{
|
||||||
|
Action: watch.Added,
|
||||||
|
Fields: resource.Row{"ns1", "fred", "15", "1m"},
|
||||||
|
Deltas: resource.Row{"", "", "20", ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumCols: map[string]bool{
|
||||||
|
"FRED": true,
|
||||||
|
},
|
||||||
|
Namespace: "",
|
||||||
|
}
|
||||||
|
v.update(data)
|
||||||
|
v.cmdBuff.setActive(true)
|
||||||
|
v.cmdBuff.buff = []rune("blee")
|
||||||
|
v.filterCmd(nil)
|
||||||
|
assert.Equal(t, 2, v.GetRowCount())
|
||||||
|
v.resetCmd(nil)
|
||||||
|
assert.Equal(t, 3, v.GetRowCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTableViewSort(t *testing.T) {
|
||||||
|
v := newTableView(NewApp(config.NewConfig(ks{})), "test")
|
||||||
|
|
||||||
|
data := resource.TableData{
|
||||||
|
Header: resource.Row{"NAMESPACE", "NAME", "FRED", "AGE"},
|
||||||
|
Rows: resource.RowEvents{
|
||||||
|
"ns1/blee": &resource.RowEvent{
|
||||||
|
Action: watch.Added,
|
||||||
|
Fields: resource.Row{"ns1", "blee", "10", "3m"},
|
||||||
|
Deltas: resource.Row{"", "", "", ""},
|
||||||
|
},
|
||||||
|
"ns1/fred": &resource.RowEvent{
|
||||||
|
Action: watch.Added,
|
||||||
|
Fields: resource.Row{"ns1", "fred", "15", "1m"},
|
||||||
|
Deltas: resource.Row{"", "", "20", ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumCols: map[string]bool{
|
||||||
|
"FRED": true,
|
||||||
|
},
|
||||||
|
Namespace: "",
|
||||||
|
}
|
||||||
|
v.update(data)
|
||||||
|
v.sortColCmd(1)(nil)
|
||||||
|
assert.Equal(t, 3, v.GetRowCount())
|
||||||
|
assert.Equal(t, "blee ", v.GetCell(1, 1).Text)
|
||||||
|
|
||||||
|
v.sortInvertCmd(nil)
|
||||||
|
assert.Equal(t, 3, v.GetRowCount())
|
||||||
|
assert.Equal(t, "fred ", v.GetCell(1, 1).Text)
|
||||||
|
}
|
||||||
|
|
||||||
func TestIsSelector(t *testing.T) {
|
func TestIsSelector(t *testing.T) {
|
||||||
uu := map[string]struct {
|
uu := map[string]struct {
|
||||||
sel string
|
sel string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue