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/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-20181219222714-6e267b5cc78e h1:XEcLGV2fKy3FrsoJVCkX+lMhqc9Suj7J5L/wldA1wu4=
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-20181220000619-583d854617af h1:iQMS7JKv/0w/iiWf1M49Cg3dmOkBoBZT5KheqPDpaac=

View File

@ -105,6 +105,9 @@ func initK9sConfig() {
if c, ok := cfg.Contexts[ctx]; ok {
config.Root.K9s.CurrentCluster = c.Cluster
if len(c.Namespace) != 0 {
config.Root.SetActiveNamespace(c.Namespace)
}
} else {
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)
defer func() {
// Clear screen
print("\033[H\033[2J")
if err := recover(); err != nil {
app.Stop()
fmt.Println(err)

View File

@ -75,12 +75,13 @@ func (c *Config) FavNamespaces() []string {
}
// 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 {
c.K9s.ActiveCluster().Namespace.SetActive(ns)
} else {
log.Debug("Doh! no active cluster. unable to set active namespace")
return c.K9s.ActiveCluster().Namespace.SetActive(ns, c.settings)
}
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.

View File

@ -130,6 +130,7 @@ func TestConfigSaveFile(t *testing.T) {
m.When(ksMock.CurrentClusterName()).ThenReturn("minikube", nil)
m.When(ksMock.CurrentNamespaceName()).ThenReturn("default", nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"minikube", "fred", "blee"}, nil)
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"default"}, nil)
cfg := config.NewConfig(ksMock)
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.CurrentNamespaceName()).ThenReturn("default", nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"blee"}, nil)
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"default"}, nil)
cfg := config.NewConfig(ksMock)
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 {
k.Clusters[k.CurrentCluster] = NewCluster()
}
if ns, err := ks.CurrentNamespaceName(); err == nil && len(ns) != 0 {
k.Clusters[k.CurrentCluster].Namespace.Active = ns
}
k.Clusters[k.CurrentCluster].Validate(ks)
}

View File

@ -27,6 +27,7 @@ func NewNamespace() *Namespace {
// Validate a namespace is setup correctly
func (n *Namespace) Validate(ks KubeSettings) {
log.Debug("Validating favorites...", n.Active)
nn, err := ks.NamespaceNames()
if err != nil {
return
@ -46,9 +47,11 @@ func (n *Namespace) Validate(ks KubeSettings) {
}
// 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.addFavNS(ns)
return nil
}
func (n *Namespace) isAllNamespace() bool {

View File

@ -52,6 +52,7 @@ func TestNSValidateNoNS(t *testing.T) {
}
func TestNSSetActive(t *testing.T) {
allNS := []string{"ns4", "ns3", "ns2", "ns1", "all", "default"}
uu := []struct {
ns string
fav []string
@ -60,30 +61,30 @@ func TestNSSetActive(t *testing.T) {
{"ns1", []string{"ns1", "all", "default"}},
{"ns2", []string{"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()
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.fav, ns.Favorites)
}
}
func TestNSRmFavNS(t *testing.T) {
ns := config.NewNamespace()
uu := []struct {
ns string
fav []string
}{
{"all", []string{"default", "kube-system"}},
{"kube-system", []string{"default"}},
{"blee", []string{"default"}},
}
func TestNSValidateRmFavs(t *testing.T) {
allNS := []string{"default", "kube-system"}
for _, u := range uu {
ns.SetActive(u.ns)
assert.Equal(t, u.ns, ns.Active)
}
ksMock := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn(allNS, nil)
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 "", nil
return "", fmt.Errorf("No active namespace specified")
}
// NamespaceNames fetch all available namespaces on current cluster.
func (c *Config) NamespaceNames() ([]string, error) {
var nn []string
ll, err := NewNamespace().List("")
if err != nil {
return nn, err
return []string{}, err
}
nn = make([]string, len(nn))
for i, n := range ll {
nn[i] = n.(v1.Namespace).Name
nn := make([]string, 0, len(ll))
for _, n := range ll {
nn = append(nn, n.(v1.Namespace).Name)
}
return nn, nil
}

View File

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

View File

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

View File

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

View File

@ -1,28 +1,61 @@
package views
import (
"context"
"errors"
"os"
"os/exec"
"os/signal"
"syscall"
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() {
if err := execute(args...); err != nil {
log.Error("Command failed:", err, args)
app.flash(flashErr, "Doh! command failed", err.Error())
if err := execute(bin, args...); err != nil {
log.Errorf("Command exited: %T %v %v", err, err, args)
app.flash(flashErr, err.Error())
}
log.Debug("Command exec successfully!")
})
}
func execute(args ...string) error {
bin, err := exec.LookPath("kubectl")
if err != nil {
return err
}
func run1(app *appView, bin string, args ...string) bool {
return app.Suspend(func() {
if err := execute(bin, args...); err != nil {
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.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
}
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()
v.refresh()
v.app.flash(flashInfo, fmt.Sprintf("Setting namespace `%s as your default namespace", ns))
return nil
}

View File

@ -1,6 +1,8 @@
package views
import (
"fmt"
"github.com/derailed/k9s/internal/resource"
"github.com/gdamore/tcell"
log "github.com/sirupsen/logrus"
@ -57,26 +59,6 @@ func (v *podView) getSelection() string {
// 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 {
// if !v.rowSelected() {
// return evt
@ -84,22 +66,49 @@ func (v *podView) logsCmd(evt *tcell.EventKey) *tcell.EventKey {
// cc, err := fetchContainers(v.list, v.selectedItem, true)
// if err != nil {
// v.app.flash(flashErr, err.Error())
// log.Error("Error fetching containers", err)
// log.Error(err)
// return evt
// }
// if len(cc) == 1 {
// v.showLogs(v.selectedItem, "")
// } else {
// p := v.GetPrimitive("choose").(*selectList)
// p.populate(cc)
// p.SetSelectedFunc(func(i int, t, d string, r rune) {
// v.showLogs(v.selectedItem, t)
// })
// v.switchPage("choose")
// l := v.GetPrimitive("logs").(*logsView)
// l.deleteAllPages()
// for _, c := range cc {
// l.addContainer(c)
// }
// 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 {
if !v.rowSelected() {
return evt
@ -113,7 +122,6 @@ func (v *podView) shellCmd(evt *tcell.EventKey) *tcell.EventKey {
if len(cc) == 1 {
v.shellIn(v.selectedItem, "")
} else {
// v.showPicker(cc)
p := v.GetPrimitive("choose").(*selectList)
p.populate(cc)
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")
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)
args := []string{"logs", "-f", "-n", ns, po}
args := make([]string, 0, 10)
args = append(args, "logs", "-f")
if len(co) != 0 {
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)
run(v.app, args...)
runK(v.app, append(args, "-n", ns, po)...)
}
func (v *podView) extraActions(aa keyActions) {

View File

@ -187,7 +187,7 @@ func (v *resourceView) editCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
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
}