switch over the kubectl logs vs k9s logview + bug fixes

mine
derailed 2019-03-01 14:25:15 -07:00
parent 6cdccf1d53
commit e41b141edb
16 changed files with 179 additions and 89 deletions

View File

@ -0,0 +1,30 @@
# Release v0.2.0
## Notes
Thank you to all that contributed with flushing out issues with 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.
Thank you so much for your support!!
---
## Change Logs
+ [Feature #97](https://github.com/derailed/k9s/issues/97)
Changed log view to now use kubectl logs shell command.
There was some issues with the previous implementation with missing info and panics.
NOTE! User must type Ctrl-C to exit the logs and navigate back to K9s
+ Reordered containers to show spec.containers first vs spec.initcontainers.
+ [Feature #29]((https://github.com/derailed/k9s/issues/29))
Side effect of #97 Log coloring if present, will now show in the terminal.
---
## Resolved Bugs
* [Issue #99](https://github.com/derailed/k9s/issues/99)
* [Issue #100](https://github.com/derailed/k9s/issues/100)

1
go.sum
View File

@ -139,6 +139,7 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e h1:XEcLGV2fKy3FrsoJVCkX+lMhqc9Suj7J5L/wldA1wu4=
golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
google.golang.org/api v0.0.0-20181220000619-583d854617af h1:iQMS7JKv/0w/iiWf1M49Cg3dmOkBoBZT5KheqPDpaac= google.golang.org/api v0.0.0-20181220000619-583d854617af h1:iQMS7JKv/0w/iiWf1M49Cg3dmOkBoBZT5KheqPDpaac=

View File

@ -105,6 +105,9 @@ func initK9sConfig() {
if c, ok := cfg.Contexts[ctx]; ok { if c, ok := cfg.Contexts[ctx]; ok {
config.Root.K9s.CurrentCluster = c.Cluster config.Root.K9s.CurrentCluster = c.Cluster
if len(c.Namespace) != 0 {
config.Root.SetActiveNamespace(c.Namespace)
}
} else { } else {
panic(fmt.Sprintf("The specified context `%s does not exists in kubeconfig", cfg.CurrentContext)) panic(fmt.Sprintf("The specified context `%s does not exists in kubeconfig", cfg.CurrentContext))
} }
@ -134,6 +137,8 @@ func run(cmd *cobra.Command, args []string) {
{ {
app.Init(version, refreshRate, k8sFlags) app.Init(version, refreshRate, k8sFlags)
defer func() { defer func() {
// Clear screen
print("\033[H\033[2J")
if err := recover(); err != nil { if err := recover(); err != nil {
app.Stop() app.Stop()
fmt.Println(err) fmt.Println(err)

View File

@ -75,12 +75,13 @@ func (c *Config) FavNamespaces() []string {
} }
// SetActiveNamespace set the active namespace in the current cluster. // SetActiveNamespace set the active namespace in the current cluster.
func (c *Config) SetActiveNamespace(ns string) { func (c *Config) SetActiveNamespace(ns string) error {
log.Debugf("Setting active namespace `%s", ns)
if c.K9s.ActiveCluster() != nil { if c.K9s.ActiveCluster() != nil {
c.K9s.ActiveCluster().Namespace.SetActive(ns) return c.K9s.ActiveCluster().Namespace.SetActive(ns, c.settings)
} else {
log.Debug("Doh! no active cluster. unable to set active namespace")
} }
log.Error("Doh! no active cluster. unable to set active namespace")
return fmt.Errorf("no active cluster. unable to set active namespace")
} }
// ActiveView returns the active view in the current cluster. // ActiveView returns the active view in the current cluster.

View File

@ -130,6 +130,7 @@ func TestConfigSaveFile(t *testing.T) {
m.When(ksMock.CurrentClusterName()).ThenReturn("minikube", nil) m.When(ksMock.CurrentClusterName()).ThenReturn("minikube", nil)
m.When(ksMock.CurrentNamespaceName()).ThenReturn("default", nil) m.When(ksMock.CurrentNamespaceName()).ThenReturn("default", nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"minikube", "fred", "blee"}, nil) m.When(ksMock.ClusterNames()).ThenReturn([]string{"minikube", "fred", "blee"}, nil)
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"default"}, nil)
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(ksMock)
cfg.Load("test_assets/k9s.yml") cfg.Load("test_assets/k9s.yml")
@ -153,6 +154,7 @@ func TestConfigReset(t *testing.T) {
m.When(ksMock.CurrentClusterName()).ThenReturn("blee", nil) m.When(ksMock.CurrentClusterName()).ThenReturn("blee", nil)
m.When(ksMock.CurrentNamespaceName()).ThenReturn("default", nil) m.When(ksMock.CurrentNamespaceName()).ThenReturn("default", nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"blee"}, nil) m.When(ksMock.ClusterNames()).ThenReturn([]string{"blee"}, nil)
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"default"}, nil)
cfg := config.NewConfig(ksMock) cfg := config.NewConfig(ksMock)
cfg.Load("test_assets/k9s.yml") cfg.Load("test_assets/k9s.yml")

View File

@ -80,8 +80,5 @@ func (k *K9s) Validate(ks KubeSettings) {
if _, ok := k.Clusters[k.CurrentCluster]; !ok { if _, ok := k.Clusters[k.CurrentCluster]; !ok {
k.Clusters[k.CurrentCluster] = NewCluster() k.Clusters[k.CurrentCluster] = NewCluster()
} }
k.Clusters[k.CurrentCluster].Validate(ks)
if ns, err := ks.CurrentNamespaceName(); err == nil && len(ns) != 0 {
k.Clusters[k.CurrentCluster].Namespace.Active = ns
}
} }

View File

@ -27,6 +27,7 @@ func NewNamespace() *Namespace {
// Validate a namespace is setup correctly // Validate a namespace is setup correctly
func (n *Namespace) Validate(ks KubeSettings) { func (n *Namespace) Validate(ks KubeSettings) {
log.Debug("Validating favorites...", n.Active)
nn, err := ks.NamespaceNames() nn, err := ks.NamespaceNames()
if err != nil { if err != nil {
return return
@ -46,9 +47,11 @@ func (n *Namespace) Validate(ks KubeSettings) {
} }
// SetActive set the active namespace. // SetActive set the active namespace.
func (n *Namespace) SetActive(ns string) { func (n *Namespace) SetActive(ns string, ks KubeSettings) error {
log.Debug("Setting active ns ", ns)
n.Active = ns n.Active = ns
n.addFavNS(ns) n.addFavNS(ns)
return nil
} }
func (n *Namespace) isAllNamespace() bool { func (n *Namespace) isAllNamespace() bool {

View File

@ -52,6 +52,7 @@ func TestNSValidateNoNS(t *testing.T) {
} }
func TestNSSetActive(t *testing.T) { func TestNSSetActive(t *testing.T) {
allNS := []string{"ns4", "ns3", "ns2", "ns1", "all", "default"}
uu := []struct { uu := []struct {
ns string ns string
fav []string fav []string
@ -60,30 +61,30 @@ func TestNSSetActive(t *testing.T) {
{"ns1", []string{"ns1", "all", "default"}}, {"ns1", []string{"ns1", "all", "default"}},
{"ns2", []string{"ns2", "ns1", "all", "default"}}, {"ns2", []string{"ns2", "ns1", "all", "default"}},
{"ns3", []string{"ns3", "ns2", "ns1", "all", "default"}}, {"ns3", []string{"ns3", "ns2", "ns1", "all", "default"}},
{"ns4", []string{"ns4", "ns3", "ns2", "ns1", "all", "default"}}, {"ns4", allNS},
} }
ksMock := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn(allNS, nil)
ns := config.NewNamespace() ns := config.NewNamespace()
for _, u := range uu { for _, u := range uu {
ns.SetActive(u.ns) err := ns.SetActive(u.ns, ksMock)
assert.Nil(t, err)
assert.Equal(t, u.ns, ns.Active) assert.Equal(t, u.ns, ns.Active)
assert.Equal(t, u.fav, ns.Favorites) assert.Equal(t, u.fav, ns.Favorites)
} }
} }
func TestNSRmFavNS(t *testing.T) { func TestNSValidateRmFavs(t *testing.T) {
ns := config.NewNamespace() allNS := []string{"default", "kube-system"}
uu := []struct {
ns string
fav []string
}{
{"all", []string{"default", "kube-system"}},
{"kube-system", []string{"default"}},
{"blee", []string{"default"}},
}
for _, u := range uu { ksMock := NewMockKubeSettings()
ns.SetActive(u.ns) m.When(ksMock.NamespaceNames()).ThenReturn(allNS, nil)
assert.Equal(t, u.ns, ns.Active)
} ns := config.NewNamespace()
ns.Favorites = []string{"default", "fred", "blee"}
ns.Validate(ksMock)
assert.Equal(t, []string{"default"}, ns.Favorites)
} }

View File

@ -211,19 +211,18 @@ func (c *Config) CurrentNamespaceName() (string, error) {
return ctx.Namespace, nil return ctx.Namespace, nil
} }
} }
return "", nil return "", fmt.Errorf("No active namespace specified")
} }
// NamespaceNames fetch all available namespaces on current cluster. // NamespaceNames fetch all available namespaces on current cluster.
func (c *Config) NamespaceNames() ([]string, error) { func (c *Config) NamespaceNames() ([]string, error) {
var nn []string
ll, err := NewNamespace().List("") ll, err := NewNamespace().List("")
if err != nil { if err != nil {
return nn, err return []string{}, err
} }
nn = make([]string, len(nn)) nn := make([]string, 0, len(ll))
for i, n := range ll { for _, n := range ll {
nn[i] = n.(v1.Namespace).Name nn = append(nn, n.(v1.Namespace).Name)
} }
return nn, nil return nn, nil
} }

View File

@ -2,6 +2,7 @@ package k8s_test
import ( import (
"errors" "errors"
"fmt"
"testing" "testing"
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
@ -68,15 +69,17 @@ func TestConfigCurrentNamespace(t *testing.T) {
uu := []struct { uu := []struct {
flags *genericclioptions.ConfigFlags flags *genericclioptions.ConfigFlags
namespace string namespace string
err error
}{ }{
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, ""}, {&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, "", fmt.Errorf("No active namespace specified")},
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Namespace: &name}, "blee"}, {&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Namespace: &name}, "blee", nil},
} }
for _, u := range uu { for _, u := range uu {
cfg := k8s.NewConfig(u.flags) cfg := k8s.NewConfig(u.flags)
ns, err := cfg.CurrentNamespaceName() ns, err := cfg.CurrentNamespaceName()
assert.Nil(t, err) fmt.Println("CRap", ns, err)
assert.Equal(t, u.err, err)
assert.Equal(t, u.namespace, ns) assert.Equal(t, u.namespace, ns)
} }
} }

View File

@ -66,14 +66,14 @@ func (*Pod) Containers(ns, n string, includeInit bool) ([]string, error) {
return cc, err return cc, err
} }
for _, c := range po.Spec.Containers {
cc = append(cc, c.Name)
}
if includeInit { if includeInit {
for _, c := range po.Spec.InitContainers { for _, c := range po.Spec.InitContainers {
cc = append(cc, c.Name) cc = append(cc, c.Name)
} }
} }
for _, c := range po.Spec.Containers {
cc = append(cc, c.Name)
}
return cc, nil return cc, nil
} }

View File

@ -63,9 +63,11 @@ func (v *cmdView) active(f bool) {
v.activated = f v.activated = f
if f { if f {
log.Debug("CmdView was activated...") log.Debug("CmdView was activated...")
v.SetBackgroundColor(tcell.ColorDodgerBlue)
v.activate() v.activate()
} else { } else {
log.Debug("CmdView was deactivated!") log.Debug("CmdView was deactivated!")
v.SetBackgroundColor(tcell.ColorDefault)
v.Clear() v.Clear()
} }
} }

View File

@ -1,28 +1,61 @@
package views package views
import ( import (
"context"
"errors"
"os" "os"
"os/exec" "os/exec"
"os/signal"
"syscall"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
) )
func run(app *appView, args ...string) bool { func runK(app *appView, args ...string) bool {
bin, err := exec.LookPath("kubectl")
if err != nil {
log.Error("Unable to find kubeclt command in path")
return false
}
log.Debugf("Running command > %s %s", bin, args)
return app.Suspend(func() { return app.Suspend(func() {
if err := execute(args...); err != nil { if err := execute(bin, args...); err != nil {
log.Error("Command failed:", err, args) log.Errorf("Command exited: %T %v %v", err, err, args)
app.flash(flashErr, "Doh! command failed", err.Error()) app.flash(flashErr, err.Error())
} }
log.Debug("Command exec successfully!")
}) })
} }
func execute(args ...string) error { func run1(app *appView, bin string, args ...string) bool {
bin, err := exec.LookPath("kubectl") return app.Suspend(func() {
if err != nil { if err := execute(bin, args...); err != nil {
return err log.Errorf("Command exited: %T %v %v", err, err, args)
} app.flash(flashErr, "Command exited: ", err.Error())
}
})
}
func execute(bin string, args ...string) error {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
log.Debug("Command canceled with signal!")
cancel()
}()
cmd := exec.Command(bin, args...) cmd := exec.Command(bin, args...)
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
return cmd.Run() err := cmd.Run()
log.Debug("Command return status ", err)
select {
case <-ctx.Done():
return errors.New("canceled by operator")
default:
return err
}
} }

View File

@ -39,10 +39,12 @@ func (v *namespaceView) useNamespace(evt *tcell.EventKey) *tcell.EventKey {
return evt return evt
} }
ns := v.getSelectedItem() ns := v.getSelectedItem()
config.Root.SetActiveNamespace(ns) if err := config.Root.SetActiveNamespace(ns); err != nil {
v.app.flash(flashErr, err.Error())
} else {
v.app.flash(flashInfo, fmt.Sprintf("Namespace %s is now active!", ns))
}
config.Root.Save() config.Root.Save()
v.refresh()
v.app.flash(flashInfo, fmt.Sprintf("Setting namespace `%s as your default namespace", ns))
return nil return nil
} }

View File

@ -1,6 +1,8 @@
package views package views
import ( import (
"fmt"
"github.com/derailed/k9s/internal/resource" "github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell" "github.com/gdamore/tcell"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
@ -57,26 +59,6 @@ func (v *podView) getSelection() string {
// Handlers... // Handlers...
func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error(err)
return evt
}
l := v.GetPrimitive("logs").(*logsView)
l.deleteAllPages()
for _, c := range cc {
l.addContainer(c)
}
v.switchPage("logs")
l.init()
return nil
}
// func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey { // func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
// if !v.rowSelected() { // if !v.rowSelected() {
// return evt // return evt
@ -84,22 +66,49 @@ func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
// cc, err := fetchContainers(v.list, v.selectedItem, true) // cc, err := fetchContainers(v.list, v.selectedItem, true)
// if err != nil { // if err != nil {
// v.app.flash(flashErr, err.Error()) // v.app.flash(flashErr, err.Error())
// log.Error("Error fetching containers", err) // log.Error(err)
// return evt // return evt
// } // }
// if len(cc) == 1 { // l := v.GetPrimitive("logs").(*logsView)
// v.showLogs(v.selectedItem, "") // l.deleteAllPages()
// } else { // for _, c := range cc {
// p := v.GetPrimitive("choose").(*selectList) // l.addContainer(c)
// p.populate(cc)
// p.SetSelectedFunc(func(i int, t, d string, r rune) {
// v.showLogs(v.selectedItem, t)
// })
// v.switchPage("choose")
// } // }
// return evt // v.switchPage("logs")
// l.init()
// return nil
// } // }
func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() {
return evt
}
previous := false
if evt.Rune() == 'p' {
log.Debug("Previous logs detected")
previous = true
}
cc, err := fetchContainers(v.list, v.selectedItem, true)
if err != nil {
v.app.flash(flashErr, err.Error())
log.Error("Error fetching containers", err)
return evt
}
if len(cc) == 1 {
v.showLogs(v.selectedItem, "", previous)
} else {
p := v.GetPrimitive("choose").(*selectList)
p.populate(cc)
p.SetSelectedFunc(func(i int, t, d string, r rune) {
v.showLogs(v.selectedItem, t, previous)
})
v.switchPage("choose")
}
return evt
}
func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey { func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
if !v.rowSelected() { if !v.rowSelected() {
return evt return evt
@ -113,7 +122,6 @@ func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
if len(cc) == 1 { if len(cc) == 1 {
v.shellIn(v.selectedItem, "") v.shellIn(v.selectedItem, "")
} else { } else {
// v.showPicker(cc)
p := v.GetPrimitive("choose").(*selectList) p := v.GetPrimitive("choose").(*selectList)
p.populate(cc) p.populate(cc)
p.SetSelectedFunc(func(i int, t, d string, r rune) { p.SetSelectedFunc(func(i int, t, d string, r rune) {
@ -138,17 +146,20 @@ func (v *podView) shellIn(path, co string) {
} }
args = append(args, "--", "sh") args = append(args, "--", "sh")
log.Debug("Shell args", args) log.Debug("Shell args", args)
run(v.app, args...) runK(v.app, args...)
} }
func (v *podView) showLogs(path, co string) { func (v *podView) showLogs(path, co string, previous bool) {
ns, po := namespaced(path) ns, po := namespaced(path)
args := []string{"logs", "-f", "-n", ns, po} args := make([]string, 0, 10)
args = append(args, "logs", "-f")
if len(co) != 0 { if len(co) != 0 {
args = append(args, "-c", co) args = append(args, "-c", co)
v.app.flash(flashInfo, fmt.Sprintf("Viewing logs from container %s on pod %s", co, po))
} else {
v.app.flash(flashInfo, fmt.Sprintf("Viewing logs from pod %s", po))
} }
log.Debug("Logs Args", args) runK(v.app, append(args, "-n", ns, po)...)
run(v.app, args...)
} }
func (v *podView) extraActions(aa keyActions) { func (v *podView) extraActions(aa keyActions) {

View File

@ -187,7 +187,7 @@ func (v *resourceView) editCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt return evt
} }
ns, s := namespaced(v.selectedItem) ns, s := namespaced(v.selectedItem)
run(v.app, "edit", v.list.GetName(), "-n", ns, s) runK(v.app, "edit", v.list.GetName(), "-n", ns, s)
return evt return evt
} }