switch over the kubectl logs vs k9s logview + bug fixes
parent
6cdccf1d53
commit
e41b141edb
|
|
@ -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
1
go.sum
|
|
@ -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=
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue