cleanup pr #489. May be fixed?? #507

mine
derailed 2020-01-23 15:32:51 -07:00
parent f9e32003b9
commit f4144015dd
17 changed files with 147 additions and 128 deletions

View File

@ -0,0 +1,44 @@
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s_small.png" align="right" width="200" height="auto"/>
# Release v0.13.4
## Notes
Thank you to all that contributed with flushing out issues and enhancements for K9s! I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev and see if we're happier with some of the fixes! If you've filed an issue please help me verify and close. Your support, kindness and awesome suggestions to make K9s better is as ever very much noticed and appreciated!
Also if you dig this tool, please make some noise on social! [@kitesurfer](https://twitter.com/kitesurfer)
---
Maintenance Release!
## GH Sponsors
A Big Thank You to the following folks that I've decided to dig in and give back!! 👏🙏🎊
Thank you for your gesture of kindness and for supporting K9s!! (not to mention for replainishing my liquids during oh-dark-thirty hours 🍺🍹🍸)
* [w11d](https://github.com/w11d)
* [vglen](https://github.com/vglen)
## CPU/MEM Metrics
A small change here based on [Benjamin](https://github.com/binarycoded) excellent PR! We've added 2 new columms for pod/container views to indicate percentages of resources request/limits if set on the containers. The columms have been renamed to represent the resources requests/limits as follows:
| Name | Description | Sort Keys |
|--------|--------------------------------|-----------|
| %CPU/R | Percentage of requested cpu | shift-x |
| %MEM/R | Percentage of requested memory | shift-z |
| %MEM/L | Percentage of limited cpu | ctrl-x |
| %MEM/L | Percentage of limited memory | ctrl-z |
---
## Resolved Bugs/Features
* [Issue #507](https://github.com/derailed/k9s/issues/507) ??May be??
* [PR #489](https://github.com/derailed/k9s/issues/489) ATTA Boy! [Benjamin](https://github.com/binarycoded)
* [PR #491](https://github.com/derailed/k9s/issues/491) Big Thanks! [Bjoern](https://github.com/bjoernmichaelsen)
---
<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2020 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)

View File

@ -22,6 +22,8 @@ const (
longAppDesc = "K9s is a CLI to view and manage your Kubernetes clusters."
)
var _ config.KubeSettings = (*client.Config)(nil)
var (
version, commit, date = "dev", "dev", "n/a"
k9sFlags *config.Flags
@ -33,7 +35,6 @@ var (
Long: longAppDesc,
Run: run,
}
_ config.KubeSettings = &client.Config{}
)
func init() {
@ -84,7 +85,12 @@ func run(cmd *cobra.Command, args []string) {
if err := app.Init(version, *k9sFlags.RefreshRate); err != nil {
panic(err)
}
app.Run()
if err := app.Run(); err != nil {
panic(err)
}
if view.ExitStatus != "" {
panic(view.ExitStatus)
}
}
}
@ -121,8 +127,8 @@ func loadConfiguration() *config.Config {
k9sCfg.SetConnection(client.InitConnectionOrDie(k8sCfg))
// Try to access server version if that fail. Connectivity issue?
if _, err := k9sCfg.GetConnection().ServerVersion(); err != nil {
log.Panic().Msgf("K9s can't connect to cluster -- %s", err)
if !k9sCfg.GetConnection().CheckConnectivity() {
log.Panic().Msgf("K9s can't connect to cluster")
}
log.Info().Msg("✅ Kubernetes connectivity")
if err := k9sCfg.Save(); err != nil {

View File

@ -29,12 +29,6 @@ const (
var supportedMetricsAPIVersions = []string{"v1beta1"}
// Authorizer checks what a user can or cannot do to a resource.
type Authorizer interface {
// CanI returns true if the user can use these actions for a given resource.
CanI(ns, gvr string, verbs []string) (bool, error)
}
// APIClient represents a Kubernetes api client.
type APIClient struct {
client kubernetes.Interface
@ -131,33 +125,26 @@ func (a *APIClient) ValidNamespaces() ([]v1.Namespace, error) {
return nn.Items, nil
}
// IsNamespaced check on server if given resource is namespaced
func (a *APIClient) IsNamespaced(res string) bool {
list, _ := a.CachedDiscoveryOrDie().ServerPreferredResources()
for _, l := range list {
for _, r := range l.APIResources {
if r.Name == res {
return r.Namespaced
}
// CheckConnectivity return true if api server is cool or false otherwise.
// BOZO!! No super sure about this approach either??
func (a *APIClient) CheckConnectivity() (status bool) {
defer func() {
if err := recover(); err != nil {
status = false
}
}
return false
}
}()
// SupportsResource checks for resource supported version against the server.
func (a *APIClient) SupportsResource(group string) bool {
list, err := a.CachedDiscoveryOrDie().ServerPreferredResources()
if err != nil {
log.Error().Err(err).Msg("Unable to dial api server")
return false
client, ok := a.DialOrDie().(*kubernetes.Clientset)
if !ok {
return status
}
for _, l := range list {
log.Debug().Msgf(">>> Group %s", l.GroupVersion)
if l.GroupVersion == group {
return true
}
if _, err := client.ServerVersion(); err != nil {
log.Error().Err(err).Msgf("K9s can't connect to cluster")
} else {
status = true
}
return false
return status
}
// Config return a kubernetes configuration.
@ -317,19 +304,3 @@ func checkMetricsVersion(grp metav1.APIGroup) bool {
return false
}
// SupportsRes checks latest supported version.
func (a *APIClient) SupportsRes(group string, versions []string) (string, bool, error) {
apiGroups, err := a.CachedDiscoveryOrDie().ServerGroups()
if err != nil {
return "", false, err
}
for _, grp := range apiGroups.Groups {
if grp.Name != group {
continue
}
return grp.Versions[len(grp.Versions)-1].Version, true, nil
}
return "", false, nil
}

View File

@ -8,7 +8,6 @@ import (
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
clientcmd "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
@ -32,27 +31,6 @@ func NewConfig(f *genericclioptions.ConfigFlags) *Config {
}
}
// CheckConnectivity return true if api server is cool or false otherwise.
// BOZO!! No super sure about this approach either??
func (c *Config) CheckConnectivity() bool {
cfg, err := c.RESTConfig()
if err != nil {
log.Error().Err(err).Msgf("K9s can't connect to cluster (config)")
return false
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
log.Error().Err(err).Msgf("K9s can't connect to cluster (client)")
return false
}
if _, err := client.ServerVersion(); err != nil {
log.Error().Err(err).Msgf("K9s can't connect to cluster (serverVersion)")
return false
}
return true
}
// Flags returns configuration flags.
func (c *Config) Flags() *genericclioptions.ConfigFlags {
return c.flags

View File

@ -141,10 +141,10 @@ func (g GVRs) Less(i, j int) bool {
// Can determines the available actions for a given resource.
func Can(verbs []string, v string) bool {
if verbs == nil {
return false
return true
}
if len(verbs) == 0 {
return true
return false
}
for _, verb := range verbs {
candidates, err := mapVerb(v)

View File

@ -52,8 +52,13 @@ var (
ReadAllAccess = []string{GetVerb, ListVerb, WatchVerb}
)
// Authorizer checks what a user can or cannot do to a resource.
type Authorizer interface {
// CanI returns true if the user can use these actions for a given resource.
CanI(ns, gvr string, verbs []string) (bool, error)
}
// Connection represents a Kubenetes apiserver connection.
// BOZO!! Refactor!
type Connection interface {
Authorizer
@ -65,12 +70,9 @@ type Connection interface {
MXDial() (*versioned.Clientset, error)
DynDialOrDie() dynamic.Interface
HasMetrics() bool
IsNamespaced(n string) bool
SupportsResource(group string) bool
ValidNamespaces() ([]v1.Namespace, error)
SupportsRes(grp string, versions []string) (string, bool, error)
ServerVersion() (*version.Info, error)
CurrentNamespaceName() (string, error)
CheckConnectivity() bool
}
// CurrentMetrics tracks current cpu/mem.

View File

@ -82,23 +82,19 @@ func (mock *MockConnection) Config() *client.Config {
return ret0
}
func (mock *MockConnection) CurrentNamespaceName() (string, error) {
func (mock *MockConnection) CheckConnectivity() bool {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentNamespaceName", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()})
var ret0 string
var ret1 error
result := pegomock.GetGenericMockFrom(mock).Invoke("CheckConnectivity", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()})
var ret0 bool
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(string)
}
if result[1] != nil {
ret1 = result[1].(error)
ret0 = result[0].(bool)
}
}
return ret0, ret1
return ret0
}
func (mock *MockConnection) DialOrDie() kubernetes.Interface {

View File

@ -36,7 +36,13 @@ func (a *Alias) Clear() {
}
}
// List returns a collection of screen dumps.
// Check verifies an alias is defined for this command.
func (a *Alias) Check(cmd string) bool {
_, ok := a.Aliases.Get(cmd)
return ok
}
// List returns a collection of aliases.
// BOZO!! Already have aliases here. Refact!!
func (a *Alias) List(ctx context.Context, _ string) ([]runtime.Object, error) {
a, ok := ctx.Value(internal.KeyAliases).(*Alias)
@ -73,8 +79,7 @@ func (a *Alias) AsGVR(cmd string) (client.GVR, bool) {
// Get fetch a resource.
func (a *Alias) Get(_ context.Context, _ string) (runtime.Object, error) {
// BOZO!! NYI
panic("NYI!")
return nil, errors.New("NYI!!")
}
// Ensure makes sure alias are loaded.

View File

@ -50,6 +50,7 @@ func (c *conn) RestConfigOrDie() *restclient.Config { return nil }
func (c *conn) MXDial() (*versioned.Clientset, error) { return nil, nil }
func (c *conn) DynDialOrDie() dynamic.Interface { return nil }
func (c *conn) HasMetrics() bool { return false }
func (c *conn) CheckConnectivity() bool { return false }
func (c *conn) IsNamespaced(n string) bool { return false }
func (c *conn) SupportsResource(group string) bool { return false }
func (c *conn) ValidNamespaces() ([]v1.Namespace, error) { return nil, nil }

View File

@ -161,6 +161,7 @@ func loadK9s(m ResourceMetas) {
Name: "containers",
Kind: "Containers",
SingularName: "container",
Verbs: []string{},
Categories: []string{"k9s"},
}
}

View File

@ -75,10 +75,10 @@ func (Container) Header(ns string) HeaderRow {
Header{Name: "PROBES(L:R)"},
Header{Name: "CPU", Align: tview.AlignRight},
Header{Name: "MEM", Align: tview.AlignRight},
Header{Name: "%CPU", Align: tview.AlignRight},
Header{Name: "%MEM", Align: tview.AlignRight},
Header{Name: "%MAX-CPU", Align: tview.AlignRight},
Header{Name: "%MAX-MEM", Align: tview.AlignRight},
Header{Name: "%CPU/R", Align: tview.AlignRight},
Header{Name: "%MEM/R", Align: tview.AlignRight},
Header{Name: "%CPU/L", Align: tview.AlignRight},
Header{Name: "%MEM/L", Align: tview.AlignRight},
Header{Name: "PORTS"},
Header{Name: "AGE", Decorator: AgeDecorator},
}

View File

@ -78,10 +78,10 @@ func (Pod) Header(ns string) HeaderRow {
Header{Name: "RS", Align: tview.AlignRight},
Header{Name: "CPU", Align: tview.AlignRight},
Header{Name: "MEM", Align: tview.AlignRight},
Header{Name: "%CPU", Align: tview.AlignRight},
Header{Name: "%MEM", Align: tview.AlignRight},
Header{Name: "%MAX_CPU", Align: tview.AlignRight},
Header{Name: "%MAX_MEM", Align: tview.AlignRight},
Header{Name: "%CPU/R", Align: tview.AlignRight},
Header{Name: "%MEM/R", Align: tview.AlignRight},
Header{Name: "%CPU/L", Align: tview.AlignRight},
Header{Name: "%MEM/L", Align: tview.AlignRight},
Header{Name: "IP"},
Header{Name: "NODE"},
Header{Name: "QOS"},

View File

@ -18,6 +18,9 @@ import (
"github.com/rs/zerolog/log"
)
// ExitStatus indicates UI exit conditions.
var ExitStatus = ""
const (
splashTime = 1
clusterRefresh = 5 * time.Second
@ -185,10 +188,14 @@ func (a *App) clusterUpdater(ctx context.Context) {
// BOZO!! Refact to use model/view strategy.
func (a *App) refreshClusterInfo() {
if !a.Conn().Config().CheckConnectivity() {
log.Error().Msgf("Something is wrong with the connection. Bailing out!")
if !a.Conn().CheckConnectivity() {
ExitStatus = "Lost K8s connection. Bailing out!"
a.BailOut()
}
// Reload alias
if err := a.command.Reset(); err != nil {
log.Error().Err(err).Msgf("Command reset failed")
}
a.QueueUpdateDraw(func() {
if !a.showHeader {
a.refreshIndicator()
@ -288,7 +295,7 @@ func (a *App) BailOut() {
}
// Run starts the application loop
func (a *App) Run() {
func (a *App) Run() error {
a.Resume()
go func() {
@ -299,11 +306,13 @@ func (a *App) Run() {
}()
if err := a.command.defaultCmd(); err != nil {
panic(err)
return err
}
if err := a.Application.Run(); err != nil {
panic(err)
return err
}
return nil
}
// Status reports a new app status for display.

View File

@ -43,6 +43,16 @@ func (c *Command) Init() error {
return nil
}
// Reset resets Command and reload aliases.
func (c *Command) Reset() error {
c.alias.Clear()
if _, err := c.alias.Ensure(); err != nil {
return err
}
return nil
}
func allowedXRay(gvr client.GVR) bool {
gg := []string{
"v1/pods",
@ -52,7 +62,6 @@ func allowedXRay(gvr client.GVR) bool {
"apps/v1/statefulsets",
"apps/v1/replicasets",
}
for _, g := range gg {
if g == gvr.String() {
return true
@ -117,23 +126,18 @@ func (c *Command) run(cmd, path string, clearStack bool) error {
if !c.app.switchNS(ns) {
return fmt.Errorf("namespace switch failed for ns %q", ns)
}
if !c.alias.Check(cmds[0]) {
return fmt.Errorf("Huh? `%s` Command not found", cmd)
}
return c.exec(cmd, gvr, c.componentFor(gvr, path, v), clearStack)
}
}
// Reset resets Command and reload aliases.
func (c *Command) Reset() error {
c.alias.Clear()
if _, err := c.alias.Ensure(); err != nil {
return err
}
return nil
}
func (c *Command) defaultCmd() error {
return c.run(c.app.Config.ActiveView(), "", true)
if err := c.run(c.app.Config.ActiveView(), "", true); err != nil {
log.Error().Err(err).Msgf("Saved command failed. Loading default view")
}
return c.run("pod", "", true)
}
func (c *Command) specialCmd(cmd string) bool {
@ -204,7 +208,7 @@ func (c *Command) componentFor(gvr, path string, v *MetaViewer) ResourceViewer {
func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) error {
if comp == nil {
return fmt.Errorf("No component given for %s", gvr)
return fmt.Errorf("No component found for %s", gvr)
}
c.app.Flash().Infof("Viewing %s...", client.NewGVR(gvr).R())
c.app.Config.SetActiveView(cmd)

View File

@ -40,12 +40,14 @@ func (c *Container) Name() string { return containerTitle }
func (c *Container) bindKeys(aa ui.KeyActions) {
aa.Delete(tcell.KeyCtrlSpace, ui.KeySpace)
aa.Add(ui.KeyActions{
ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true),
ui.KeyS: ui.NewKeyAction("Shell", c.shellCmd, true),
ui.KeyShiftC: ui.NewKeyAction("Sort CPU", c.GetTable().SortColCmd(6, false), false),
ui.KeyShiftM: ui.NewKeyAction("Sort MEM", c.GetTable().SortColCmd(7, false), false),
ui.KeyShiftX: ui.NewKeyAction("Sort CPU%", c.GetTable().SortColCmd(8, false), false),
ui.KeyShiftZ: ui.NewKeyAction("Sort MEM%", c.GetTable().SortColCmd(9, false), false),
ui.KeyShiftF: ui.NewKeyAction("PortForward", c.portFwdCmd, true),
ui.KeyS: ui.NewKeyAction("Shell", c.shellCmd, true),
ui.KeyShiftC: ui.NewKeyAction("Sort CPU", c.GetTable().SortColCmd(6, false), false),
ui.KeyShiftM: ui.NewKeyAction("Sort MEM", c.GetTable().SortColCmd(7, false), false),
ui.KeyShiftX: ui.NewKeyAction("Sort %CPU (REQ)", c.GetTable().SortColCmd(8, false), false),
ui.KeyShiftZ: ui.NewKeyAction("Sort %MEM (REQ)", c.GetTable().SortColCmd(9, false), false),
tcell.KeyCtrlX: ui.NewKeyAction("Sort %CPU (LIM)", c.GetTable().SortColCmd(8, false), false),
tcell.KeyCtrlZ: ui.NewKeyAction("Sort %MEM (LIM)", c.GetTable().SortColCmd(9, false), false),
})
}

View File

@ -13,5 +13,5 @@ func TestContainerNew(t *testing.T) {
assert.Nil(t, c.Init(makeCtx()))
assert.Equal(t, "Containers", c.Name())
assert.Equal(t, 11, len(c.Hints()))
assert.Equal(t, 13, len(c.Hints()))
}

View File

@ -46,10 +46,10 @@ func (p *Pod) bindKeys(aa ui.KeyActions) {
ui.KeyShiftT: ui.NewKeyAction("Sort Restart", p.GetTable().SortColCmd(3, false), false),
ui.KeyShiftC: ui.NewKeyAction("Sort CPU", p.GetTable().SortColCmd(4, false), false),
ui.KeyShiftM: ui.NewKeyAction("Sort MEM", p.GetTable().SortColCmd(5, false), false),
ui.KeyShiftX: ui.NewKeyAction("Sort CPU%", p.GetTable().SortColCmd(6, false), false),
ui.KeyShiftZ: ui.NewKeyAction("Sort MEM%", p.GetTable().SortColCmd(7, false), false),
tcell.KeyCtrlX: ui.NewKeyAction("Sort CPU% LIMITS", p.GetTable().SortColCmd(8, false), false),
tcell.KeyCtrlZ: ui.NewKeyAction("Sort MEM% LIMITS", p.GetTable().SortColCmd(9, false), false),
ui.KeyShiftX: ui.NewKeyAction("Sort %CPU (REQ)", p.GetTable().SortColCmd(6, false), false),
ui.KeyShiftZ: ui.NewKeyAction("Sort %MEM (REQ)", p.GetTable().SortColCmd(7, false), false),
tcell.KeyCtrlX: ui.NewKeyAction("Sort %CPU (LIM)", p.GetTable().SortColCmd(8, false), false),
tcell.KeyCtrlZ: ui.NewKeyAction("Sort %MEM (LIM)", p.GetTable().SortColCmd(9, false), false),
ui.KeyShiftI: ui.NewKeyAction("Sort IP", p.GetTable().SortColCmd(10, true), false),
ui.KeyShiftO: ui.NewKeyAction("Sort Node", p.GetTable().SortColCmd(11, true), false),
})