refactor command line args + beefup tests

mine
derailed 2019-02-18 18:09:49 -07:00
parent 52e817bff2
commit b358f3631f
164 changed files with 2105 additions and 1025 deletions

View File

@ -1,24 +0,0 @@
package config_test
import (
"testing"
m "github.com/petergtz/pegomock"
"github.com/derailed/k9s/config"
"github.com/stretchr/testify/assert"
)
func TestClusterValidate(t *testing.T) {
setup(t)
ciMock := NewMockClusterInfo()
m.When(ciMock.AllNamespacesOrDie()).ThenReturn([]string{"ns1", "ns2", "default"})
c := config.NewCluster()
c.Validate(ciMock)
assert.Equal(t, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active)
assert.Equal(t, 2, len(c.Namespace.Favorites))
assert.Equal(t, []string{"all", "default"}, c.Namespace.Favorites)
}

View File

@ -1,108 +0,0 @@
package config_test
import (
"fmt"
"io/ioutil"
"path/filepath"
"testing"
m "github.com/petergtz/pegomock"
"github.com/derailed/k9s/config"
"github.com/stretchr/testify/assert"
)
func TestConfigValidate(t *testing.T) {
setup(t)
assert.Nil(t, config.Load("test_assets/k9s.yml"))
ciMock := NewMockClusterInfo()
m.When(ciMock.AllNamespacesOrDie()).ThenReturn([]string{"ns1", "ns2", "default"})
m.When(ciMock.AllClustersOrDie()).ThenReturn([]string{"c1", "c2"})
config.Root.Validate(ciMock)
}
func TestConfigLoad(t *testing.T) {
assert.Nil(t, config.Load("test_assets/k9s.yml"))
assert.Equal(t, 2, config.Root.K9s.RefreshRate)
assert.Equal(t, 200, config.Root.K9s.LogBufferSize)
ctx := config.Root.K9s.Context
assert.Equal(t, "minikube", ctx.Active)
assert.NotNil(t, ctx.Clusters)
nn := []string{
"default",
"kube-public",
"istio-system",
"all",
"kube-system",
}
assert.Equal(t, "kube-system", ctx.Clusters["minikube"].Namespace.Active)
assert.Equal(t, nn, ctx.Clusters["minikube"].Namespace.Favorites)
assert.Equal(t, "ctx", ctx.Clusters["minikube"].View.Active)
}
func TestConfigLoadOldCfg(t *testing.T) {
assert.Nil(t, config.Load("test_assets/k9s_old.yml"))
}
func TestConfigSaveFile(t *testing.T) {
config.Load("test_assets/k9s.yml")
config.Root.K9s.RefreshRate = 100
config.Root.K9s.LogBufferSize = 500
config.Root.K9s.Context.Active = "fred"
path := filepath.Join("/tmp", "k9s.yml")
err := config.Root.SaveFile(path)
assert.Nil(t, err)
raw, err := ioutil.ReadFile(path)
assert.Nil(t, err)
assert.Equal(t, expectedConfig, string(raw))
}
// Helpers...
func setup(t *testing.T) {
m.RegisterMockTestingT(t)
m.RegisterMockFailHandler(func(m string, i ...int) {
fmt.Println("Boom!", m, i)
})
}
// ----------------------------------------------------------------------------
// Test Data...
var expectedConfig = `k9s:
refreshRate: 100
logBufferSize: 500
context:
active: fred
clusters:
fred:
namespace:
active: default
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: po
minikube:
namespace:
active: kube-system
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: ctx
`

View File

@ -1,76 +0,0 @@
package config
import (
log "github.com/sirupsen/logrus"
)
// Context tracks K9s cluster context configuration.
type Context struct {
Active string `yaml:"active"`
Clusters map[string]*Cluster `yaml:"clusters"`
}
// NewContext creates a new cluster config context.
func NewContext() *Context {
return &Context{Clusters: make(map[string]*Cluster, 1)}
}
// SetActiveCluster set the active cluster.
func (c *Context) SetActiveCluster(s string) {
c.Active = s
if _, ok := c.Clusters[c.Active]; ok {
return
}
c.Clusters[c.Active] = NewCluster()
return
}
// ActiveClusterName returns the currently active cluster name.
func (c *Context) ActiveClusterName() string {
return c.Active
}
// ActiveCluster returns the currently active cluster configuration.
func (c *Context) ActiveCluster() *Cluster {
if cl, ok := c.Clusters[c.Active]; ok {
return cl
}
c.Clusters[c.Active] = NewCluster()
return c.Clusters[c.Active]
}
// Validate this configuration
func (c *Context) Validate(ci ClusterInfo) {
if len(c.Active) == 0 {
c.Active = ci.ActiveClusterOrDie()
}
if c.Clusters == nil {
c.Clusters = make(map[string]*Cluster, 1)
}
cc := ci.AllClustersOrDie()
if len(cc) == 0 {
log.Panic("Unable to find any live clusters in this configuration")
}
if !InList(cc, c.Active) {
c.Active = cc[0]
c.Clusters[cc[0]] = NewCluster()
}
if len(c.Clusters) == 0 {
c.Clusters[c.Active] = NewCluster()
}
for k, cl := range c.Clusters {
if !InList(cc, k) {
delete(c.Clusters, k)
} else {
cl.Validate(ci)
}
}
}
func (c *Context) activeCluster() *Cluster {
return c.Clusters[c.Active]
}

View File

@ -1,54 +0,0 @@
package config
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestHelperInList(t *testing.T) {
uu := []struct {
item string
list []string
expected bool
}{
{"a", []string{}, false},
{"", []string{}, false},
{"", []string{""}, true},
{"a", []string{"a", "b", "c", "d"}, true},
{"z", []string{"a", "b", "c", "d"}, false},
}
for _, u := range uu {
assert.Equal(t, u.expected, InList(u.list, u.item))
}
}
func TestHelperInNSList(t *testing.T) {
uu := []struct {
item string
list []interface{}
expected bool
}{
{
"fred",
[]interface{}{
v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "fred"}},
},
true,
},
{
"blee",
[]interface{}{
v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "fred"}},
},
false,
},
}
for _, u := range uu {
assert.Equal(t, u.expected, InNSList(u.list, u.item))
}
}

View File

@ -1,46 +0,0 @@
package config
const (
refreshRate = 2
logBufferSize = 200
)
// K9s tracks K9s configuration options.
type K9s struct {
RefreshRate int `yaml:"refreshRate"`
LogBufferSize int `yaml:"logBufferSize"`
Context *Context `yaml:"context"`
}
// NewK9s create a new K9s configuration.
func NewK9s() *K9s {
return &K9s{
RefreshRate: refreshRate,
LogBufferSize: logBufferSize,
Context: NewContext(),
}
}
// ActiveCluster return the current Cluster config.
func (k K9s) ActiveCluster() *Cluster {
if k.Context == nil {
k.Context = NewContext()
}
return k.Context.ActiveCluster()
}
// Validate the configuration
func (k K9s) Validate(ci ClusterInfo) {
if k.RefreshRate <= 0 {
k.RefreshRate = refreshRate
}
if k.LogBufferSize <= 0 {
k.LogBufferSize = logBufferSize
}
if k.Context == nil {
k.Context = NewContext()
}
k.Context.Validate(ci)
}

View File

@ -1,24 +0,0 @@
package config_test
import (
"testing"
m "github.com/petergtz/pegomock"
"github.com/derailed/k9s/config"
"github.com/stretchr/testify/assert"
)
func TestK9sValidate(t *testing.T) {
setup(t)
ci := NewMockClusterInfo()
m.When(ci.AllClustersOrDie()).ThenReturn([]string{"c1", "c2"})
m.When(ci.ActiveClusterOrDie()).ThenReturn("c1")
c := config.NewK9s()
c.Validate(ci)
assert.Equal(t, 2, c.RefreshRate)
assert.Equal(t, 200, c.LogBufferSize)
assert.Equal(t, "c1", c.Context.Active)
assert.Equal(t, 1, len(c.Context.Clusters))
}

View File

@ -1,151 +0,0 @@
// Code generated by pegomock. DO NOT EDIT.
// Source: github.com/derailed/k9s/config (interfaces: ClusterInfo)
package config_test
import (
pegomock "github.com/petergtz/pegomock"
"reflect"
"time"
)
type MockClusterInfo struct {
fail func(message string, callerSkip ...int)
}
func NewMockClusterInfo() *MockClusterInfo {
return &MockClusterInfo{fail: pegomock.GlobalFailHandler}
}
func (mock *MockClusterInfo) ActiveClusterOrDie() string {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterInfo().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("ActiveClusterOrDie", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem()})
var ret0 string
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].(string)
}
}
return ret0
}
func (mock *MockClusterInfo) AllClustersOrDie() []string {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterInfo().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("AllClustersOrDie", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem()})
var ret0 []string
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].([]string)
}
}
return ret0
}
func (mock *MockClusterInfo) AllNamespacesOrDie() []string {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockClusterInfo().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("AllNamespacesOrDie", params, []reflect.Type{reflect.TypeOf((*[]string)(nil)).Elem()})
var ret0 []string
if len(result) != 0 {
if result[0] != nil {
ret0 = result[0].([]string)
}
}
return ret0
}
func (mock *MockClusterInfo) VerifyWasCalledOnce() *VerifierClusterInfo {
return &VerifierClusterInfo{
mock: mock,
invocationCountMatcher: pegomock.Times(1),
}
}
func (mock *MockClusterInfo) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierClusterInfo {
return &VerifierClusterInfo{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
}
}
func (mock *MockClusterInfo) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierClusterInfo {
return &VerifierClusterInfo{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
inOrderContext: inOrderContext,
}
}
func (mock *MockClusterInfo) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierClusterInfo {
return &VerifierClusterInfo{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
timeout: timeout,
}
}
type VerifierClusterInfo struct {
mock *MockClusterInfo
invocationCountMatcher pegomock.Matcher
inOrderContext *pegomock.InOrderContext
timeout time.Duration
}
func (verifier *VerifierClusterInfo) ActiveClusterOrDie() *ClusterInfo_ActiveClusterOrDie_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ActiveClusterOrDie", params, verifier.timeout)
return &ClusterInfo_ActiveClusterOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type ClusterInfo_ActiveClusterOrDie_OngoingVerification struct {
mock *MockClusterInfo
methodInvocations []pegomock.MethodInvocation
}
func (c *ClusterInfo_ActiveClusterOrDie_OngoingVerification) GetCapturedArguments() {
}
func (c *ClusterInfo_ActiveClusterOrDie_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierClusterInfo) AllClustersOrDie() *ClusterInfo_AllClustersOrDie_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "AllClustersOrDie", params, verifier.timeout)
return &ClusterInfo_AllClustersOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type ClusterInfo_AllClustersOrDie_OngoingVerification struct {
mock *MockClusterInfo
methodInvocations []pegomock.MethodInvocation
}
func (c *ClusterInfo_AllClustersOrDie_OngoingVerification) GetCapturedArguments() {
}
func (c *ClusterInfo_AllClustersOrDie_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierClusterInfo) AllNamespacesOrDie() *ClusterInfo_AllNamespacesOrDie_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "AllNamespacesOrDie", params, verifier.timeout)
return &ClusterInfo_AllNamespacesOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type ClusterInfo_AllNamespacesOrDie_OngoingVerification struct {
mock *MockClusterInfo
methodInvocations []pegomock.MethodInvocation
}
func (c *ClusterInfo_AllNamespacesOrDie_OngoingVerification) GetCapturedArguments() {
}
func (c *ClusterInfo_AllNamespacesOrDie_OngoingVerification) GetAllCapturedArguments() {
}

View File

@ -1,60 +0,0 @@
package config_test
import (
"testing"
m "github.com/petergtz/pegomock"
"github.com/derailed/k9s/config"
"github.com/stretchr/testify/assert"
)
func TestNSValidate(t *testing.T) {
setup(t)
ns := config.NewNamespace()
ciMock := NewMockClusterInfo()
m.When(ciMock.AllNamespacesOrDie()).ThenReturn([]string{"ns1", "ns2", "default"})
ns.Validate(ciMock)
ciMock.VerifyWasCalledOnce()
assert.Equal(t, "default", ns.Active)
assert.Equal(t, []string{"all", "default"}, ns.Favorites)
}
func TestNSSetActive(t *testing.T) {
uu := []struct {
ns string
fav []string
}{
{"all", []string{"all", "default", "kube-system"}},
{"ns1", []string{"ns1", "all", "default", "kube-system"}},
{"ns2", []string{"ns2", "ns1", "all", "default", "kube-system"}},
{"ns3", []string{"ns3", "ns2", "ns1", "all", "default", "kube-system"}},
{"ns4", []string{"ns4", "ns3", "ns2", "ns1", "all", "default", "kube-system"}},
}
ns := config.NewNamespace()
for _, u := range uu {
ns.SetActive(u.ns)
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"}},
}
for _, u := range uu {
ns.SetActive(u.ns)
assert.Equal(t, u.ns, ns.Active)
}
}

View File

@ -1,28 +0,0 @@
k9s:
refreshRate: 2
logBufferSize: 200
context:
active: minikube
clusters:
minikube:
namespace:
active: kube-system
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: ctx
fred:
namespace:
active: default
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: po

1
go.mod
View File

@ -40,7 +40,6 @@ require (
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.2.2
k8s.io/api v0.0.0-20190202010724-74b699b93c15
k8s.io/apiextensions-apiserver v0.0.0-20190205053453-ba46448aaa9f // indirect
k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467
k8s.io/cli-runtime v0.0.0-20190207094101-a32b78e5dd0a
k8s.io/client-go v10.0.0+incompatible

24
go.sum
View File

@ -7,6 +7,7 @@ git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGy
git.apache.org/thrift.git v0.0.0-20181218151757-9b75e4fe745a/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg=
github.com/Azure/go-autorest v11.4.0+incompatible h1:z3Yr6KYqs0nhSNwqGXEBpWK977hxVqsLv2n9PVYcixY=
github.com/Azure/go-autorest v11.4.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/census-instrumentation/opencensus-proto v0.1.0-0.20181214143942-ba49f56771b8 h1:gUqsFVdUKoRHNg8fkFd8gB5OOEa/g5EwlAHznb4zjbI=
github.com/census-instrumentation/opencensus-proto v0.1.0-0.20181214143942-ba49f56771b8/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -17,6 +18,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/evanphx/json-patch v4.1.0+incompatible h1:K1MDoo4AZ4wU0GIU/fPmtZg7VpzLjCxu+UwBD1FvwOc=
github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
@ -25,6 +27,7 @@ github.com/gdamore/tcell v1.1.0/go.mod h1:tqyG50u7+Ctv1w5VX67kLzKcj9YXR/JSBZQq/+
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
@ -33,6 +36,7 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
@ -40,12 +44,14 @@ github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhp
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.0.0-20190206201033-83b528acebb4 h1:wAcVWwS69gs5c6cFkCa/ns/eaL2gC761nF8Ugvd1dGw=
github.com/gophercloud/gophercloud v0.0.0-20190206201033-83b528acebb4/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.6.2 h1:8KyC64BiO8ndiGHY5DlFWWdangUPC9QHPakFRre/Ud0=
github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
@ -53,25 +59,30 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k8sland/tview v0.1.1 h1:732F8kcz5EjUAsFTZJ5BkJx3n34+EwiQRuDaegeS2yU=
github.com/k8sland/tview v0.1.1/go.mod h1:PwEtOCvGYNgUA2FQuciQBKB6igksu4GHtq3GY6vOkQo=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08 h1:5MnxBC15uMxFv5FY/J/8vzyaBiArCOkMdFT9Jsw78iY=
github.com/lucasb-eyer/go-colorful v0.0.0-20181028223441-12d3b2882a08/go.mod h1:NXg0ArsFk0Y01623LgUqoqcouGDB+PwCCQlrwrG6xJ4=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@ -84,17 +95,23 @@ github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81/go.mod h1:nuBLWZ
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181218105931-67670fe90761 h1:z6tvbDJ5OLJ48FFmnksv04a78maSTRBUIhkdHYV5Y98=
github.com/prometheus/common v0.0.0-20181218105931-67670fe90761/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/rivo/tview v0.0.0-20190124120153-84fdb36408f3/go.mod h1:J4W+hErFfITUbyFAEXizpmkuxX7ZN56dopxHB4XQhMw=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
@ -135,6 +152,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=
@ -151,12 +169,16 @@ google.golang.org/grpc v1.15.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9M
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY=
gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
@ -165,8 +187,6 @@ honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20190202010724-74b699b93c15 h1:AoUGjnJ3PJMFz+Rkp4lx3X+6mPUnY1MESJhbUSGX+pc=
k8s.io/api v0.0.0-20190202010724-74b699b93c15/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
k8s.io/apiextensions-apiserver v0.0.0-20190205053453-ba46448aaa9f h1:+XEx9/2g9PNRGh8cX3wbIUhFvtkglP/GvJfIdX1IuBs=
k8s.io/apiextensions-apiserver v0.0.0-20190205053453-ba46448aaa9f/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE=
k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467 h1:zmz9UYvvXrK/B8EDqFuqreJEaXbIWdzEkNgWrN/Cd3o=
k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/cli-runtime v0.0.0-20190207094101-a32b78e5dd0a h1:MrGQxLLZ09Bl5hYYU9VlKnhY60bpPlYd9yXOPnxkdc0=

31
internal/cmd/info.go Normal file
View File

@ -0,0 +1,31 @@
package cmd
import (
"fmt"
"strings"
"github.com/derailed/k9s/internal/config"
"github.com/spf13/cobra"
)
func infoCmd() *cobra.Command {
return &cobra.Command{
Use: "info",
Short: "Print configuration information",
Long: "Print configuration information",
Run: func(cmd *cobra.Command, args []string) {
const (
cyan = "\033[1;36m%s\033[0m"
green = "\033[1;32m%s\033[0m"
magenta = "\033[1;35m%s\033[0m"
)
fmt.Printf(cyan+"\n", strings.Repeat("-", 80))
fmt.Printf(green+"\n", "🐶 K9s Information")
fmt.Printf(magenta, fmt.Sprintf("%-10s", "LogFile:"))
fmt.Printf("%s\n", config.K9sLogs)
fmt.Printf(magenta, fmt.Sprintf("%-10s", "Config:"))
fmt.Printf("%s\n", config.K9sConfigFile)
fmt.Printf(cyan+"\n", strings.Repeat("-", 80))
},
}
}

View File

@ -2,17 +2,15 @@ package cmd
import (
"fmt"
"strings"
"github.com/derailed/k9s/config"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/views"
"github.com/derailed/k9s/internal/config"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/views"
"github.com/gdamore/tcell"
"github.com/k8sland/tview"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"k8s.io/cli-runtime/pkg/genericclioptions"
_ "k8s.io/client-go/plugin/pkg/client/auth"
)
@ -27,46 +25,20 @@ var (
date = "n/a"
refreshRate int
logLevel string
kubeconfig string
k8sFlags *genericclioptions.ConfigFlags
rootCmd = &cobra.Command{
Use: "k9s",
Short: "A graphical CLI for your Kubernetes cluster management.",
Long: `K9s is a Kubernetes CLI to view and manage your Kubernetes clusters.`,
Long: `K9s is a CLI to view and manage your Kubernetes clusters.`,
Run: run,
}
versionCmd = &cobra.Command{
Use: "version",
Short: "Print version info",
Long: "Prints version info",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Version:%s GitCommit:%s On %s\n", version, commit, date)
},
}
infoCmd = &cobra.Command{
Use: "info",
Short: "Print configuration information",
Long: "Print configuration information",
Run: func(cmd *cobra.Command, args []string) {
const (
cyan = "\033[1;36m%s\033[0m"
green = "\033[1;32m%s\033[0m"
magenta = "\033[1;35m%s\033[0m"
)
fmt.Printf(cyan+"\n", strings.Repeat("-", 80))
fmt.Printf(green+"\n", "🐶 K9s Information")
fmt.Printf(magenta, fmt.Sprintf("%-10s", "LogFile:"))
fmt.Printf("%s\n", config.K9sLogs)
fmt.Printf(magenta, fmt.Sprintf("%-10s", "Config:"))
fmt.Printf("%s\n", config.K9sConfigFile)
fmt.Printf(cyan+"\n", strings.Repeat("-", 80))
},
}
)
var _ config.KubeSettings = &k8s.Config{}
func init() {
rootCmd.AddCommand(versionCmd, infoCmd)
rootCmd.AddCommand(versionCmd(), infoCmd())
rootCmd.Flags().IntVarP(
&refreshRate,
@ -82,12 +54,135 @@ func init() {
"Specify a log level (info, warn, debug, error, fatal, panic, trace)",
)
k8sFlags = genericclioptions.NewConfigFlags(false)
k8sFlags.AddFlags(rootCmd.PersistentFlags())
initK8sFlags()
}
func initK8s() {
k8s.InitConnection(k8sFlags)
func initK8sFlags() {
k8sFlags = genericclioptions.NewConfigFlags(false)
rootCmd.Flags().StringVar(
k8sFlags.KubeConfig,
"kubeconfig",
"",
"Path to the kubeconfig file to use for CLI requests",
)
rootCmd.Flags().StringVar(
k8sFlags.Timeout,
"request-timeout",
"",
"The length of time to wait before giving up on a single server request",
)
rootCmd.Flags().StringVar(
k8sFlags.Context,
"context",
"",
"The name of the kuconfig context to use",
)
rootCmd.Flags().StringVar(
k8sFlags.ClusterName,
"cluster",
"",
"The name of the kubeconfig cluster to use",
)
rootCmd.Flags().StringVar(
k8sFlags.AuthInfoName,
"user",
"",
"The name of the kubeconfig user to use",
)
rootCmd.Flags().BoolVar(
k8sFlags.Insecure,
"insecure-skip-tls-verify",
false,
"If true, the server's caCertFile will not be checked for validity",
)
rootCmd.Flags().StringVar(
k8sFlags.CAFile,
"certificate-authority",
"",
"Path to a cert file for the certificate authority",
)
rootCmd.Flags().StringVar(
k8sFlags.KeyFile,
"client-key",
"",
"Path to a client key file for TLS",
)
rootCmd.Flags().StringVar(
k8sFlags.CertFile,
"client-certificate",
"",
"Path to a client certificate file for TLS",
)
rootCmd.Flags().StringVar(
k8sFlags.BearerToken,
"token",
"",
"Bearer token for authentication to the API server",
)
rootCmd.Flags().StringVar(
k8sFlags.Namespace,
"namespace",
"n",
"If present, the namespace scope for this CLI request",
)
}
func initK9s() {
log.Info("🐶 K9s starting up...")
// Load K9s config file...
cfg := k8s.NewConfig(k8sFlags)
config.Root = config.NewConfig(cfg)
initK9sConfig()
// Init K8s connection...
k8s.InitConnectionOrDie(cfg)
log.Info("✅ Kubernetes connectivity")
config.Root.Save()
}
func initK9sConfig() {
if err := config.Root.Load(config.K9sConfigFile); err != nil {
log.Warnf("Unable to locate K9s config. Generating new configuration...")
}
config.Root.K9s.RefreshRate = refreshRate
cfg, err := k8sFlags.ToRawKubeConfigLoader().RawConfig()
if err != nil {
panic("Invalid configuration. Unable to connect to api")
}
ctx := cfg.CurrentContext
if isSet(k8sFlags.Context) {
ctx = *k8sFlags.Context
}
config.Root.K9s.CurrentContext = ctx
log.Debugf("Active Context `%v`", ctx)
if isSet(k8sFlags.Namespace) {
config.Root.SetActiveNamespace(*k8sFlags.Namespace)
}
if c, ok := cfg.Contexts[ctx]; ok {
config.Root.K9s.CurrentCluster = c.Cluster
} else {
panic(fmt.Sprintf("The specified context `%s does not exists in kubeconfig", cfg.CurrentContext))
}
}
func isSet(s *string) bool {
return s != nil && len(*s) > 0
}
// Execute root command
@ -105,7 +200,7 @@ func run(cmd *cobra.Command, args []string) {
log.SetLevel(level)
log.SetFormatter(&log.TextFormatter{FullTimestamp: true, ForceColors: true})
initK8s()
initK9s()
initStyles()
initKeys()

18
internal/cmd/version.go Normal file
View File

@ -0,0 +1,18 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
func versionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Print version info",
Long: "Prints version info",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Version:%s GitCommit:%s On %s\n", version, commit, date)
},
}
}

View File

@ -12,14 +12,14 @@ func NewCluster() *Cluster {
}
// Validate a cluster config.
func (c *Cluster) Validate(ci ClusterInfo) {
func (c *Cluster) Validate(ks KubeSettings) {
if c.Namespace == nil {
c.Namespace = NewNamespace()
}
c.Namespace.Validate(ci)
c.Namespace.Validate(ks)
if c.View == nil {
c.View = NewView()
}
c.View.Validate(ci)
c.View.Validate()
}

View File

@ -0,0 +1,39 @@
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
)
func TestClusterValidate(t *testing.T) {
setup(t)
ksMock := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil)
c := config.NewCluster()
c.Validate(ksMock)
assert.Equal(t, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active)
assert.Equal(t, 1, len(c.Namespace.Favorites))
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
}
func TestClusterValidateEmpty(t *testing.T) {
setup(t)
ksMock := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil)
var c config.Cluster
c.Validate(ksMock)
assert.Equal(t, "po", c.View.Active)
assert.Equal(t, "default", c.Namespace.Active)
assert.Equal(t, 1, len(c.Namespace.Favorites))
assert.Equal(t, []string{"default"}, c.Namespace.Favorites)
}

View File

@ -1,18 +1,22 @@
package config
// BOZO!! Once yaml is stable implement validation
// go get gopkg.in/validator.v2
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"github.com/derailed/k9s/internal/resource"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
)
var (
// Root K9s configuration.
Root = NewConfig()
Root *Config
// K9sHome represent K9s home directory.
K9sHome = filepath.Join(mustK9sHome(), ".k9s")
// K9sConfigFile represents K9s config file location.
@ -21,46 +25,47 @@ var (
K9sLogs = filepath.Join(os.TempDir(), fmt.Sprintf("k9s-%s.log", mustK9sUser()))
)
type ClusterInfo interface {
ActiveClusterOrDie() string
AllClustersOrDie() []string
AllNamespacesOrDie() []string
// KubeSettings exposes kubeconfig context informations.
type KubeSettings interface {
CurrentContextName() (string, error)
CurrentClusterName() (string, error)
ClusterNames() ([]string, error)
NamespaceNames() ([]string, error)
}
// Config tracks K9s configuration options.
type Config struct {
K9s *K9s `yaml:"k9s"`
K9s *K9s `yaml:"k9s"`
settings KubeSettings
}
// NewConfig creates a new default config.
func NewConfig() *Config {
return &Config{K9s: NewK9s()}
func NewConfig(ks KubeSettings) *Config {
return &Config{K9s: NewK9s(), settings: ks}
}
// ActiveClusterName fetch the configuration activeCluster.
func (c *Config) ActiveClusterName() string {
if c.K9s.Context == nil {
c.K9s.Context = NewContext()
// Reset the context to the new current context/cluster.
// if it does not exist.
func (c *Config) Reset() {
c.K9s.CurrentContext, c.K9s.CurrentCluster = "", ""
}
// CurrentCluster fetch the configuration activeCluster.
func (c *Config) CurrentCluster() *Cluster {
if c, ok := c.K9s.Clusters[c.K9s.CurrentCluster]; ok {
return c
}
return c.K9s.Context.Active
}
// ActiveCluster fetch the configuration activeCluster.
func (c *Config) ActiveCluster() *Cluster {
if c.K9s.Context == nil {
c.K9s.Context = NewContext()
}
return c.K9s.ActiveCluster()
}
// SetActiveCluster set the active cluster to the a new configuration.
func (c *Config) SetActiveCluster(s string) {
c.K9s.Context.SetActiveCluster(s)
return nil
}
// ActiveNamespace returns the active namespace in the current cluster.
func (c *Config) ActiveNamespace() string {
return c.K9s.ActiveCluster().Namespace.Active
if cl := c.CurrentCluster(); cl != nil {
if cl.Namespace != nil {
return cl.Namespace.Active
}
}
return resource.DefaultNamespace
}
// FavNamespaces returns fav namespaces in the current cluster.
@ -70,7 +75,9 @@ func (c *Config) FavNamespaces() []string {
// SetActiveNamespace set the active namespace in the current cluster.
func (c *Config) SetActiveNamespace(ns string) {
c.K9s.ActiveCluster().Namespace.SetActive(ns)
if c.K9s.ActiveCluster() != nil {
c.K9s.ActiveCluster().Namespace.SetActive(ns)
}
}
// ActiveView returns the active view in the current cluster.
@ -83,14 +90,12 @@ func (c *Config) ActiveView() string {
// SetActiveView set the currently cluster active view
func (c *Config) SetActiveView(view string) {
if c.K9s.Context == nil {
c.K9s.Context = NewContext()
}
c.Dump("ActiveView")
c.K9s.ActiveCluster().View.Active = view
}
// Load K9s configuration from file
func Load(path string) error {
func (c *Config) Load(path string) error {
f, err := ioutil.ReadFile(path)
if err != nil {
return err
@ -98,23 +103,24 @@ func Load(path string) error {
var cfg Config
if err := yaml.Unmarshal(f, &cfg); err != nil {
Root = NewConfig()
return err
}
Root = &cfg
c.K9s = cfg.K9s
return nil
}
// Save configuration to disk.
func (c *Config) Save(ci ClusterInfo) error {
c.Validate(ci)
func (c *Config) Save() error {
log.Debugf("[Config] Saving configuration...")
c.Dump("Saving")
c.Validate()
return c.SaveFile(K9sConfigFile)
}
// SaveFile K9s configuration to disk.
func (c *Config) SaveFile(path string) error {
log.Debugf("[Config] Saving configuration")
ensurePath(path, 0755)
EnsurePath(path, DefaultDirMod)
cfg, err := yaml.Marshal(c)
if err != nil {
log.Errorf("[Config] Unable to save K9s config file: %v", err)
@ -123,14 +129,16 @@ func (c *Config) SaveFile(path string) error {
return ioutil.WriteFile(path, cfg, 0644)
}
func (c *Config) activeCluster() *Cluster {
return c.K9s.Context.Clusters[c.K9s.Context.Active]
// Validate the configuration.
func (c *Config) Validate() {
c.K9s.Validate(c.settings)
}
// Validate the configuration.
func (c *Config) Validate(ci ClusterInfo) {
if c.K9s == nil {
c.K9s = NewK9s()
// Dump debug...
func (c *Config) Dump(msg string) {
log.Debug(msg)
log.Debugf("Current Context: %s\n", c.K9s.CurrentCluster)
for k, cl := range c.K9s.Clusters {
log.Debugf("K9s cluster: %s -- %s\n", k, cl.Namespace)
}
c.K9s.Validate(ci)
}

View File

@ -0,0 +1,231 @@
package config_test
import (
"fmt"
"io/ioutil"
"path/filepath"
"testing"
"github.com/derailed/k9s/internal/config"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
)
func TestConfigValidate(t *testing.T) {
setup(t)
ksMock := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.Validate()
}
func TestConfigLoad(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
assert.Equal(t, 2, cfg.K9s.RefreshRate)
assert.Equal(t, 200, cfg.K9s.LogBufferSize)
assert.Equal(t, "minikube", cfg.K9s.CurrentContext)
assert.Equal(t, "minikube", cfg.K9s.CurrentCluster)
assert.NotNil(t, cfg.K9s.Clusters)
assert.Equal(t, 2, len(cfg.K9s.Clusters))
assert.Equal(t, 0, len(cfg.K9s.Aliases))
nn := []string{
"default",
"kube-public",
"istio-system",
"all",
"kube-system",
}
assert.Equal(t, "kube-system", cfg.K9s.Clusters["minikube"].Namespace.Active)
assert.Equal(t, nn, cfg.K9s.Clusters["minikube"].Namespace.Favorites)
assert.Equal(t, "ctx", cfg.K9s.Clusters["minikube"].View.Active)
}
func TestConfigCurrentCluster(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
assert.NotNil(t, cfg.CurrentCluster())
assert.Equal(t, "kube-system", cfg.CurrentCluster().Namespace.Active)
assert.Equal(t, "ctx", cfg.CurrentCluster().View.Active)
}
func TestConfigActiveNamespace(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
assert.Equal(t, "kube-system", cfg.ActiveNamespace())
}
func TestConfigActiveNamespaceBlank(t *testing.T) {
cfg := config.Config{K9s: new(config.K9s)}
assert.Equal(t, "default", cfg.ActiveNamespace())
}
func TestConfigSetActiveNamespace(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.SetActiveNamespace("default")
assert.Equal(t, "default", cfg.ActiveNamespace())
}
func TestConfigActiveView(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
assert.Equal(t, "ctx", cfg.ActiveView())
}
func TestConfigActiveViewBlank(t *testing.T) {
cfg := config.Config{K9s: new(config.K9s)}
assert.Equal(t, "po", cfg.ActiveView())
}
func TestConfigSetActiveView(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.SetActiveView("po")
assert.Equal(t, "po", cfg.ActiveView())
}
func TestConfigFavNamespaces(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
expectedNS := []string{"default", "kube-public", "istio-system", "all", "kube-system"}
assert.Equal(t, expectedNS, cfg.FavNamespaces())
}
func TestConfigLoadOldCfg(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s_old.yml"))
}
func TestConfigLoadCrap(t *testing.T) {
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.NotNil(t, cfg.Load("test_assets/k9s_not_there.yml"))
}
func TestConfigSaveFile(t *testing.T) {
ksMock := NewMockKubeSettings()
m.When(ksMock.CurrentContextName()).ThenReturn("minikube", nil)
m.When(ksMock.CurrentClusterName()).ThenReturn("minikube", nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"minikube", "fred", "blee"}, nil)
cfg := config.NewConfig(ksMock)
cfg.Load("test_assets/k9s.yml")
cfg.K9s.RefreshRate = 100
cfg.K9s.LogBufferSize = 500
cfg.K9s.CurrentContext = "blee"
cfg.K9s.CurrentCluster = "blee"
cfg.Validate()
path := filepath.Join("/tmp", "k9s.yml")
err := cfg.SaveFile(path)
assert.Nil(t, err)
raw, err := ioutil.ReadFile(path)
assert.Nil(t, err)
assert.Equal(t, expectedConfig, string(raw))
}
func TestConfigReset(t *testing.T) {
ksMock := NewMockKubeSettings()
m.When(ksMock.CurrentContextName()).ThenReturn("blee", nil)
m.When(ksMock.CurrentClusterName()).ThenReturn("blee", nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"blee"}, nil)
cfg := config.NewConfig(ksMock)
cfg.Load("test_assets/k9s.yml")
cfg.Reset()
cfg.Validate()
path := filepath.Join("/tmp", "k9s.yml")
err := cfg.SaveFile(path)
assert.Nil(t, err)
raw, err := ioutil.ReadFile(path)
assert.Nil(t, err)
assert.Equal(t, resetConfig, string(raw))
}
// Helpers...
func setup(t *testing.T) {
m.RegisterMockTestingT(t)
m.RegisterMockFailHandler(func(m string, i ...int) {
fmt.Println("Boom!", m, i)
})
}
// ----------------------------------------------------------------------------
// Test Data...
var expectedConfig = `k9s:
refreshRate: 100
logBufferSize: 500
currentContext: blee
currentCluster: blee
clusters:
blee:
namespace:
active: default
favorites:
- default
view:
active: po
fred:
namespace:
active: default
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: po
minikube:
namespace:
active: kube-system
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: ctx
`
var resetConfig = `k9s:
refreshRate: 2
logBufferSize: 200
currentContext: blee
currentCluster: blee
clusters:
blee:
namespace:
active: default
favorites:
- default
view:
active: po
`

View File

@ -6,7 +6,14 @@ import (
"path/filepath"
log "github.com/sirupsen/logrus"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
)
const (
// DefaultDirMod default unix perms for k9s directory.
DefaultDirMod os.FileMode = 0755
// DefaultFileMod default unix perms for k9s files.
DefaultFileMod os.FileMode = 0644
)
// InList check if string is in a collection of strings.
@ -44,7 +51,8 @@ func mustK9sUser() string {
return usr.Username
}
func ensurePath(path string, mod os.FileMode) {
// EnsurePath ensures a directory exist from the given path.
func EnsurePath(path string, mod os.FileMode) {
dir := filepath.Dir(path)
if _, err := os.Stat(dir); os.IsNotExist(err) {
if err = os.Mkdir(dir, mod); err != nil {

View File

@ -0,0 +1,84 @@
package config_test
import (
"os"
"path/filepath"
"testing"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestHelperInList(t *testing.T) {
uu := []struct {
item string
list []string
expected bool
}{
{"a", []string{}, false},
{"", []string{}, false},
{"", []string{""}, true},
{"a", []string{"a", "b", "c", "d"}, true},
{"z", []string{"a", "b", "c", "d"}, false},
}
for _, u := range uu {
assert.Equal(t, u.expected, config.InList(u.list, u.item))
}
}
func TestHelperInNSList(t *testing.T) {
uu := []struct {
item string
list []interface{}
expected bool
}{
{
"fred",
[]interface{}{
v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "fred"}},
},
true,
},
{
"blee",
[]interface{}{
v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "fred"}},
},
false,
},
}
for _, u := range uu {
assert.Equal(t, u.expected, config.InNSList(u.list, u.item))
}
}
func TestEnsurePathNone(t *testing.T) {
var mod os.FileMode = 0744
dir := filepath.Join("/tmp", "fred")
assert.Nil(t, os.Remove(dir))
path := filepath.Join(dir, "duh.yml")
config.EnsurePath(path, mod)
p, err := os.Stat(dir)
assert.Nil(t, err)
assert.Equal(t, "drwxr--r--", p.Mode().String())
}
func TestEnsurePathNoOpt(t *testing.T) {
var mod os.FileMode = 0744
dir := filepath.Join("/tmp", "blee")
assert.Nil(t, os.Remove(dir))
assert.Nil(t, os.Mkdir(dir, mod))
path := filepath.Join(dir, "duh.yml")
config.EnsurePath(path, mod)
p, err := os.Stat(dir)
assert.Nil(t, err)
assert.Equal(t, "drwxr--r--", p.Mode().String())
}

83
internal/config/k9s.go Normal file
View File

@ -0,0 +1,83 @@
package config
const (
defaultRefreshRate = 2
defaultLogBufferSize = 200
)
// K9s tracks K9s configuration options.
type K9s struct {
RefreshRate int `yaml:"refreshRate"`
LogBufferSize int `yaml:"logBufferSize"`
CurrentContext string `yaml:"currentContext"`
CurrentCluster string `yaml:"currentCluster"`
Clusters map[string]*Cluster `yaml:"clusters,omitempty"`
Aliases map[string]string `yaml:"aliases,omitempty"`
}
// NewK9s create a new K9s configuration.
func NewK9s() *K9s {
return &K9s{
RefreshRate: defaultRefreshRate,
LogBufferSize: defaultLogBufferSize,
Clusters: map[string]*Cluster{},
Aliases: map[string]string{},
}
}
// ActiveCluster returns the currently active cluster.
func (k *K9s) ActiveCluster() *Cluster {
if k.Clusters == nil {
k.Clusters = map[string]*Cluster{}
}
if len(k.CurrentCluster) == 0 {
return nil
}
if c, ok := k.Clusters[k.CurrentCluster]; ok {
return c
}
k.Clusters[k.CurrentCluster] = NewCluster()
return k.Clusters[k.CurrentCluster]
}
// Validate the current configuration.
func (k *K9s) Validate(ks KubeSettings) {
if k.RefreshRate <= 0 {
k.RefreshRate = defaultRefreshRate
}
if k.LogBufferSize <= 0 {
k.LogBufferSize = defaultLogBufferSize
}
if k.Clusters == nil {
k.Clusters = map[string]*Cluster{}
}
cc, err := ks.ClusterNames()
if err != nil {
return
}
for key := range k.Clusters {
if !InList(cc, key) {
if k.CurrentCluster == key {
k.CurrentCluster = ""
}
delete(k.Clusters, key)
}
}
if ctx, err := ks.CurrentContextName(); err == nil && len(k.CurrentContext) == 0 {
k.CurrentContext = ctx
k.CurrentCluster = ""
}
if cl, err := ks.CurrentClusterName(); err == nil && len(k.CurrentCluster) == 0 {
k.CurrentCluster = cl
}
if _, ok := k.Clusters[k.CurrentCluster]; !ok {
k.Clusters[k.CurrentCluster] = NewCluster()
}
}

View File

@ -0,0 +1,81 @@
package config_test
import (
"testing"
"github.com/derailed/k9s/internal/config"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
)
func TestK9sValidate(t *testing.T) {
setup(t)
ksMock := NewMockKubeSettings()
m.When(ksMock.CurrentContextName()).ThenReturn("ctx1", nil)
m.When(ksMock.CurrentClusterName()).ThenReturn("c1", nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
c := config.NewK9s()
c.Validate(ksMock)
assert.Equal(t, 2, c.RefreshRate)
assert.Equal(t, 200, c.LogBufferSize)
assert.Equal(t, "ctx1", c.CurrentContext)
assert.Equal(t, "c1", c.CurrentCluster)
assert.Equal(t, 1, len(c.Clusters))
_, ok := c.Clusters[c.CurrentCluster]
assert.True(t, ok)
}
func TestK9sValidateBlank(t *testing.T) {
setup(t)
ksMock := NewMockKubeSettings()
m.When(ksMock.CurrentContextName()).ThenReturn("ctx1", nil)
m.When(ksMock.CurrentClusterName()).ThenReturn("c1", nil)
m.When(ksMock.ClusterNames()).ThenReturn([]string{"c1", "c2"}, nil)
var c config.K9s
c.Validate(ksMock)
assert.Equal(t, 2, c.RefreshRate)
assert.Equal(t, 200, c.LogBufferSize)
assert.Equal(t, "ctx1", c.CurrentContext)
assert.Equal(t, "c1", c.CurrentCluster)
assert.Equal(t, 1, len(c.Clusters))
_, ok := c.Clusters[c.CurrentCluster]
assert.True(t, ok)
}
func TestK9sActiveClusterZero(t *testing.T) {
setup(t)
c := config.NewK9s()
c.CurrentCluster = "fred"
cl := c.ActiveCluster()
assert.NotNil(t, cl)
assert.Equal(t, "default", cl.Namespace.Active)
assert.Equal(t, 1, len(cl.Namespace.Favorites))
}
func TestK9sActiveClusterBlank(t *testing.T) {
setup(t)
var c config.K9s
cl := c.ActiveCluster()
assert.Nil(t, cl)
}
func TestK9sActiveCluster(t *testing.T) {
setup(t)
ksMock := NewMockKubeSettings()
cfg := config.NewConfig(ksMock)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cl := cfg.K9s.ActiveCluster()
assert.NotNil(t, cl)
assert.Equal(t, "kube-system", cl.Namespace.Active)
assert.Equal(t, 5, len(cl.Namespace.Favorites))
}

View File

@ -0,0 +1,199 @@
// Code generated by pegomock. DO NOT EDIT.
// Source: github.com/derailed/k9s/internal/config (interfaces: KubeSettings)
package config_test
import (
pegomock "github.com/petergtz/pegomock"
"reflect"
"time"
)
type MockKubeSettings struct {
fail func(message string, callerSkip ...int)
}
func NewMockKubeSettings() *MockKubeSettings {
return &MockKubeSettings{fail: pegomock.GlobalFailHandler}
}
func (mock *MockKubeSettings) ClusterNames() ([]string, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterNames", 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 *MockKubeSettings) CurrentClusterName() (string, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentClusterName", 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 *MockKubeSettings) CurrentContextName() (string, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("CurrentContextName", 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 *MockKubeSettings) NamespaceNames() ([]string, error) {
if mock == nil {
panic("mock must not be nil. Use myMock := NewMockKubeSettings().")
}
params := []pegomock.Param{}
result := pegomock.GetGenericMockFrom(mock).Invoke("NamespaceNames", 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 *MockKubeSettings) VerifyWasCalledOnce() *VerifierKubeSettings {
return &VerifierKubeSettings{
mock: mock,
invocationCountMatcher: pegomock.Times(1),
}
}
func (mock *MockKubeSettings) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierKubeSettings {
return &VerifierKubeSettings{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
}
}
func (mock *MockKubeSettings) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierKubeSettings {
return &VerifierKubeSettings{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
inOrderContext: inOrderContext,
}
}
func (mock *MockKubeSettings) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierKubeSettings {
return &VerifierKubeSettings{
mock: mock,
invocationCountMatcher: invocationCountMatcher,
timeout: timeout,
}
}
type VerifierKubeSettings struct {
mock *MockKubeSettings
invocationCountMatcher pegomock.Matcher
inOrderContext *pegomock.InOrderContext
timeout time.Duration
}
func (verifier *VerifierKubeSettings) ClusterNames() *KubeSettings_ClusterNames_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterNames", params, verifier.timeout)
return &KubeSettings_ClusterNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type KubeSettings_ClusterNames_OngoingVerification struct {
mock *MockKubeSettings
methodInvocations []pegomock.MethodInvocation
}
func (c *KubeSettings_ClusterNames_OngoingVerification) GetCapturedArguments() {
}
func (c *KubeSettings_ClusterNames_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierKubeSettings) CurrentClusterName() *KubeSettings_CurrentClusterName_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentClusterName", params, verifier.timeout)
return &KubeSettings_CurrentClusterName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type KubeSettings_CurrentClusterName_OngoingVerification struct {
mock *MockKubeSettings
methodInvocations []pegomock.MethodInvocation
}
func (c *KubeSettings_CurrentClusterName_OngoingVerification) GetCapturedArguments() {
}
func (c *KubeSettings_CurrentClusterName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierKubeSettings) CurrentContextName() *KubeSettings_CurrentContextName_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "CurrentContextName", params, verifier.timeout)
return &KubeSettings_CurrentContextName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type KubeSettings_CurrentContextName_OngoingVerification struct {
mock *MockKubeSettings
methodInvocations []pegomock.MethodInvocation
}
func (c *KubeSettings_CurrentContextName_OngoingVerification) GetCapturedArguments() {
}
func (c *KubeSettings_CurrentContextName_OngoingVerification) GetAllCapturedArguments() {
}
func (verifier *VerifierKubeSettings) NamespaceNames() *KubeSettings_NamespaceNames_OngoingVerification {
params := []pegomock.Param{}
methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NamespaceNames", params, verifier.timeout)
return &KubeSettings_NamespaceNames_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations}
}
type KubeSettings_NamespaceNames_OngoingVerification struct {
mock *MockKubeSettings
methodInvocations []pegomock.MethodInvocation
}
func (c *KubeSettings_NamespaceNames_OngoingVerification) GetCapturedArguments() {
}
func (c *KubeSettings_NamespaceNames_OngoingVerification) GetAllCapturedArguments() {
}

View File

@ -1,12 +1,15 @@
package config
import (
"github.com/derailed/k9s/resource"
log "github.com/sirupsen/logrus"
)
// MaxFavoritesNS number # favorite namespaces to keep in the configuration.
const MaxFavoritesNS = 10
const (
// MaxFavoritesNS number # favorite namespaces to keep in the configuration.
MaxFavoritesNS = 10
defaultNS = "default"
allNS = "all"
)
// Namespace tracks active and favorites namespaces.
type Namespace struct {
@ -17,22 +20,25 @@ type Namespace struct {
// NewNamespace create a new namespace configuration.
func NewNamespace() *Namespace {
return &Namespace{
Active: resource.DefaultNamespace,
Favorites: []string{"all", "default", "kube-system"},
Active: defaultNS,
Favorites: []string{defaultNS},
}
}
// Validate a namespace is setup correctly
func (n *Namespace) Validate(ci ClusterInfo) {
nn := ci.AllNamespacesOrDie()
func (n *Namespace) Validate(ks KubeSettings) {
nn, err := ks.NamespaceNames()
if err != nil {
return
}
if !n.isAllNamespace() && !InList(nn, n.Active) {
log.Debugf("[Config] Validation error active namespace reseting to `default")
n.Active = resource.DefaultNamespace
log.Debugf("[Config] Validation error active namespace resetting to `default")
n.Active = defaultNS
}
for _, ns := range n.Favorites {
if ns != resource.AllNamespace && !InList(nn, ns) {
if ns != allNS && !InList(nn, ns) {
log.Debugf("[Config] Invalid favorite found '%s' - %t", ns, n.isAllNamespace())
n.rmFavNS(ns)
}
@ -46,7 +52,7 @@ func (n *Namespace) SetActive(ns string) {
}
func (n *Namespace) isAllNamespace() bool {
return n.Active == resource.AllNamespace
return n.Active == allNS
}
func (n *Namespace) addFavNS(ns string) {
@ -77,4 +83,4 @@ func (n *Namespace) rmFavNS(ns string) {
}
n.Favorites = append(n.Favorites[:victim], n.Favorites[victim+1:]...)
}
}

View File

@ -0,0 +1,89 @@
package config_test
import (
"errors"
"testing"
"github.com/derailed/k9s/internal/config"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
)
func TestNSValidate(t *testing.T) {
setup(t)
ns := config.NewNamespace()
ksMock := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2", "default"}, nil)
ns.Validate(ksMock)
ksMock.VerifyWasCalledOnce()
assert.Equal(t, "default", ns.Active)
assert.Equal(t, []string{"default"}, ns.Favorites)
}
func TestNSValidateMissing(t *testing.T) {
setup(t)
ns := config.NewNamespace()
ksMock := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2"}, nil)
ns.Validate(ksMock)
ksMock.VerifyWasCalledOnce()
assert.Equal(t, "default", ns.Active)
assert.Equal(t, []string{}, ns.Favorites)
}
func TestNSValidateNoNS(t *testing.T) {
setup(t)
ns := config.NewNamespace()
ksMock := NewMockKubeSettings()
m.When(ksMock.NamespaceNames()).ThenReturn([]string{"ns1", "ns2"}, errors.New("boom"))
ns.Validate(ksMock)
ksMock.VerifyWasCalledOnce()
assert.Equal(t, "default", ns.Active)
assert.Equal(t, []string{"default"}, ns.Favorites)
}
func TestNSSetActive(t *testing.T) {
uu := []struct {
ns string
fav []string
}{
{"all", []string{"all", "default"}},
{"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"}},
}
ns := config.NewNamespace()
for _, u := range uu {
ns.SetActive(u.ns)
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"}},
}
for _, u := range uu {
ns.SetActive(u.ns)
assert.Equal(t, u.ns, ns.Active)
}
}

View File

@ -0,0 +1,19 @@
apiVersion: v1
kind: Config
preferences: {}
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:6443
name: docker-for-desktop-cluster
contexts:
- context:
cluster: docker-for-desktop-cluster
user: docker-for-desktop
name: docker-for-desktop
current-context: docker-for-desktop
users:
- name: docker-for-desktop
user:
client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUM5RENDQWR5Z0F3SUJBZ0lJWFNHb3I3ZlJlOHN3RFFZSktvWklodmNOQVFFTEJRQXdGVEVUTUJFR0ExVUUKQXhNS2EzVmlaWEp1WlhSbGN6QWVGdzB4T0RBNU1ESXlNREkyTlRaYUZ3MHlNREF5TURreE5qQXhNVGhhTURZeApGekFWQmdOVkJBb1REbk41YzNSbGJUcHRZWE4wWlhKek1Sc3dHUVlEVlFRREV4SmtiMk5yWlhJdFptOXlMV1JsCmMydDBiM0F3Z2dFaU1BMEdDU3FHU0liM0RRRUJBUVVBQTRJQkR3QXdnZ0VLQW9JQkFRRGxBTWZKVUUvWUIwb0UKWmN5TmE1S0dVMkZpRmNYLys2dFJQUlpETkhZSkE1ZGtaME40UC9kVVdZeWJGRlVYc0E3UU1Sbm1mS280Q25MTQptK28wS2NUd3NRMnY3UzlPejVJYlJCOVZGVnFqNDJmNW9mVFFDcnZNN20wWVovNlRzcjhtSDE0QVYzWkRZaWtsCkF1VjlqRUgvczF5WWppbG0rODVlbm02RUZYYkJMV2czcXZkQ3VxNmlMa2FjWFptWTJYVXVtTWVOTVZnQllrS1UKVi84czJ0VlhyTlhvaU9qZVFMZlIvYmpvYkFZbzlMM0JWZFczYUxjanBwcDYzWmE0YlZITHYyQ2ZXMDcwNjNvbQpYZ0syM1hHWjQwdFFGaElxbUlvZktYZ0lVSWk2YVV3UVI0WFVRd3RMeTk4aHRDazZ2ckl5UUpMWkdKV29WVlU4CitJclVtZFRyQWdNQkFBR2pKekFsTUE0R0ExVWREd0VCL3dRRUF3SUZvREFUQmdOVkhTVUVEREFLQmdnckJnRUYKQlFjREFqQU5CZ2txaGtpRzl3MEJBUXNGQUFPQ0FRRUFRRzlVaFVjUDdJQzdyZmRyK1pxTTBKbGxITzY3MzZoTgpVRkpvNmZSRUdDbjlxclN6SW44K0RZV1N3RnF4ZVRhZlNFK3VJZHFGREQ1ais1bWhEQzBzZUV2WWlNQ09CZFJDCk4xT3RRK1lrQndndnNKU3RxZGdzNTRXdkJwLzFiS09leFNLS1laTzJPaUJLd3NRV1ZXeksrQ0VjOXhRSm1jN2MKZGhlK0tNZVNTeC9LSmR0bHc4VWVSUkVCOU00WjZjRHpLYzQ2cjhBd04zWkxibzdsYzVCNE0wb2lXMUVwR0wwQgpKUXYrT1FDblV4K0d1dVcvTGdNT1JQRVFXaXF1UjFvWXlJQjlRb0wxRXFCTDZHejhTWjVtbTE1ellWSVZXTHh6ClNQLzVyd2VjNDY0Z216RDR4MHJVUHBIaWlRSVJzUjk1WXBIMjNxWkl2QlVwd2dnTjJnd2hTUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBNVFESHlWQlAyQWRLQkdYTWpXdVNobE5oWWhYRi8vdXJVVDBXUXpSMkNRT1haR2RECmVELzNWRm1NbXhSVkY3QU8wREVaNW55cU9BcHl6SnZxTkNuRThMRU5yKzB2VHMrU0cwUWZWUlZhbytObithSDAKMEFxN3pPNXRHR2YrazdLL0poOWVBRmQyUTJJcEpRTGxmWXhCLzdOY21JNHBadnZPWHA1dWhCVjJ3UzFvTjZyMwpRcnF1b2k1R25GMlptTmwxTHBqSGpURllBV0pDbEZmL0xOclZWNnpWNklqbzNrQzMwZjI0Nkd3R0tQUzl3VlhWCnQyaTNJNmFhZXQyV3VHMVJ5NzlnbjF0TzlPdDZKbDRDdHQxeG1lTkxVQllTS3BpS0h5bDRDRkNJdW1sTUVFZUYKMUVNTFM4dmZJYlFwT3I2eU1rQ1MyUmlWcUZWVlBQaUsxSm5VNndJREFRQUJBb0lCQUZSY29EenlZQ2VXTDlkRQo1VUVuNHRlbk9kWFhiWlNxMHViZm1TYnkyWlRpaE5BUkZwTGpCYXRHUGYwWFZXMmZoeVY5SVN4K3VucGdwdi9uClpEVUpPaXJ0SHJ5enBOemtyTTlzbmhwSy9wUW5mek5BVFo2aWhhS3VKdlI1d3hnSUhsRGQ5MVFxNUQ5WWx3MnkKYm5aOHlBZDV2Ti9hWnpnd0JVdG9GQkNHazdQLzRxK0JlbHZoNWd1SzdzS0dvRi94dGMwWlp6RGtYMkw5VHJlZQowSE5nTmJlYm91SHhlVHBkcGNLQzZ2TENST2tqb0RTdDY1ZEo2ajBGTzhzTERVcWRrWkxNa0ducTdoZWhYV2JwClBtRVI5dWc3Qk1HVUFtcHhpbGdGVHM0MnRSNnoxZXdvSWs5bC92V3ZMTFpZbEE3OUo4YkU0UTNPZGpXc2Nza1YKV0ZpakZ0a0NnWUVBNXU2SnV3b3A0Z2QzTjNhUHVhakpHNEpjYTZNWFZQNEVVay9vY2pobloyRDF5cjd2ZXhZSApVaE1WN2p6dzJUQ1FJa2JtMWZlSTZpa1llUzNVNXprZDhKSm9VaXV4T3ZsS0VJSlFrTEROQyttMHFuN0xEamU1Cmx0SkE3Sm9IdDhSTzNMS1JBTFhvQTVsV0l0NWNQLzVuTW1IMTlDZGQ3L294MU0xRXFFYUxNMmNDZ1lFQS9keWsKMExyR0VtbTg0SlU0dDIvZDVzNm5ERnAxOWxhUXJlbFY1bWRsZEFKNGRPVHkxZXV4dHNFeS8xSFExWXBLa25aSgpTa2Q2RTJzYk1MUncxdzFGWTZpczI0ektmaWtLV0N5SXBPMTkvQWh5UkpweGxKTlN4a2hjK1FpVXVSd1lsVmtMCi9XQ3dFUFVVaDI1VHJRNE9LRTNKazh3VmVmdFlwdkZNRDhvaHc5MENnWUF5ZTkxQ05XT1lsUmM3MmNCcnp2ay8KK1V5by96dGZpalI1cGh4anMrN3ZDNlJRRVZPYkxlS2x6NlJRczZQWFp5VnJTT0szemVoeGdGQm9WVnVndkx6TgoxY1BXaXRTdzFzU1pQVlBOZmNrbG5JNnhZd3lTN0IyM1dmbDFmK3JHQXJWV3kvYWxHQjlEZ2lieGNuanFTSHhZCjZFOXpjNU8yblpSOU4rNlZkdTZCYXdLQmdDV3Vtc2hnOFFYS3JENnA1OEZTMloxcEQyTEdDcnlHSFBPenJ3eUUKVElycjB2V0hCb1M2ZDZhcEJ1amZQQ0IyWnB0Vzg0b1RFZ3ZQMmpsZ2oxOWNtUEF5R1haOWI1RktoajZRWGJnZAppSlhncXhXRDExZzJoaExvcXVSTVljY1laSTNHcWdEeVdUQXJNT0RwZjRJd2srbG5vb1JOeHVKVWJOUmEvTzliCkVhZ0JBb0dBUE9VZk15M3JPSWRBRTV4Q0VxOUlza1hXblFrcmRwYktKVDVzbVFyVUhuRFh4QWM5L0libm1jWmwKOWN4Y3czdktMbWVZU3loWFF5ZWF1VXo3amZhdjZ3TGhnVi9KK1NYdlBlUng2aDFmb0lsVUF4WDJMdDFSOEduZApNejhqdHJxN29ycE1EQU9xOHNyaGxzZkZDYnJtUFZKSnNTd1J4R3ZJN3ZLTm54VXRXVFE9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==

View File

@ -0,0 +1,28 @@
k9s:
refreshRate: 2
logBufferSize: 200
currentContext: minikube
currentCluster: minikube
clusters:
minikube:
namespace:
active: kube-system
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: ctx
fred:
namespace:
active: default
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: po

View File

@ -0,0 +1,28 @@
k9s:
refreshRate: 2
logBufferSize: 200
currentContext: minikube
currentCluster: minikube
clusters:
minikube:
namespace:
active: kube-system
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: ctx
fred:
namespace:
active: default
favorites:
- default
- kube-public
- istio-system
- all
- kube-system
view:
active: po

View File

@ -7,11 +7,13 @@ type View struct {
Active string `yaml:"active"`
}
// NewView creates a new view configuration.
func NewView() *View {
return &View{Active: defaultView}
}
func (v *View) Validate(ClusterInfo) {
// Validate a view configuration.
func (v *View) Validate() {
if len(v.Active) == 0 {
v.Active = defaultView
}

View File

@ -3,18 +3,23 @@ package config_test
import (
"testing"
"github.com/derailed/k9s/config"
"github.com/derailed/k9s/internal/config"
"github.com/stretchr/testify/assert"
)
func TestViewValidate(t *testing.T) {
v := config.NewView()
ci := NewMockClusterInfo()
v.Validate(ci)
v.Validate()
assert.Equal(t, "po", v.Active)
v.Active = "fred"
v.Validate(ci)
v.Validate()
assert.Equal(t, "fred", v.Active)
}
func TestViewValidateBlank(t *testing.T) {
var v config.View
v.Validate()
assert.Equal(t, "po", v.Active)
}

View File

@ -1,17 +1,13 @@
package k8s
import (
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
restclient "k8s.io/client-go/rest"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
clientcmd "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/kubernetes/pkg/kubectl/metricsutil"
"k8s.io/kubernetes/staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metricsapi "k8s.io/metrics/pkg/apis/metrics"
versioned "k8s.io/metrics/pkg/client/clientset/versioned"
)
@ -20,13 +16,13 @@ import (
const NA = "n/a"
var (
conn connection = &apiServer{}
supportedMetricsAPIVersions = []string{"v1beta1"}
conn = &apiServer{}
supportedMetricsAPIVersions = []string{"v1beta1"}
)
type (
// ApiGroup represents a K8s resource descriptor.
ApiGroup struct {
// APIGroup represents a K8s resource descriptor.
APIGroup struct {
Resource string
Group, Kind, Version string
Plural, Singular string
@ -53,31 +49,25 @@ type (
nsDialOrDie() dynamic.NamespaceableResourceInterface
mxsDial() (*versioned.Clientset, error)
heapsterDial() (*metricsutil.HeapsterMetricsClient, error)
getClusterName() string
hasMetricsServer() bool
}
apiServer struct {
flags *genericclioptions.ConfigFlags
config *Config
client kubernetes.Interface
dClient dynamic.Interface
csClient *clientset.Clientset
nsClient dynamic.NamespaceableResourceInterface
heapsterClient *metricsutil.HeapsterMetricsClient
mxsClient *versioned.Clientset
clusterName string
useMetricServer bool
}
)
// InitConnection initialize connection from command line args.
func InitConnection(flags *genericclioptions.ConfigFlags) {
conn = &apiServer{flags: flags}
}
func (a *apiServer) getClusterName() string {
a.checkCurrentConfig()
return a.clusterName
// InitConnectionOrDie initialize connection from command line args.
// Checks for connectivity with the api server.
func InitConnectionOrDie(config *Config) {
conn = &apiServer{config: config}
conn.useMetricServer = conn.supportsMxServer()
}
func (a *apiServer) hasMetricsServer() bool {
@ -86,32 +76,20 @@ func (a *apiServer) hasMetricsServer() bool {
// ActiveClusterOrDie Fetch the current cluster based on current context.
func ActiveClusterOrDie() string {
cfg := conn.apiConfigOrDie()
return cfg.Contexts[cfg.CurrentContext].Cluster
}
// AllClustersOrDie fetch all available clusters from config.
func AllClustersOrDie() []string {
cfg := conn.apiConfigOrDie()
cc := make([]string, 0, len(cfg.Clusters))
for k := range cfg.Clusters {
cc = append(cc, k)
}
return cc
}
// AllNamespacesOrDie fetch all available namespaces on current cluster.
func AllNamespacesOrDie() []string {
nn, err := NewNamespace().List("")
cl, err := conn.config.CurrentClusterName()
if err != nil {
panic(err)
}
ss := make([]string, len(nn))
for i, n := range nn {
ss[i] = n.(v1.Namespace).Name
return cl
}
// AllClusterNamesOrDie fetch all available clusters from config.
func AllClusterNamesOrDie() []string {
if cc, err := conn.config.ClusterNames(); err != nil {
panic(err)
} else {
return cc
}
return ss
}
// DialOrDie returns a handle to api server or die.
@ -125,26 +103,9 @@ func (a *apiServer) dialOrDie() kubernetes.Interface {
if a.client, err = kubernetes.NewForConfig(a.restConfigOrDie()); err != nil {
panic(err)
}
return a.client
}
func (a *apiServer) csDialOrDie() *clientset.Clientset {
a.checkCurrentConfig()
if a.csClient != nil {
return a.csClient
}
var cfg *rest.Config
// cfg := clientcmd.NewNonInteractiveClientConfig(config, contextName, overrides, configAccess)
var err error
if a.csClient, err = clientset.NewForConfig(cfg); err != nil {
panic(err)
}
return a.csClient
}
// DynDial returns a handle to the api server.
func (a *apiServer) dynDialOrDie() dynamic.Interface {
a.checkCurrentConfig()
@ -201,56 +162,54 @@ func (a *apiServer) mxsDial() (*versioned.Clientset, error) {
return a.mxsClient, err
}
// ConfigOrDie check kubernetes cluster config.
// Dies if no config is found or incorrect.
func ConfigOrDie() {
cfg := conn.apiConfigOrDie()
if clientcmdapi.IsConfigEmpty(&cfg) {
panic("K8s config file load failed. Please check your .kube/config or $KUBECONFIG env")
}
}
func (a *apiServer) configAccess() clientcmd.ConfigAccess {
return a.flags.ToRawKubeConfigLoader().ConfigAccess()
}
func (a *apiServer) apiConfigOrDie() clientcmdapi.Config {
c, err := a.flags.ToRawKubeConfigLoader().RawConfig()
if err != nil {
panic(err)
}
return c
}
func (a *apiServer) restConfigOrDie() *restclient.Config {
cfg, err := a.flags.ToRESTConfig()
cfg, err := a.config.ConfigAccess()
if err != nil {
panic(err)
}
return cfg
}
func (a *apiServer) checkCurrentConfig() {
cfg := a.apiConfigOrDie()
currentCluster := cfg.Contexts[cfg.CurrentContext].Cluster
func (a *apiServer) restConfigOrDie() *restclient.Config {
cfg, err := a.config.RESTConfig()
if err != nil {
panic(err)
}
return cfg
}
if len(a.clusterName) == 0 {
a.clusterName = currentCluster
a.useMetricServer = a.supportsMxServer()
return
func (a *apiServer) switchContextOrDie(ctx string) {
currentCtx, err := a.config.CurrentContextName()
if err != nil {
panic(err)
}
if a.clusterName != currentCluster {
if currentCtx != ctx {
a.reset()
a.clusterName = currentCluster
if err := a.config.SwitchContext(ctx); err != nil {
panic(err)
}
a.useMetricServer = a.supportsMxServer()
}
}
func (a *apiServer) checkCurrentConfig() {
// currentCluster, err := a.config.CurrentCluster()
// if err != nil {
// panic(err)
// }
// if a.clusterName != currentCluster {
// a.reset()
// a.clusterName = currentCluster
// a.useMetricServer = a.supportsMxServer()
// return
// }
}
func (a *apiServer) reset() {
a.client, a.dClient, a.nsClient = nil, nil, nil
a.heapsterClient, a.mxsClient = nil, nil
a.clusterName = ""
}
func (a *apiServer) supportsMxServer() bool {

View File

@ -0,0 +1,43 @@
apiVersion: v1
kind: Config
preferences: {}
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3000
name: fred
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3001
name: blee
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3002
name: duh
contexts:
- context:
cluster: fred
user: fred
name: fred
- context:
cluster: blee
user: blee
name: blee
- context:
cluster: duh
user: duh
name: duh
current-context: fred
users:
- name: fred
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: blee
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: duh
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==

View File

@ -0,0 +1,39 @@
apiVersion: v1
clusters:
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3001
name: blee
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3002
name: duh
- cluster:
insecure-skip-tls-verify: true
server: https://localhost:3000
name: fred
contexts:
- context:
cluster: blee
user: blee
name: blee
- context:
cluster: duh
user: duh
name: duh
current-context: fred
kind: Config
preferences: {}
users:
- name: blee
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: duh
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==
- name: fred
user:
client-certificate-data: ZnJlZA==
client-key-data: ZnJlZA==

45
internal/k8s/cluster.go Normal file
View File

@ -0,0 +1,45 @@
package k8s
// Cluster manages a Kubernetes ClusterRole.
type Cluster struct{}
// NewCluster instantiates a new ClusterRole.
func NewCluster() *Cluster {
return &Cluster{}
}
// Version returns the current cluster git version.
func (c *Cluster) Version() (string, error) {
rev, err := conn.dialOrDie().Discovery().ServerVersion()
if err != nil {
return "", err
}
return rev.GitVersion, nil
}
// ContextName returns the currently active context.
func (c *Cluster) ContextName() string {
ctx, err := conn.config.CurrentContextName()
if err != nil {
return "N/A"
}
return ctx
}
// ClusterName return the currently active cluster name.
func (c *Cluster) ClusterName() string {
ctx, err := conn.config.CurrentClusterName()
if err != nil {
return "N/A"
}
return ctx
}
// UserName returns the currently active user.
func (c *Cluster) UserName() string {
usr, err := conn.config.CurrentUserName()
if err != nil {
return "N/A"
}
return usr
}

260
internal/k8s/config.go Normal file
View File

@ -0,0 +1,260 @@
package k8s
import (
"errors"
"fmt"
log "github.com/sirupsen/logrus"
v1 "k8s.io/api/core/v1"
"k8s.io/cli-runtime/pkg/genericclioptions"
restclient "k8s.io/client-go/rest"
clientcmd "k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
// Config tracks a kubernetes configuration.
type Config struct {
flags *genericclioptions.ConfigFlags
clientConfig clientcmd.ClientConfig
currentContext string
rawConfig *clientcmdapi.Config
restConfig *restclient.Config
}
// NewConfig returns a new k8s config or an error if the flags are invalid.
func NewConfig(f *genericclioptions.ConfigFlags) *Config {
return &Config{flags: f}
}
// SwitchContext changes the kubeconfig context to a new cluster.
func (c *Config) SwitchContext(name string) error {
currentCtx, err := c.CurrentContextName()
if err != nil {
return err
}
if currentCtx != name {
c.reset()
c.flags.Context, c.currentContext = &name, name
}
return nil
}
func (c *Config) reset() {
c.clientConfig, c.rawConfig, c.restConfig = nil, nil, nil
}
// CurrentContextName returns the currently active config context.
func (c *Config) CurrentContextName() (string, error) {
if isSet(c.flags.Context) {
return *c.flags.Context, nil
}
cfg, err := c.RawConfig()
if err != nil {
return "", err
}
return cfg.CurrentContext, nil
}
// GetContext fetch a given context or error if it does not exists.
func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) {
cfg, err := c.RawConfig()
if err != nil {
return nil, err
}
if c, ok := cfg.Contexts[n]; ok {
return c, nil
}
return nil, fmt.Errorf("invalid context `%s specified", n)
}
// Contexts fetch all available contexts.
func (c *Config) Contexts() (map[string]*clientcmdapi.Context, error) {
var cc map[string]*clientcmdapi.Context
cfg, err := c.RawConfig()
if err != nil {
return cc, err
}
return cfg.Contexts, nil
}
// DelContext remove a given context from the configuration.
func (c *Config) DelContext(n string) error {
cfg, err := c.RawConfig()
if err != nil {
return err
}
delete(cfg.Contexts, n)
return clientcmd.ModifyConfig(c.clientConfig.ConfigAccess(), cfg, true)
}
// ContextNames fetch all available contexts.
func (c *Config) ContextNames() ([]string, error) {
var cc []string
cfg, err := c.RawConfig()
if err != nil {
return cc, err
}
cc = make([]string, 0, len(cfg.Contexts))
for n := range cfg.Contexts {
cc = append(cc, n)
}
return cc, nil
}
// ClusterNameFromContext returns the cluster associated with the given context.
func (c *Config) ClusterNameFromContext(ctx string) (string, error) {
cfg, err := c.RawConfig()
if err != nil {
return "", err
}
if ctx, ok := cfg.Contexts[ctx]; ok {
return ctx.Cluster, nil
}
return "", fmt.Errorf("unable to locate cluster from context %s", ctx)
}
// CurrentClusterName returns the active cluster name.
func (c *Config) CurrentClusterName() (string, error) {
if isSet(c.flags.ClusterName) {
return *c.flags.ClusterName, nil
}
cfg, err := c.RawConfig()
if err != nil {
return "", err
}
current := cfg.CurrentContext
if isSet(c.flags.Context) {
current = *c.flags.Context
}
if ctx, ok := cfg.Contexts[current]; ok {
return ctx.Cluster, nil
}
return "", errors.New("unable to locate current cluster")
}
// ClusterNames fetch all kubeconfig defined clusters.
func (c *Config) ClusterNames() ([]string, error) {
var cc []string
if err := c.configFromFlags(); err != nil {
return cc, err
}
cfg, err := c.RawConfig()
if err != nil {
return cc, err
}
cc = make([]string, 0, len(cfg.Clusters))
for name := range cfg.Clusters {
cc = append(cc, name)
}
return cc, nil
}
// CurrentUserName retrieves the active user name.
func (c *Config) CurrentUserName() (string, error) {
if isSet(c.flags.AuthInfoName) {
return *c.flags.AuthInfoName, nil
}
cfg, err := c.RawConfig()
if err != nil {
return "", err
}
current := cfg.CurrentContext
if isSet(c.flags.Context) {
current = *c.flags.Context
}
if ctx, ok := cfg.Contexts[current]; ok {
return ctx.AuthInfo, nil
}
return "", errors.New("unable to locate current cluster")
}
// CurrentNamespaceName retrieves the active namespace.
func (c *Config) CurrentNamespaceName() string {
if isSet(c.flags.Namespace) {
return *c.flags.Namespace
}
return "default"
}
// 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
}
nn = make([]string, len(nn))
for i, n := range ll {
nn[i] = n.(v1.Namespace).Name
}
return nn, nil
}
// ConfigAccess return the current kubeconfig api server access configuration.
func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) {
var acc clientcmd.ConfigAccess
if err := c.configFromFlags(); err != nil {
return acc, err
}
return c.clientConfig.ConfigAccess(), nil
}
// RawConfig fetch the current kubeconfig with no overrides.
func (c *Config) RawConfig() (clientcmdapi.Config, error) {
if c.rawConfig != nil && c.rawConfig.CurrentContext != c.currentContext {
log.Debugf("Context swith detected...")
c.currentContext = c.rawConfig.CurrentContext
c.reset()
}
if c.rawConfig == nil {
if err := c.configFromFlags(); err != nil {
return clientcmdapi.Config{}, err
}
log.Debugf("Reloading RawConfig...")
cfg, err := c.clientConfig.RawConfig()
if err != nil {
return cfg, err
}
c.rawConfig = &cfg
}
return *c.rawConfig, nil
}
// RESTConfig fetch the current REST api service connection.
func (c *Config) RESTConfig() (*restclient.Config, error) {
var err error
if c.restConfig == nil {
if err = c.configFromFlags(); err != nil {
return nil, err
}
c.restConfig, err = c.flags.ToRESTConfig()
if err != nil {
return c.restConfig, err
}
log.Debugf("Connecting to API Server %s", c.restConfig.Host)
}
return c.restConfig, nil
}
// ----------------------------------------------------------------------------
// Helpers...
func (c *Config) configFromFlags() error {
if c.clientConfig == nil {
c.clientConfig = c.flags.ToRawKubeConfigLoader()
}
return nil
}
func isSet(s *string) bool {
return s != nil && len(*s) != 0
}

221
internal/k8s/config_test.go Normal file
View File

@ -0,0 +1,221 @@
package k8s_test
import (
"errors"
"testing"
"github.com/derailed/k9s/internal/k8s"
"github.com/stretchr/testify/assert"
"k8s.io/cli-runtime/pkg/genericclioptions"
)
func TestConfigCurrentContext(t *testing.T) {
name, kubeConfig := "blee", "./assets/config"
uu := []struct {
flags *genericclioptions.ConfigFlags
context string
}{
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, "fred"},
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Context: &name}, "blee"},
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
ctx, err := cfg.CurrentContextName()
assert.Nil(t, err)
assert.Equal(t, u.context, ctx)
}
}
func TestConfigCurrentCluster(t *testing.T) {
name, kubeConfig := "blee", "./assets/config"
uu := []struct {
flags *genericclioptions.ConfigFlags
cluster string
}{
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, "fred"},
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, ClusterName: &name}, "blee"},
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
ctx, err := cfg.CurrentClusterName()
assert.Nil(t, err)
assert.Equal(t, u.cluster, ctx)
}
}
func TestConfigCurrentUser(t *testing.T) {
name, kubeConfig := "blee", "./assets/config"
uu := []struct {
flags *genericclioptions.ConfigFlags
user string
}{
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, "fred"},
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, AuthInfoName: &name}, "blee"},
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
ctx, err := cfg.CurrentUserName()
assert.Nil(t, err)
assert.Equal(t, u.user, ctx)
}
}
func TestConfigCurrentNamespace(t *testing.T) {
name, kubeConfig := "blee", "./assets/config"
uu := []struct {
flags *genericclioptions.ConfigFlags
namespace string
}{
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, "default"},
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig, Namespace: &name}, "blee"},
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
assert.Equal(t, u.namespace, cfg.CurrentNamespaceName())
}
}
func TestConfigGetContext(t *testing.T) {
kubeConfig := "./assets/config"
uu := []struct {
flags *genericclioptions.ConfigFlags
cluster string
err error
}{
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, "blee", nil},
{&genericclioptions.ConfigFlags{KubeConfig: &kubeConfig}, "bozo", errors.New("invalid context `bozo specified")},
}
for _, u := range uu {
cfg := k8s.NewConfig(u.flags)
ctx, err := cfg.GetContext(u.cluster)
if err != nil {
assert.Equal(t, u.err, err)
} else {
assert.NotNil(t, ctx)
assert.Equal(t, u.cluster, ctx.Cluster)
}
}
}
func TestConfigSwitchContext(t *testing.T) {
cluster, kubeConfig := "duh", "./assets/config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
err := cfg.SwitchContext("blee")
assert.Nil(t, err)
ctx, err := cfg.CurrentContextName()
assert.Nil(t, err)
assert.Equal(t, "blee", ctx)
}
func TestConfigClusterNameFromContext(t *testing.T) {
cluster, kubeConfig := "duh", "./assets/config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cl, err := cfg.ClusterNameFromContext("blee")
assert.Nil(t, err)
assert.Equal(t, "blee", cl)
}
func TestConfigAccess(t *testing.T) {
cluster, kubeConfig := "duh", "./assets/config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
acc, err := cfg.ConfigAccess()
assert.Nil(t, err)
assert.True(t, len(acc.GetDefaultFilename()) > 0)
}
func TestConfigContexts(t *testing.T) {
cluster, kubeConfig := "duh", "./assets/config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cc, err := cfg.Contexts()
assert.Nil(t, err)
assert.Equal(t, 3, len(cc))
}
func TestConfigContextNames(t *testing.T) {
cluster, kubeConfig := "duh", "./assets/config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cc, err := cfg.ContextNames()
assert.Nil(t, err)
assert.Equal(t, 3, len(cc))
}
func TestConfigClusterNames(t *testing.T) {
cluster, kubeConfig := "duh", "./assets/config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
cc, err := cfg.ClusterNames()
assert.Nil(t, err)
assert.Equal(t, 3, len(cc))
}
func TestConfigDelContext(t *testing.T) {
cluster, kubeConfig := "duh", "./assets/config.1"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
ClusterName: &cluster,
}
cfg := k8s.NewConfig(&flags)
err := cfg.DelContext("fred")
assert.Nil(t, err)
cc, err := cfg.ContextNames()
assert.Nil(t, err)
assert.Equal(t, 2, len(cc))
}
func TestConfigRestConfig(t *testing.T) {
kubeConfig := "./assets/config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
}
cfg := k8s.NewConfig(&flags)
rc, err := cfg.RESTConfig()
assert.Nil(t, err)
assert.Equal(t, "https://localhost:3000", rc.Host)
}
func TestConfigBadConfig(t *testing.T) {
kubeConfig := "./assets/bork_config"
flags := genericclioptions.ConfigFlags{
KubeConfig: &kubeConfig,
}
cfg := k8s.NewConfig(&flags)
_, err := cfg.RESTConfig()
assert.NotNil(t, err)
}

View File

@ -3,7 +3,6 @@ package k8s
import (
"fmt"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
)
@ -19,9 +18,13 @@ type NamedContext struct {
Context *api.Context
}
// CurrentCluster return active cluster name
func (c *NamedContext) CurrentCluster() string {
return conn.getClusterName()
// MustCurrentClusterName return the active cluster name.
func (c *NamedContext) MustCurrentClusterName() string {
cl, err := conn.config.CurrentClusterName()
if err != nil {
panic(err)
}
return cl
}
// Context represents a Kubernetes Context.
@ -34,17 +37,21 @@ func NewContext() Res {
// Get a Context.
func (*Context) Get(_, n string) (interface{}, error) {
return &NamedContext{
Name: n,
Context: conn.apiConfigOrDie().Contexts[n],
}, nil
ctx, err := conn.config.GetContext(n)
if err != nil {
return nil, err
}
return &NamedContext{Name: n, Context: ctx}, nil
}
// List all Contexts in a given namespace
func (*Context) List(string) (Collection, error) {
con := conn.apiConfigOrDie()
cc := make([]interface{}, 0, len(con.Contexts))
for k, v := range con.Contexts {
ctxs, err := conn.config.Contexts()
if err != nil {
return Collection{}, err
}
cc := make([]interface{}, 0, len(ctxs))
for k, v := range ctxs {
cc = append(cc, &NamedContext{k, v})
}
return cc, nil
@ -52,18 +59,18 @@ func (*Context) List(string) (Collection, error) {
// Delete a Context
func (*Context) Delete(_, n string) error {
con := conn.apiConfigOrDie()
if con.CurrentContext == n {
ctx, err := conn.config.CurrentContextName()
if err != nil {
return err
}
if ctx == n {
return fmt.Errorf("trying to delete your current context %s", n)
}
delete(con.Contexts, n)
return clientcmd.ModifyConfig(conn.configAccess(), con, true)
return conn.config.DelContext(n)
}
// Switch cluster Context.
func (*Context) Switch(n string) error {
con := conn.apiConfigOrDie()
con.CurrentContext = n
return clientcmd.ModifyConfig(conn.configAccess(), con, true)
conn.switchContextOrDie(n)
return nil
}

View File

@ -3,7 +3,7 @@ package resource
import (
"path"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

View File

@ -1,15 +1,17 @@
package resource
import (
"github.com/derailed/k9s/resource/k8s"
"k8s.io/api/core/v1"
"github.com/derailed/k9s/internal/k8s"
v1 "k8s.io/api/core/v1"
)
type (
// ClusterIfc represents a cluster.
ClusterIfc interface {
Version() (string, error)
ContextName() string
ClusterName() string
UserName() string
}
// MetricsIfc represents a metrics server.
@ -45,11 +47,21 @@ func (c *Cluster) Version() string {
return info
}
// Name returns the cluster name
func (c *Cluster) Name() string {
// ContextName returns the context name.
func (c *Cluster) ContextName() string {
return c.api.ContextName()
}
// ClusterName returns the cluster name.
func (c *Cluster) ClusterName() string {
return c.api.ClusterName()
}
// UserName returns the user name.
func (c *Cluster) UserName() string {
return c.api.UserName()
}
// Metrics gathers node level metrics and compute utilization percentages.
func (c *Cluster) Metrics() (k8s.Metric, error) {
return c.mx.NodeMetrics()

View File

@ -4,10 +4,9 @@ import (
"fmt"
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource"
m "github.com/petergtz/pegomock"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)
@ -38,7 +37,7 @@ func TestClusterName(t *testing.T) {
m.When(cIfc.ClusterName()).ThenReturn("fred")
ci := resource.NewClusterWithArgs(cIfc, mxIfc)
assert.Equal(t, "fred", ci.Name())
assert.Equal(t, "fred", ci.ClusterName())
}
func TestClusterMetrics(t *testing.T) {
@ -58,7 +57,7 @@ func TestClusterMetrics(t *testing.T) {
func setup(t *testing.T) {
m.RegisterMockTestingT(t)
m.RegisterMockFailHandler(func(m string, i ...int) {
log.Println("Boom!", m, i)
fmt.Println("Boom!", m, i)
})
}

View File

@ -4,9 +4,9 @@ import (
"log"
"strconv"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
yaml "gopkg.in/yaml.v2"
"k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
)
// ConfigMap tracks a kubernetes resource.

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"

View File

@ -1,7 +1,7 @@
package resource
import (
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
)
@ -79,7 +79,7 @@ func (r *Context) Fields(ns string) Row {
i := r.instance
name := i.Name
if i.CurrentCluster() == name {
if i.MustCurrentClusterName() == name {
name += "*"
}

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"k8s.io/client-go/tools/clientcmd/api"

View File

@ -1,7 +1,7 @@
package resource
import (
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
"k8s.io/api/rbac/v1"

View File

@ -1,7 +1,7 @@
package resource
import (
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v2"
"k8s.io/api/rbac/v1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
rbacv1 "k8s.io/api/rbac/v1"

View File

@ -1,14 +1,14 @@
package resource_test
import (
"fmt"
"strings"
"testing"
"time"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
"github.com/derailed/k9s/internal/resource"
m "github.com/petergtz/pegomock"
log "github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -122,7 +122,7 @@ func newClusterRole() resource.Columnar {
func testTime() time.Time {
t, err := time.Parse(time.RFC3339, "2018-12-14T10:36:43.326972-07:00")
if err != nil {
log.Println("TestTime Failed", err)
fmt.Println("TestTime Failed", err)
}
return t
}

View File

@ -3,7 +3,7 @@ package resource
import (
"time"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

View File

@ -3,7 +3,7 @@ package resource
import (
"strconv"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
batchv1beta1 "k8s.io/api/batch/v1beta1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
batchv1beta1 "k8s.io/api/batch/v1beta1"

View File

@ -7,7 +7,7 @@ import (
"path"
"strings"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
@ -153,13 +153,13 @@ func (*Custom) ExtFields() Properties {
return Properties{}
}
func getCRDS() map[string]k8s.ApiGroup {
m := map[string]k8s.ApiGroup{}
func getCRDS() map[string]k8s.APIGroup {
m := map[string]k8s.APIGroup{}
list := NewCRDList("")
ll, _ := list.Resource().List("")
for _, l := range ll {
ff := l.ExtFields()
grp := k8s.ApiGroup{
grp := k8s.APIGroup{
Resource: ff["name"].(string),
Version: ff["version"].(string),
Group: ff["group"].(string),

View File

@ -3,7 +3,7 @@ package resource
import (
"strconv"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
"k8s.io/api/apps/v1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"

View File

@ -3,7 +3,7 @@ package resource
import (
"strconv"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
extv1beta1 "k8s.io/api/extensions/v1beta1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
extv1beta1 "k8s.io/api/extensions/v1beta1"

View File

@ -5,7 +5,7 @@ import (
"strconv"
"strings"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
"k8s.io/api/core/v1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"

View File

@ -4,7 +4,7 @@ import (
"regexp"
"strconv"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
"k8s.io/api/core/v1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"

View File

@ -4,7 +4,7 @@ import (
"fmt"
"strconv"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
"k8s.io/api/autoscaling/v1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"k8s.io/api/autoscaling/v1"

View File

@ -3,7 +3,7 @@ package resource
import (
"strings"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
"k8s.io/api/core/v1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"k8s.io/api/extensions/v1beta1"

View File

@ -4,7 +4,7 @@ import (
"fmt"
"time"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
"k8s.io/api/batch/v1"

View File

@ -3,8 +3,8 @@ package resource_test
import (
"testing"
"github.com/derailed/k9s/resource"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/resource"
"github.com/derailed/k9s/internal/k8s"
m "github.com/petergtz/pegomock"
"github.com/stretchr/testify/assert"
"k8s.io/api/batch/v1"

View File

@ -4,7 +4,7 @@ import (
"reflect"
"sort"
"github.com/derailed/k9s/resource/k8s"
"github.com/derailed/k9s/internal/k8s"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/watch"
)

View File

@ -1,10 +1,10 @@
// Code generated by pegomock. DO NOT EDIT.
// Source: github.com/derailed/k9s/resource (interfaces: Caller)
// Source: github.com/derailed/k9s/internal/resource (interfaces: Caller)
package resource_test
import (
k8s "github.com/derailed/k9s/resource/k8s"
k8s "github.com/derailed/k9s/internal/k8s"
pegomock "github.com/petergtz/pegomock"
"reflect"
"time"

Some files were not shown because too many files have changed in this diff Show More