fixed ns + klog stderr

mine
derailed 2019-05-06 10:33:11 -06:00
parent 5db91d79a6
commit a52cf37ff1
20 changed files with 248 additions and 31 deletions

View File

@ -154,7 +154,6 @@ This initial drop is brittle. K9s will most likely blow up...
1. You're running older versions of Kubernetes. K9s works best Kubernetes 1.12+.
1. You don't have enough RBAC fu to manage your cluster (see RBAC section below).
1. Your cluster does not run a metric server.
---
@ -182,15 +181,15 @@ rules:
# Grants RO access to cluster resources node and namespace
- apiGroups: [""]
resources: ["nodes", "namespaces"]
verbs: ["get", "list"]
verbs: ["get", "list", "watch"]
# Grants RO access to RBAC resources
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterroles", "roles", "clusterrolebindings", "rolebindings"]
verbs: ["get", "list"]
verbs: ["get", "list", "watch"]
# Grants RO access to CRD resources
- apiGroups: ["apiextensions.k8s.io"]
resources: ["customresourcedefinitions"]
verbs: ["get", "list"]
verbs: ["get", "list", "watch"]
# Grants RO access to netric server
- apiGroups: ["metrics.k8s.io"]
resources: ["nodes", "pods"]
@ -228,7 +227,7 @@ rules:
# Grants RO access to most namespaced resources
- apiGroups: ["", "apps", "autoscaling", "batch", "extensions"]
resources: ["*"]
verbs: ["get", "list"]
verbs: ["get", "list", "watch"]
---
# Sample K9s user RoleBinding

View File

@ -23,14 +23,14 @@ func infoCmd() *cobra.Command {
func printInfo() {
const secFmt = "%-15s "
printLogo()
printLogo(printer.ColorCyan)
printTuple(secFmt, "Configuration", config.K9sConfigFile)
printTuple(secFmt, "Logs", config.K9sLogs)
}
func printLogo() {
func printLogo(color int) {
for _, l := range views.LogoSmall {
fmt.Println(printer.Colorize(l, printer.ColorCyan))
fmt.Println(printer.Colorize(l, color))
}
fmt.Println()
}

View File

@ -56,6 +56,7 @@ func run(cmd *cobra.Command, args []string) {
if err := recover(); err != nil {
log.Error().Msgf("Boom! %v", err)
log.Error().Msg(string(debug.Stack()))
printLogo(printer.ColorRed)
fmt.Printf(printer.Colorize("Boom!! ", printer.ColorRed))
fmt.Println(printer.Colorize(fmt.Sprintf("%v.", err), printer.ColorDarkGray))
}

View File

@ -21,7 +21,7 @@ func versionCmd() *cobra.Command {
func printVersion() {
const secFmt = "%-10s "
printLogo()
printLogo(printer.ColorCyan)
printTuple(secFmt, "Version", version)
printTuple(secFmt, "Commit", commit)
printTuple(secFmt, "Date", date)

1
go.mod
View File

@ -53,6 +53,7 @@ require (
k8s.io/apiserver v0.0.0-20190426133039-accf7b6d6716 // indirect
k8s.io/cli-runtime v0.0.0-20190325194458-f2b4781c3ae1
k8s.io/client-go v10.0.0+incompatible
k8s.io/klog v0.3.0
k8s.io/kube-openapi v0.0.0-20190426233423-c5d3b0f4bee0 // indirect
k8s.io/kubernetes v1.13.5
k8s.io/metrics v0.0.0-20190325194013-29123f6a4aa6

View File

@ -19,13 +19,15 @@ func init() {
func TestConfigValidate(t *testing.T) {
mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
mk := NewMockKubeSettings()
m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
cfg := config.NewConfig(mk)
cfg.SetConnection(mc)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.Validate()
// mc.VerifyWasCalledOnce().ValidNamespaces()
}
func TestConfigLoad(t *testing.T) {

View File

@ -10,10 +10,13 @@ import (
func TestK9sValidate(t *testing.T) {
mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
mk := NewMockKubeSettings()
m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil)
m.When(mk.CurrentClusterName()).ThenReturn("c1", nil)
m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
c := config.NewK9s()
c.Validate(mc, mk)
@ -30,10 +33,13 @@ func TestK9sValidate(t *testing.T) {
func TestK9sValidateBlank(t *testing.T) {
mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
mk := NewMockKubeSettings()
m.When(mk.CurrentContextName()).ThenReturn("ctx1", nil)
m.When(mk.CurrentClusterName()).ThenReturn("c1", nil)
m.When(mk.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
m.When(mk.NamespaceNames(namespaces())).ThenReturn([]string{"default"})
var c config.K9s
c.Validate(mc, mk)

View File

@ -39,6 +39,25 @@ func (mock *MockConnection) Config() *k8s.Config {
return ret0
}
func (mock *MockConnection) CurrentNamespaceName() (string, error) {
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
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(string)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0, ret1
}
func (mock *MockConnection) DialOrDie() kubernetes.Interface {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
@ -320,6 +339,23 @@ func (c *Connection_Config_OngoingVerification) GetCapturedArguments() {
func (c *Connection_Config_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierConnection) CurrentNamespaceName() *Connection_CurrentNamespaceName_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentNamespaceName", params, verifier.timeout)
return &Connection_CurrentNamespaceName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type Connection_CurrentNamespaceName_OngoingVerification struct {
mock *MockConnection
methodInvocations []pegomock.MethodInvocation
}
func (c *Connection_CurrentNamespaceName_OngoingVerification) GetCapturedArguments() {
}
func (c *Connection_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierConnection) DialOrDie() *Connection_DialOrDie_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout)

View File

@ -1,6 +1,8 @@
package config
import (
"fmt"
"github.com/rs/zerolog/log"
)
@ -31,11 +33,10 @@ func (n *Namespace) Validate(c Connection, ks KubeSettings) {
if err != nil {
return
}
nn := ks.NamespaceNames(nns)
if !n.isAllNamespace() && !InList(nn, n.Active) {
log.Debug().Msg("[Config] Validation error active namespace resetting to `default")
n.Active = defaultNS
log.Error().Msgf("[Config] Validation error active namespace `%s does not exists", n.Active)
panic(fmt.Errorf("Invalid namespace. The provided namespace `%s does not exists", n.Active))
}
for _, ns := range n.Favorites {

View File

@ -24,6 +24,12 @@ func TestNSValidate(t *testing.T) {
}
func TestNSValidateMissing(t *testing.T) {
defer func() {
if err := recover(); err == nil {
t.Fatalf("Expected panic on non existing namespace")
}
}()
mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
mk := NewMockKubeSettings()
@ -81,6 +87,8 @@ func TestNSValidateRmFavs(t *testing.T) {
allNS := []string{"default", "kube-system"}
mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
mk := NewMockKubeSettings()
m.When(mk.NamespaceNames(namespaces())).ThenReturn(allNS)
@ -88,5 +96,5 @@ func TestNSValidateRmFavs(t *testing.T) {
ns.Favorites = []string{"default", "fred", "blee"}
ns.Validate(mc, mk)
assert.Equal(t, []string{"fred"}, ns.Favorites)
assert.Equal(t, []string{"default"}, ns.Favorites)
}

View File

@ -60,6 +60,7 @@ type (
SupportsRes(grp string, versions []string) (string, bool)
ServerVersion() (*version.Info, error)
FetchNodes() (*v1.NodeList, error)
CurrentNamespaceName() (string, error)
}
// APIClient represents a Kubernetes api client.
@ -83,6 +84,11 @@ func InitConnectionOrDie(config *Config, logger zerolog.Logger) *APIClient {
return &conn
}
// CurrentNamespaceName return namespace name set via either cli arg or cluster config.
func (a *APIClient) CurrentNamespaceName() (string, error) {
return a.config.CurrentNamespaceName()
}
// ServerVersion returns the current server version info.
func (a *APIClient) ServerVersion() (*version.Info, error) {
return a.DialOrDie().Discovery().ServerVersion()

View File

@ -265,7 +265,7 @@ func (l *list) fetchFromStore(m *wa.Meta, ns string) (Columnars, error) {
res = l.resource.New(r)
pmx, err := m.Get(wa.PodMXIndex, fqn, opts)
if err != nil {
log.Warn().Err(err).Msg("PodMetrics")
log.Warn().Err(err).Msgf("PodMetrics %s", fqn)
}
if mx, ok := pmx.(*mv1beta1.PodMetrics); ok {
res.SetPodMetrics(mx)
@ -275,7 +275,7 @@ func (l *list) fetchFromStore(m *wa.Meta, ns string) (Columnars, error) {
res = l.resource.New(r)
pmx, err := m.Get(wa.PodMXIndex, fqn, opts)
if err != nil {
log.Warn().Err(err).Msg("PodMetrics")
log.Warn().Err(err).Msgf("PodMetrics<container> %s", fqn)
}
if mx, ok := pmx.(*mv1beta1.PodMetrics); ok {
res.SetPodMetrics(mx)

View File

@ -69,6 +69,25 @@ func (mock *MockClusterMeta) ContextName() string {
return ret0
}
func (mock *MockClusterMeta) CurrentNamespaceName() (string, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
}
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
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(string)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0, ret1
}
func (mock *MockClusterMeta) DialOrDie() kubernetes.Interface {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
@ -437,6 +456,23 @@ func (c *ClusterMeta_ContextName_OngoingVerification) GetCapturedArguments() {
func (c *ClusterMeta_ContextName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierClusterMeta) CurrentNamespaceName() *ClusterMeta_CurrentNamespaceName_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentNamespaceName", params, verifier.timeout)
return &ClusterMeta_CurrentNamespaceName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type ClusterMeta_CurrentNamespaceName_OngoingVerification struct {
mock *MockClusterMeta
methodInvocations []pegomock.MethodInvocation
}
func (c *ClusterMeta_CurrentNamespaceName_OngoingVerification) GetCapturedArguments() {
}
func (c *ClusterMeta_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierClusterMeta) DialOrDie() *ClusterMeta_DialOrDie_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout)

View File

@ -39,6 +39,25 @@ func (mock *MockConnection) Config() *k8s.Config {
return ret0
}
func (mock *MockConnection) CurrentNamespaceName() (string, error) {
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
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(string)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0, ret1
}
func (mock *MockConnection) DialOrDie() kubernetes.Interface {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
@ -320,6 +339,23 @@ func (c *Connection_Config_OngoingVerification) GetCapturedArguments() {
func (c *Connection_Config_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierConnection) CurrentNamespaceName() *Connection_CurrentNamespaceName_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentNamespaceName", params, verifier.timeout)
return &Connection_CurrentNamespaceName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type Connection_CurrentNamespaceName_OngoingVerification struct {
mock *MockConnection
methodInvocations []pegomock.MethodInvocation
}
func (c *Connection_CurrentNamespaceName_OngoingVerification) GetCapturedArguments() {
}
func (c *Connection_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierConnection) DialOrDie() *Connection_DialOrDie_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout)

View File

@ -104,19 +104,25 @@ func (v *clusterInfoView) refresh() {
v.GetCell(row, 1).SetText(cluster.Version())
row++
c := v.GetCell(row, 1)
c.SetText(resource.NAValue)
c = v.GetCell(row+1, 1)
c.SetText(resource.NAValue)
nos, err := v.app.informer.List(watch.NodeIndex, "", metav1.ListOptions{})
if err != nil {
log.Warn().Err(err).Msg("List nodes")
log.Warn().Err(err).Msg("ListNodes")
return
}
nmx, err := v.app.informer.List(watch.NodeMXIndex, "", metav1.ListOptions{})
if err != nil {
log.Warn().Err(err).Msg("List node metrics")
log.Warn().Err(err).Msg("ListNodeMetrics")
return
}
var cmx k8s.ClusterMetrics
cluster.Metrics(nos, nmx, &cmx)
c := v.GetCell(row, 1)
c = v.GetCell(row, 1)
cpu := resource.AsPerc(cmx.PercCPU)
c.SetText(cpu + deltas(strip(c.Text), cpu))
row++

View File

@ -69,6 +69,25 @@ func (mock *MockClusterMeta) ContextName() string {
return ret0
}
func (mock *MockClusterMeta) CurrentNamespaceName() (string, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
}
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
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(string)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0, ret1
}
func (mock *MockClusterMeta) DialOrDie() kubernetes.Interface {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterMeta().")
@ -437,6 +456,23 @@ func (c *ClusterMeta_ContextName_OngoingVerification) GetCapturedArguments() {
func (c *ClusterMeta_ContextName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierClusterMeta) CurrentNamespaceName() *ClusterMeta_CurrentNamespaceName_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentNamespaceName", params, verifier.timeout)
return &ClusterMeta_CurrentNamespaceName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type ClusterMeta_CurrentNamespaceName_OngoingVerification struct {
mock *MockClusterMeta
methodInvocations []pegomock.MethodInvocation
}
func (c *ClusterMeta_CurrentNamespaceName_OngoingVerification) GetCapturedArguments() {
}
func (c *ClusterMeta_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierClusterMeta) DialOrDie() *ClusterMeta_DialOrDie_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout)

View File

@ -459,6 +459,7 @@ func namespaced(n string) (string, string) {
func (v *resourceView) refreshActions() {
if v.list.Access(resource.NamespaceAccess) {
if ns, err := v.app.conn().CurrentNamespaceName(); err != nil || ns == "" {
v.namespaces = make(map[int]string, config.MaxFavoritesNS)
v.actions[tcell.Key(numKeys[0])] = newKeyAction(resource.AllNamespace, v.switchNamespaceCmd, true)
v.namespaces[0] = resource.AllNamespace
@ -472,6 +473,7 @@ func (v *resourceView) refreshActions() {
index++
}
}
}
v.actions[tcell.KeyEnter] = newKeyAction("Enter", v.enterCmd, false)
v.actions[tcell.KeyCtrlR] = newKeyAction("Refresh", v.refreshCmd, false)

View File

@ -81,7 +81,7 @@ func (m *Meta) List(res, ns string, opts metav1.ListOptions) (k8s.Collection, er
return i.List(ns, opts), nil
}
return nil, fmt.Errorf("No informer found for resource %s", res)
return nil, fmt.Errorf("No informer found for resource %s:%q", res, ns)
}
// Get a resource by name.
@ -90,7 +90,7 @@ func (m Meta) Get(res, fqn string, opts metav1.GetOptions) (interface{}, error)
return informer.Get(fqn, opts)
}
return nil, fmt.Errorf("No informer found for resource %s", res)
return nil, fmt.Errorf("No informer found for resource %s:%q", res, fqn)
}
// Run starts watching cluster resources.

View File

@ -39,6 +39,25 @@ func (mock *MockConnection) Config() *k8s.Config {
return ret0
}
func (mock *MockConnection) CurrentNamespaceName() (string, error) {
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
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(string)
}
if result[1] != nil {
ret1 = result[1].(error)
}
}
return ret0, ret1
}
func (mock *MockConnection) DialOrDie() kubernetes.Interface {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockConnection().")
@ -320,6 +339,23 @@ func (c *Connection_Config_OngoingVerification) GetCapturedArguments() {
func (c *Connection_Config_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierConnection) CurrentNamespaceName() *Connection_CurrentNamespaceName_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentNamespaceName", params, verifier.timeout)
return &Connection_CurrentNamespaceName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type Connection_CurrentNamespaceName_OngoingVerification struct {
mock *MockConnection
methodInvocations []pegomock.MethodInvocation
}
func (c *Connection_CurrentNamespaceName_OngoingVerification) GetCapturedArguments() {
}
func (c *Connection_CurrentNamespaceName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierConnection) DialOrDie() *Connection_DialOrDie_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout)

View File

@ -2,12 +2,14 @@ package main
import (
"os"
"syscall"
"github.com/derailed/k9s/cmd"
"github.com/derailed/k9s/internal/config"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"k8s.io/klog"
)
func init() {
@ -16,6 +18,9 @@ func init() {
mod := os.O_CREATE | os.O_APPEND | os.O_WRONLY
if file, err := os.OpenFile(config.K9sLogs, mod, config.DefaultFileMod); err == nil {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: file})
// Klogs (of course) want to print stuff to the screen ;(
klog.SetOutput(file)
syscall.Dup2(int(file.Fd()), 2)
} else {
panic(err)
}