checkpoint

mine
derailed 2019-11-15 10:06:30 -07:00
parent afe41cd924
commit 56e4dc9ba8
144 changed files with 1093 additions and 1017 deletions

View File

@ -117,7 +117,7 @@ linters-settings:
min-complexity: 10
gocognit:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 10
min-complexity: 20
maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true
@ -179,8 +179,8 @@ linters-settings:
# See https://go-critic.github.io/overview#checks-overview
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run`
# By default list of stable checks is used.
enabled-checks:
- rangeValCopy
# enabled-checks:
# - rangeValCopy
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty
disabled-checks:
@ -230,9 +230,12 @@ linters:
enable:
- megacheck
- govet
- funlen
- gocyclo
disable:
- maligned
- prealloc
- gosec
disable-all: false
presets:
- bugs
@ -256,6 +259,9 @@ issues:
- errcheck
- dupl
- gosec
- funlen
- goconst
- gocognit
# Exclude known linters from partially hard-vendored code,
# which is impossible to exclude via "nolint" comments.
@ -296,6 +302,5 @@ issues:
# Show only new issues created after git revision `REV`
new-from-rev: REV
# Show only new issues created in git patch with set file path.
new-from-patch: path/to/patch/file
# new-from-patch: path/to/patch/file

View File

@ -38,16 +38,25 @@ var (
)
func init() {
const falseFlag = "false"
rootCmd.AddCommand(versionCmd(), infoCmd())
initK9sFlags()
initK8sFlags()
// Klogs (of course) want to print stuff to the screen ;(
klog.InitFlags(nil)
flag.Set("log_file", config.K9sLogs)
flag.Set("stderrthreshold", "fatal")
flag.Set("alsologtostderr", "false")
flag.Set("logtostderr", "false")
if err := flag.Set("log_file", config.K9sLogs); err != nil {
log.Error().Err(err)
}
if err := flag.Set("stderrthreshold", "fatal"); err != nil {
log.Error().Err(err)
}
if err := flag.Set("alsologtostderr", falseFlag); err != nil {
log.Error().Err(err)
}
if err := flag.Set("logtostderr", falseFlag); err != nil {
log.Error().Err(err)
}
}
// Execute root command
@ -64,7 +73,7 @@ func run(cmd *cobra.Command, args []string) {
log.Error().Msgf("Boom! %v", err)
log.Error().Msg(string(debug.Stack()))
printLogo(color.Red)
fmt.Printf(color.Colorize("Boom!! ", color.Red))
fmt.Printf("%s", color.Colorize("Boom!! ", color.Red))
fmt.Println(color.Colorize(fmt.Sprintf("%v.", err), color.White))
}
}()
@ -85,6 +94,7 @@ func loadConfiguration() *config.Config {
// Load K9s config file...
k8sCfg := k8s.NewConfig(k8sFlags)
k9sCfg := config.NewConfig(k8sCfg)
if err := k9sCfg.Load(config.K9sConfigFile); err != nil {
log.Warn().Msg("Unable to locate K9s config. Generating new configuration...")
}
@ -101,8 +111,8 @@ func loadConfiguration() *config.Config {
k9sCfg.K9s.OverrideCommand(*k9sFlags.Command)
}
if k9sFlags.AllNamespaces != nil && *k9sFlags.AllNamespaces {
k9sCfg.SetActiveNamespace(resource.AllNamespaces)
if isBoolSet(k9sFlags.AllNamespaces) && k9sCfg.SetActiveNamespace(resource.AllNamespaces) != nil {
log.Error().Msg("Setting active namespace")
}
if err := k9sCfg.Refine(k8sFlags); err != nil {
@ -115,11 +125,17 @@ func loadConfiguration() *config.Config {
log.Panic().Err(err).Msg("K9s can't connect to cluster")
}
log.Info().Msg("✅ Kubernetes connectivity")
k9sCfg.Save()
if err := k9sCfg.Save(); err != nil {
log.Error().Err(err).Msg("Config save")
}
return k9sCfg
}
func isBoolSet(b *bool) bool {
return b != nil && *b
}
func parseLevel(level string) zerolog.Level {
switch level {
case "debug":

View File

@ -17,7 +17,8 @@ func TestColorize(t *testing.T) {
"default": {"blee", 0, "\x1b[37mblee\x1b[0m"},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, Colorize(u.s, u.c))
})

View File

@ -27,6 +27,15 @@ func NewAliases() Aliases {
}
func (a Aliases) loadDefaults() {
const (
contexts = "contexts"
portFwds = "portforwards"
benchmarks = "benchmarks"
dumps = "screendumps"
groups = "groups"
users = "users"
)
a.Alias["dp"] = "apps/v1/deployments"
a.Alias["sec"] = "v1/secrets"
a.Alias["jo"] = "batch/v1/jobs"
@ -36,34 +45,34 @@ func (a Aliases) loadDefaults() {
a.Alias["rob"] = "rbac.authorization.k8s.io/v1/rolebindings"
a.Alias["np"] = "networking.k8s.io/v1/networkpolicies"
{
a.Alias["ctx"] = "contexts"
a.Alias["contexts"] = "contexts"
a.Alias["context"] = "contexts"
a.Alias["ctx"] = contexts
a.Alias[contexts] = contexts
a.Alias["context"] = contexts
}
{
a.Alias["usr"] = "users"
a.Alias["users"] = "users"
a.Alias["user"] = "user"
a.Alias["usr"] = users
a.Alias[users] = users
a.Alias["user"] = users
}
{
a.Alias["grp"] = "groups"
a.Alias["group"] = "groups"
a.Alias["groups"] = "groups"
a.Alias["grp"] = groups
a.Alias["group"] = groups
a.Alias[groups] = groups
}
{
a.Alias["pf"] = "portforwards"
a.Alias["portforwards"] = "portforwards"
a.Alias["portforward"] = "portforwards"
a.Alias["pf"] = portFwds
a.Alias[portFwds] = portFwds
a.Alias["portforward"] = portFwds
}
{
a.Alias["be"] = "benchmarks"
a.Alias["benchmark"] = "benchmarks"
a.Alias["benchmarks"] = "benchmarks"
a.Alias["be"] = benchmarks
a.Alias["benchmark"] = benchmarks
a.Alias[benchmarks] = benchmarks
}
{
a.Alias["sd"] = "screendumps"
a.Alias["screendump"] = "screendumps"
a.Alias["screendumps"] = "screendumps"
a.Alias["sd"] = dumps
a.Alias["screendump"] = dumps
a.Alias[dumps] = dumps
}
}

View File

@ -13,7 +13,7 @@ func TestAliasDefine(t *testing.T) {
aliases []string
}
tts := []struct {
uu := []struct {
name string
aliases []aliasDef
registeredCommands map[string]string
@ -51,15 +51,16 @@ func TestAliasDefine(t *testing.T) {
},
}
for _, tt := range tts {
t.Run(tt.name, func(t *testing.T) {
for i := range uu {
u := uu[i]
t.Run(u.name, func(t *testing.T) {
configAlias := config.NewAliases()
for _, aliases := range tt.aliases {
for _, aliases := range u.aliases {
for _, a := range aliases.aliases {
configAlias.Define(aliases.cmd, a)
}
}
for alias, cmd := range tt.registeredCommands {
for alias, cmd := range u.registeredCommands {
v, ok := configAlias.Get(alias)
assert.True(t, ok)
assert.Equal(t, cmd, v, "Wrong command for alias "+alias)
@ -70,18 +71,17 @@ func TestAliasDefine(t *testing.T) {
func TestAliasesLoad(t *testing.T) {
a := config.NewAliases()
assert.Nil(t, a.LoadAliases("test_assets/alias.yml"))
assert.Nil(t, a.LoadAliases("test_assets/alias.yml"))
assert.Equal(t, 27, len(a.Alias))
}
func TestAliasesSave(t *testing.T) {
a := config.NewAliases()
a.Alias["test"] = "fred"
a.Alias["blee"] = "duh"
a.SaveAliases("/tmp/a.yml")
assert.Nil(t, a.SaveAliases("/tmp/a.yml"))
assert.Nil(t, a.LoadAliases("/tmp/a.yml"))
assert.Equal(t, 28, len(a.Alias))
}

View File

@ -16,7 +16,8 @@ func TestBenchEmpty(t *testing.T) {
"notEmpty": {newBenchmark(), false},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.b.empty())
})
@ -46,7 +47,8 @@ func TestBenchLoad(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench(u.file)
@ -95,7 +97,8 @@ func TestBenchServiceLoad(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench("test_assets/b_good.yml")
@ -165,7 +168,8 @@ func TestBenchContainerLoad(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
b, err := NewBench("test_assets/b_containers.yml")

View File

@ -84,7 +84,9 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error {
}
c.K9s.CurrentCluster = ctx.Cluster
if len(ctx.Namespace) != 0 {
c.SetActiveNamespace(ctx.Namespace)
if err := c.SetActiveNamespace(ctx.Namespace); err != nil {
return err
}
}
if isSet(flags.ClusterName) {
@ -92,7 +94,9 @@ func (c *Config) Refine(flags *genericclioptions.ConfigFlags) error {
}
if isSet(flags.Namespace) {
c.SetActiveNamespace(*flags.Namespace)
if err := c.SetActiveNamespace(*flags.Namespace); err != nil {
return err
}
}
return nil

View File

@ -54,7 +54,8 @@ func TestConfigRefine(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
mc := NewMockConnection()
m.When(mc.ValidNamespaces()).ThenReturn(namespaces(), nil)
@ -142,7 +143,7 @@ func TestConfigSetActiveNamespace(t *testing.T) {
cfg := config.NewConfig(mk)
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.SetActiveNamespace("default")
assert.Nil(t, cfg.SetActiveNamespace("default"))
assert.Equal(t, "default", cfg.ActiveNamespace())
}
@ -202,7 +203,7 @@ func TestConfigSaveFile(t *testing.T) {
cfg := config.NewConfig(mk)
cfg.SetConnection(mc)
cfg.Load("test_assets/k9s.yml")
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.K9s.RefreshRate = 100
cfg.K9s.LogBufferSize = 500
cfg.K9s.LogRequestSize = 100
@ -231,7 +232,7 @@ func TestConfigReset(t *testing.T) {
cfg := config.NewConfig(mk)
cfg.SetConnection(mc)
cfg.Load("test_assets/k9s.yml")
assert.Nil(t, cfg.Load("test_assets/k9s.yml"))
cfg.Reset()
cfg.Validate()

View File

@ -66,17 +66,12 @@ type (
CanIAccess(ns, rvg string, verbs []string) (bool, error)
}
k8sClient struct {
client kubernetes.Interface
dClient dynamic.Interface
nsClient dynamic.NamespaceableResourceInterface
mxsClient *versioned.Clientset
}
// APIClient represents a Kubernetes api client.
APIClient struct {
k8sClient
client kubernetes.Interface
dClient dynamic.Interface
nsClient dynamic.NamespaceableResourceInterface
mxsClient *versioned.Clientset
cachedDiscovery *disk.CachedDiscoveryClient
config *Config
useMetricServer bool

View File

@ -5,6 +5,8 @@ import (
v1 "k8s.io/api/core/v1"
)
const na = "n/a"
// Cluster represents a Kubernetes cluster.
type Cluster struct {
Connection
@ -32,7 +34,7 @@ func (c *Cluster) ContextName() string {
ctx, err := c.Config().CurrentContextName()
if err != nil {
c.logger.Warn().Msgf("%s", err)
return "N/A"
return na
}
return ctx
}
@ -42,7 +44,7 @@ func (c *Cluster) ClusterName() string {
ctx, err := c.Config().CurrentClusterName()
if err != nil {
c.logger.Warn().Msgf("%s", err)
return "N/A"
return na
}
return ctx
}
@ -52,7 +54,7 @@ func (c *Cluster) UserName() string {
usr, err := c.Config().CurrentUserName()
if err != nil {
c.logger.Warn().Msgf("%s", err)
return "N/A"
return na
}
return usr
}

View File

@ -12,8 +12,6 @@ import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
const defaultNamespace = "default"
// Config tracks a kubernetes configuration.
type Config struct {
flags *genericclioptions.ConfigFlags
@ -283,7 +281,6 @@ func (c *Config) ensureConfig() {
log.Debug().Msg("Loading raw config from flags...")
c.clientConfig = c.flags.ToRawKubeConfigLoader()
return
}
// ----------------------------------------------------------------------------

View File

@ -99,7 +99,9 @@ func (c *Context) KubeUpdate(n string) error {
if err != nil {
return err
}
c.Switch(n)
if err := c.Switch(n); err != nil {
return err
}
return clientcmd.ModifyConfig(
clientcmd.NewDefaultPathOptions(), config, true,
)

View File

@ -1,6 +1,8 @@
package k8s
import (
"errors"
batchv1 "k8s.io/api/batch/v1"
batchv1beta1 "k8s.io/api/batch/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -50,8 +52,11 @@ func (c *CronJob) Run(ns, n string) error {
if err != nil {
return err
}
cronJob := cj.(*batchv1beta1.CronJob)
cronJob, ok := cj.(*batchv1beta1.CronJob)
if !ok {
return errors.New("Expecting valid cronjob")
}
var jobName = cronJob.Name
if len(cronJob.Name) >= maxJobNameSize {
jobName = cronJob.Name[0:maxJobNameSize]

View File

@ -13,12 +13,13 @@ func TestAsGR(t *testing.T) {
gvr string
e schema.GroupVersion
}{
"full": {"apps/v1/deployments", schema.GroupVersion{"apps", "v1"}},
"core": {"v1/pods", schema.GroupVersion{"", "v1"}},
"bork": {"users", schema.GroupVersion{"", ""}},
"full": {"apps/v1/deployments", schema.GroupVersion{Group: "apps", Version: "v1"}},
"core": {"v1/pods", schema.GroupVersion{Group: "", Version: "v1"}},
"bork": {"users", schema.GroupVersion{Group: "", Version: ""}},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).AsGR())
})
@ -34,7 +35,8 @@ func TestNewGVR(t *testing.T) {
"core": {"", "v1", "pods", "v1/pods"},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.NewGVR(u.g, u.v, u.r).String())
})
@ -49,7 +51,8 @@ func TestToGVR(t *testing.T) {
"core": {"v1", "pods", "v1/pods"},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.ToGVR(u.gv, u.r).String())
})
@ -67,7 +70,8 @@ func TestResName(t *testing.T) {
"empty": {"", ".."},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ResName())
})
@ -85,7 +89,8 @@ func TestToR(t *testing.T) {
"empty": {"", ""},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToR())
})
@ -103,7 +108,8 @@ func TestToG(t *testing.T) {
"empty": {"", ""},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToG())
})
@ -121,7 +127,8 @@ func TestToV(t *testing.T) {
"empty": {"", ""},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, k8s.GVR(u.gvr).ToV())
})
@ -138,7 +145,8 @@ func TestToStringer(t *testing.T) {
"empty": {""},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.gvr, k8s.GVR(u.gvr).String())
})

View File

@ -4,8 +4,6 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
var supportedAutoScalingAPIVersions = []string{"v2beta2", "v2beta1", "v1"}
// HorizontalPodAutoscalerV2Beta2 represents am HorizontalPodAutoscaler.
type HorizontalPodAutoscalerV2Beta2 struct {
*base

View File

@ -1,6 +1,7 @@
package k8s
import (
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
@ -51,7 +52,10 @@ func NewMetricsServer(c Connection) *MetricsServer {
// NodesMetrics retrieves metrics for a given set of nodes.
func (m *MetricsServer) NodesMetrics(nodes Collection, metrics *mv1beta1.NodeMetricsList, mmx NodesMetrics) {
for _, n := range nodes {
no := n.(*v1.Node)
no, ok := n.(*v1.Node)
if !ok {
log.Fatal().Msg("Expecting a valid node")
}
mmx[no.Name] = NodeMetrics{
AvailCPU: no.Status.Allocatable.Cpu().MilliValue(),
AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()),
@ -73,7 +77,10 @@ func (m *MetricsServer) NodesMetrics(nodes Collection, metrics *mv1beta1.NodeMet
func (m *MetricsServer) ClusterLoad(nos Collection, nmx Collection, mx *ClusterMetrics) {
nodeMetrics := make(NodesMetrics, len(nos))
for _, n := range nos {
no := n.(*v1.Node)
no, ok := n.(*v1.Node)
if !ok {
log.Fatal().Msg("Expecting valid node")
}
nodeMetrics[no.Name] = NodeMetrics{
AvailCPU: no.Status.Allocatable.Cpu().MilliValue(),
AvailMEM: ToMB(no.Status.Allocatable.Memory().Value()),
@ -81,7 +88,10 @@ func (m *MetricsServer) ClusterLoad(nos Collection, nmx Collection, mx *ClusterM
}
for _, mx := range nmx {
mxx := mx.(*mv1beta1.NodeMetrics)
mxx, ok := mx.(*mv1beta1.NodeMetrics)
if !ok {
log.Fatal().Msg("Expecting a valid node metric")
}
if m, ok := nodeMetrics[mxx.Name]; ok {
m.CurrentCPU = mxx.Usage.Cpu().MilliValue()
m.CurrentMEM = ToMB(mxx.Usage.Memory().Value())

View File

@ -54,7 +54,7 @@ func TestNodesMetrics(t *testing.T) {
nodes := Collection{
makeNode("n1", "32", "128Gi", "50m", "2Mi"),
makeNode("n2", "8", "4Gi", "50m", "2Mi"),
makeNode("n2", "8", "4Gi", "50m", "10Mi"),
}
metrics := v1beta1.NodeMetricsList{
@ -79,8 +79,8 @@ func TestNodesMetrics(t *testing.T) {
func BenchmarkNodesMetrics(b *testing.B) {
nodes := Collection{
makeNode("n1", "100m", "4Mi", "50m", "2Mi"),
makeNode("n2", "100m", "4Mi", "50m", "2Mi"),
makeNode("n1", "100m", "4Mi", "100m", "2Mi"),
makeNode("n2", "100m", "4Mi", "100m", "2Mi"),
}
metrics := v1beta1.NodeMetricsList{

View File

@ -4,8 +4,6 @@ import (
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/rs/zerolog"
@ -19,7 +17,6 @@ import (
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/portforward"
"k8s.io/client-go/transport/spdy"
"k8s.io/kubectl/pkg/util"
)
const localhost = "localhost"
@ -150,39 +147,40 @@ func codec() (serializer.CodecFactory, runtime.ParameterCodec) {
return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme)
}
func svcPortToTargetPort(ports []string, svc v1.Service, pod v1.Pod) ([]string, error) {
var translated []string
for _, port := range ports {
localPort, remotePort := splitPort(port)
portnum, err := strconv.Atoi(remotePort)
if err != nil {
svcPort, err := util.LookupServicePortNumberByName(svc, remotePort)
if err != nil {
return nil, err
}
portnum = int(svcPort)
if localPort == remotePort {
localPort = strconv.Itoa(portnum)
}
}
containerPort, err := util.LookupContainerPortNumberByServicePort(svc, pod, int32(portnum))
if err != nil {
return nil, err
}
if int32(portnum) != containerPort {
port = fmt.Sprintf("%s:%d", localPort, containerPort)
}
translated = append(translated, port)
}
// BOZO!!
// func svcPortToTargetPort(ports []string, svc v1.Service, pod v1.Pod) ([]string, error) {
// var translated []string
// for _, port := range ports {
// localPort, remotePort := splitPort(port)
// portnum, err := strconv.Atoi(remotePort)
// if err != nil {
// svcPort, e := util.LookupServicePortNumberByName(svc, remotePort)
// if e != nil {
// return nil, e
// }
// portnum = int(svcPort)
// if localPort == remotePort {
// localPort = strconv.Itoa(portnum)
// }
// }
// containerPort, err := util.LookupContainerPortNumberByServicePort(svc, pod, int32(portnum))
// if err != nil {
// return nil, err
// }
// if int32(portnum) != containerPort {
// port = fmt.Sprintf("%s:%d", localPort, containerPort)
// }
// translated = append(translated, port)
// }
return translated, nil
}
// return translated, nil
// }
func splitPort(port string) (local, remote string) {
parts := strings.Split(port, ":")
if len(parts) == 2 {
return parts[0], parts[1]
}
// func splitPort(port string) (local, remote string) {
// parts := strings.Split(port, ":")
// if len(parts) == 2 {
// return parts[0], parts[1]
// }
return parts[0], parts[0]
}
// return parts[0], parts[0]
// }

View File

@ -25,7 +25,8 @@ func TestHints(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
h := model.NewHint()
l := hintL{count: -1}

View File

@ -14,7 +14,7 @@ type MenuHint struct {
// IsBlank checks if menu hint is a place holder.
func (m MenuHint) IsBlank() bool {
return m.Mnemonic == "" && m.Description == "" && m.Visible == false
return m.Mnemonic == "" && m.Description == "" && !m.Visible
}
// MenuHints represents a collection of hints.

View File

@ -41,7 +41,8 @@ func TestStackPush(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
s := model.NewStack()
for _, c := range u.items {
@ -77,7 +78,8 @@ func TestStackTop(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
s := model.NewStack()
for _, item := range u.items {

View File

@ -75,10 +75,6 @@ func (b *Benchmark) init(base string) error {
return nil
}
func (b *Benchmark) annulled() bool {
return b.canceled
}
// Cancel kills the benchmark in progress.
func (b *Benchmark) Cancel() {
if b == nil {
@ -118,14 +114,19 @@ func (b *Benchmark) save(cluster string, r io.Reader) error {
if err != nil {
return err
}
defer f.Close()
defer func() {
if e := f.Close(); e != nil {
log.Fatal().Err(e).Msg("Bench save")
}
}()
bb, err := ioutil.ReadAll(r)
if err != nil {
return err
}
f.Write(bb)
if _, err := f.Write(bb); err != nil {
return err
}
return nil
}

View File

@ -163,8 +163,11 @@ func (*Base) marshalObject(o runtime.Object) (string, error) {
}
func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]string, opts LogOptions) error {
i := ctx.Value(IKey("informer")).(*watch.Informer)
pods, err := i.List(watch.PodIndex, opts.Namespace, metav1.ListOptions{
inf, ok := ctx.Value(IKey("informer")).(*watch.Informer)
if !ok {
return errors.New("Expecting valid informer")
}
pods, err := inf.List(watch.PodIndex, opts.Namespace, metav1.ListOptions{
LabelSelector: toSelector(sel),
})
if err != nil {
@ -176,7 +179,10 @@ func (b *Base) podLogs(ctx context.Context, c chan<- string, sel map[string]stri
}
pr := NewPod(b.Connection)
for _, p := range pods {
po := p.(*v1.Pod)
po, ok := p.(*v1.Pod)
if !ok {
return errors.New("Expecting valid pod")
}
if po.Status.Phase == v1.PodRunning {
opts.Namespace, opts.Name = po.Namespace, po.Name
if err := pr.PodLogs(ctx, c, opts); err != nil {

View File

@ -2,6 +2,7 @@ package resource
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
@ -9,6 +10,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1"
)
@ -48,7 +50,12 @@ func NewContainer(c Connection, pod *v1.Pod) *Container {
// New builds a new Container instance from a k8s resource.
func (r *Container) New(i interface{}) Columnar {
co := NewContainer(r.Connection, r.pod)
co.instance = i.(v1.Container)
coi, ok := i.(v1.Container)
if !ok {
log.Error().Err(errors.New("Expecting a container resource"))
return nil
}
co.instance = coi
co.path = r.namespacedName(r.pod.ObjectMeta) + ":" + co.instance.Name
return co
@ -228,9 +235,9 @@ func toState(s v1.ContainerState) string {
if s.Terminated.Reason != "" {
return s.Terminated.Reason
}
return "Terminating"
return Terminating
case s.Running != nil:
return "Running"
return Running
default:
return MissingValue
}

View File

@ -17,7 +17,8 @@ func TestProbe(t *testing.T) {
"undefined": {nil, "off"},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, probe(u.probe))
})
@ -34,7 +35,8 @@ func TestAsMi(t *testing.T) {
"10Mb": {10 * 1024 * 1024, 1.048576e+07},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, asMi(u.mem))
})
@ -55,7 +57,8 @@ func TestToRes(t *testing.T) {
"0", "0"},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
cpu, mem := toRes(u.res)
assert.Equal(t, u.ecpu, cpu)
@ -93,7 +96,8 @@ func TestToState(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, toState(u.state))
})

View File

@ -120,14 +120,6 @@ func k8sConfig() *k8s.Config {
return k8s.NewConfig(&f)
}
func k8sCTX() *api.Context {
return &api.Context{
LocationOfOrigin: "fred",
Cluster: "blee",
AuthInfo: "secret",
}
}
func k8sNamedCTX() *k8s.NamedContext {
return k8s.NewNamedContext(
k8sConfig(),

View File

@ -1,6 +1,8 @@
package resource
import (
"errors"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/rbac/v1"
@ -54,7 +56,10 @@ func (r *ClusterRole) Marshal(path string) (string, error) {
return "", err
}
cr := i.(*v1.ClusterRole)
cr, ok := i.(*v1.ClusterRole)
if !ok {
return "", errors.New("Expecting a cr resource")
}
cr.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1"
cr.TypeMeta.Kind = "ClusterRole"

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strings"
"github.com/derailed/k9s/internal/k8s"
@ -56,7 +57,10 @@ func (r *ClusterRoleBinding) Marshal(path string) (string, error) {
return "", err
}
crb := i.(*v1.ClusterRoleBinding)
crb, ok := i.(*v1.ClusterRoleBinding)
if !ok {
return "", errors.New("Expecting a crb resource")
}
crb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1"
crb.TypeMeta.Kind = "ClusterRoleBinding"

View File

@ -73,7 +73,7 @@ func k8sCRB() *rbacv1.ClusterRoleBinding {
ObjectMeta: metav1.ObjectMeta{
Name: "fred",
Namespace: "blee",
CreationTimestamp: metav1.Time{testTime()},
CreationTimestamp: metav1.Time{Time: testTime()},
},
Subjects: []rbacv1.Subject{
{Kind: "test", Name: "fred", Namespace: "blee"},

View File

@ -94,7 +94,7 @@ func k8sCR() *rbacv1.ClusterRole {
ObjectMeta: metav1.ObjectMeta{
Name: "fred",
Namespace: "blee",
CreationTimestamp: metav1.Time{testTime()},
CreationTimestamp: metav1.Time{Time: testTime()},
},
Rules: []rbacv1.PolicyRule{
{

View File

@ -48,8 +48,16 @@ func (r *CustomResourceDefinition) New(i interface{}) Columnar {
default:
log.Fatal().Msgf("unknown CustomResourceDefinition type %#v", i)
}
meta := c.instance.Object["metadata"].(map[string]interface{})
c.path = meta["name"].(string)
meta, ok := c.instance.Object["metadata"].(map[string]interface{})
if !ok {
log.Error().Err(errors.New("Expecting a map interface")).Msg("CRD New")
return nil
}
c.path, ok = meta["name"].(string)
if !ok {
log.Error().Err(errors.New("Expecting a string name")).Msg("CRD New")
return nil
}
return c
}
@ -82,13 +90,16 @@ func (r *CustomResourceDefinition) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
i := r.instance
meta := i.Object["metadata"].(map[string]interface{})
meta, ok := i.Object["metadata"].(map[string]interface{})
if !ok {
log.Fatal().Err(errors.New("Expecting a map interface")).Msg("CRD Fields")
}
t, err := time.Parse(time.RFC3339, meta["creationTimestamp"].(string))
if err != nil {
log.Error().Msgf("Fields timestamp %v", err)
}
return append(ff, meta["name"].(string), toAge(metav1.Time{t}))
return append(ff, meta["name"].(string), toAge(metav1.Time{Time: t}))
}
// ExtFields returns extended fields.
@ -97,20 +108,33 @@ func (r *CustomResourceDefinition) ExtFields() (TypeMeta, error) {
i := r.instance
spec, ok := i.Object["spec"].(map[string]interface{})
if !ok {
return m, errors.New("missing crd specs")
return m, errors.New("expecting interface map spec")
}
if meta, ok := i.Object["metadata"].(map[string]interface{}); ok {
m.Name = meta["name"].(string)
if meta, k := i.Object["metadata"].(map[string]interface{}); k {
m.Name, ok = meta["name"].(string)
if !ok {
return m, errors.New("expecting meta string name")
}
}
m.Group, m.Version = spec["group"].(string), spec["version"].(string)
m.Namespaced = isNamespaced(spec["scope"].(string))
names, ok := spec["names"].(map[string]interface{})
if !ok {
return m, errors.New("missing crd names")
return m, errors.New("expecting crd interface map names")
}
m.Kind, ok = names["kind"].(string)
if !ok {
return m, errors.New("expecting string kind")
}
m.Singular, ok = names["singular"].(string)
if !ok {
return m, errors.New("expecting string singular")
}
m.Plural, ok = names["plural"].(string)
if !ok {
return m, errors.New("expecting string plural")
}
m.Kind = names["kind"].(string)
m.Singular, m.Plural = names["singular"].(string), names["plural"].(string)
if names["shortNames"] != nil {
for _, s := range names["shortNames"].([]interface{}) {
m.ShortNames = append(m.ShortNames, s.(string))

View File

@ -103,33 +103,6 @@ func k8sCRD() *unstructured.Unstructured {
}
}
func k8sCRDFull() *unstructured.Unstructured {
return &unstructured.Unstructured{
Object: map[string]interface{}{
"metadata": map[string]interface{}{
"namespace": "blee",
"name": "fred",
"creationTimestamp": "2018-12-14T10:36:43.326972Z",
},
"spec": map[string]interface{}{
"group": "apps",
"version": "v1",
"names": map[string]interface{}{
"kind": "cool",
"singular": "cool",
"plural": "cools",
"shortNamed": []string{"co", "cos"},
},
},
},
}
}
func newCRDFull() resource.Columnar {
mc := NewMockConnection()
return resource.NewCustomResourceDefinition(mc).New(k8sCRDFull())
}
func newCRD() resource.Columnar {
mc := NewMockConnection()
return resource.NewCustomResourceDefinition(mc).New(k8sCRD())

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"fmt"
"strconv"
@ -69,7 +70,10 @@ func (r *CronJob) Marshal(path string) (string, error) {
return "", err
}
cj := i.(*batchv1beta1.CronJob)
cj, ok := i.(*batchv1beta1.CronJob)
if !ok {
return "", errors.New("expecting cronjob resource")
}
cj.TypeMeta.APIVersion = "extensions/batchv1beta1"
cj.TypeMeta.Kind = "CronJob"

View File

@ -48,6 +48,20 @@ func NewCustom(c k8s.Connection, gvr k8s.GVR) *Custom {
return cr
}
func mustExtractMeta(o map[string]interface{}) map[string]interface{} {
if m, ok := o["metadata"].(map[string]interface{}); ok {
return m
}
panic("unable to extract meta")
}
func mustExtractStr(o map[string]interface{}, k string) string {
if s, ok := o[k].(string); ok {
return s
}
panic("unable to extract string for key `" + k)
}
// New builds a new Custom instance from a k8s resource.
func (r *Custom) New(i interface{}) Columnar {
cr := NewCustom(r.Connection, "")
@ -64,14 +78,10 @@ func (r *Custom) New(i interface{}) Columnar {
if err != nil {
log.Error().Err(err)
}
meta := obj["metadata"].(map[string]interface{})
ns := ""
if n, ok := meta["namespace"]; ok {
ns = n.(string)
}
name := meta["name"].(string)
cr.path = path.Join(ns, name)
cr.gvr = k8s.NewGVR(obj["kind"].(string), obj["apiVersion"].(string), name)
meta := mustExtractMeta(obj)
ns, n := mustExtractStr(meta, "namespace"), mustExtractStr(meta, "name")
cr.path = path.Join(ns, n)
cr.gvr = k8s.NewGVR(obj["kind"].(string), obj["apiVersion"].(string), n)
return cr
}
@ -107,7 +117,10 @@ func (r *Custom) List(ns string, opts v1.ListOptions) (Columnars, error) {
return Columnars{}, errors.New("no resources found")
}
table := ii[0].(*metav1beta1.Table)
table, ok := ii[0].(*metav1beta1.Table)
if !ok {
return nil, errors.New("expecting a table resource")
}
r.headers = make(Row, len(table.ColumnDefinitions))
for i, h := range table.ColumnDefinitions {
r.headers[i] = h.Name
@ -146,9 +159,11 @@ func (r *Custom) Fields(ns string) Row {
return Row{}
}
meta := obj["metadata"].(map[string]interface{})
meta, ok := obj["metadata"].(map[string]interface{})
if !ok {
log.Fatal().Msg("expecting interface map meta")
}
rns, ok := meta["namespace"].(string)
if ns == AllNamespaces {
if ok {
ff = append(ff, rns)

View File

@ -2,6 +2,7 @@ package resource
import (
"context"
"errors"
"fmt"
"strconv"
@ -62,7 +63,10 @@ func (r *Deployment) Marshal(path string) (string, error) {
return "", err
}
dp := i.(*appsv1.Deployment)
dp, ok := i.(*appsv1.Deployment)
if !ok {
return "", errors.New("expecting dp resource")
}
dp.TypeMeta.APIVersion = "apps/v1"
dp.TypeMeta.Kind = "Deployment"
@ -75,7 +79,10 @@ func (r *Deployment) Logs(ctx context.Context, c chan<- string, opts LogOptions)
if err != nil {
return err
}
dp := instance.(*appsv1.Deployment)
dp, ok := instance.(*appsv1.Deployment)
if !ok {
return errors.New("Expecting valid deployment")
}
if dp.Spec.Selector == nil || len(dp.Spec.Selector.MatchLabels) == 0 {
return fmt.Errorf("No valid selector found on deployment %s", opts.Name)
}

View File

@ -2,6 +2,7 @@ package resource
import (
"context"
"errors"
"fmt"
"strconv"
@ -61,7 +62,10 @@ func (r *DaemonSet) Marshal(path string) (string, error) {
return "", err
}
ds := i.(*appsv1.DaemonSet)
ds, ok := i.(*appsv1.DaemonSet)
if !ok {
return "", errors.New("expecting ds resource")
}
ds.TypeMeta.APIVersion = "apps/v1"
ds.TypeMeta.Kind = "DaemonSet"
@ -75,7 +79,10 @@ func (r *DaemonSet) Logs(ctx context.Context, c chan<- string, opts LogOptions)
return err
}
ds := instance.(*appsv1.DaemonSet)
ds, ok := instance.(*appsv1.DaemonSet)
if !ok {
return errors.New("expecting ds resource")
}
if ds.Spec.Selector == nil || len(ds.Spec.Selector.MatchLabels) == 0 {
return fmt.Errorf("No valid selector found on daemonset %s", opts.FQN())
}

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strconv"
"strings"
@ -57,7 +58,10 @@ func (r *Endpoints) Marshal(path string) (string, error) {
return "", err
}
ep := i.(*v1.Endpoints)
ep, ok := i.(*v1.Endpoints)
if !ok {
return "", errors.New("expecting ep resource")
}
ep.TypeMeta.APIVersion = "v1"
ep.TypeMeta.Kind = "Endpoint"

View File

@ -97,11 +97,6 @@ func k8sEndpoints() *v1.Endpoints {
}
}
func newEndpoints() resource.Columnar {
mc := NewMockConnection()
return resource.NewEndpoints(mc).New(k8sEndpoints())
}
func epYaml() string {
return `apiVersion: v1
kind: Endpoint

View File

@ -1,7 +1,7 @@
package resource
import (
"regexp"
"errors"
"strconv"
"github.com/derailed/k9s/internal/k8s"
@ -57,7 +57,10 @@ func (r *Event) Marshal(path string) (string, error) {
return "", err
}
ev := i.(*v1.Event)
ev, ok := i.(*v1.Event)
if !ok {
return "", errors.New("expecting evt resource")
}
ev.TypeMeta.APIVersion = "v1"
ev.TypeMeta.Kind = "Event"
@ -79,8 +82,6 @@ func (*Event) Header(ns string) Row {
return append(ff, "NAME", "REASON", "SOURCE", "COUNT", "MESSAGE", "AGE")
}
var rx = regexp.MustCompile(`(.+)\.(.+)`)
// Fields returns display fields.
func (r *Event) Fields(ns string) Row {
ff := make(Row, 0, len(r.Header(ns)))
@ -99,29 +100,3 @@ func (r *Event) Fields(ns string) Row {
toAge(i.LastTimestamp),
)
}
// ----------------------------------------------------------------------------
// Helpers...
func (*Event) toEmoji(t, r string) string {
switch t {
case "Warning":
switch r {
case "Failed":
return "😡"
case "Killing":
return "👿"
default:
return "😡"
}
default:
switch r {
case "Killing":
return "👿"
case "BackOff":
return "👹"
default:
return "😮"
}
}
}

View File

@ -33,6 +33,9 @@ const (
MissingValue = "<none>"
// NAValue indicates a value that does not pertain.
NAValue = "n/a"
// UnknownValue represents an unknown.
UnknownValue = "<unknown>"
)
// MetaFQN returns a fully qualified resource name.
@ -61,48 +64,16 @@ func toSelector(m map[string]string) string {
return strings.Join(s, ",")
}
func empty(s []string) bool {
for _, v := range s {
if len(v) != 0 {
return false
}
}
return true
}
// Join a slice of strings, skipping blanks.
func join(a []string, sep string) string {
switch len(a) {
case 0:
return ""
case 1:
return a[0]
}
var b []string
func join(a []string) string {
ss := make([]string, 0, len(a))
for _, s := range a {
if s != "" {
b = append(b, s)
ss = append(ss, s)
}
}
if len(b) == 0 {
return ""
}
n := len(sep) * (len(b) - 1)
for i := 0; i < len(b); i++ {
n += len(a[i])
}
var buff strings.Builder
buff.Grow(n)
buff.WriteString(a[0])
for _, s := range b[1:] {
buff.WriteString(sep)
buff.WriteString(s)
}
return buff.String()
return strings.Join(ss, ",")
}
// AsPerc prints a number as a percentage.
@ -141,10 +112,6 @@ func check(s, sub string) string {
return s
}
func intToStr(i int64) string {
return strconv.Itoa(int(i))
}
func boolToStr(b bool) string {
switch b {
case true:
@ -161,7 +128,7 @@ func toAge(timestamp metav1.Time) string {
func toAgeHuman(s string) string {
d, err := time.ParseDuration(s)
if err != nil {
return "<unknown>"
return UnknownValue
}
return duration.HumanDuration(d)

View File

@ -17,9 +17,10 @@ func TestJoin(t *testing.T) {
"sparse": {[]string{"a", "", "c"}, "a,c"},
}
for k, v := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, v.e, join(v.i, ","))
assert.Equal(t, u.e, join(u.i))
})
}
}

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strconv"
"github.com/derailed/k9s/internal/k8s"
@ -56,7 +57,10 @@ func (r *HorizontalPodAutoscalerV1) Marshal(path string) (string, error) {
return "", err
}
hpa := i.(*autoscalingv1.HorizontalPodAutoscaler)
hpa, ok := i.(*autoscalingv1.HorizontalPodAutoscaler)
if !ok {
return "", errors.New("expecting hpa resource")
}
hpa.TypeMeta.APIVersion = extractVersion(hpa.Annotations)
hpa.TypeMeta.Kind = "HorizontalPodAutoscaler"
@ -104,12 +108,12 @@ func (r *HorizontalPodAutoscalerV1) Fields(ns string) Row {
// Helpers...
func (r *HorizontalPodAutoscalerV1) toMetrics(spec autoscalingv1.HorizontalPodAutoscalerSpec, status autoscalingv1.HorizontalPodAutoscalerStatus) string {
current := "<unknown>"
current := UnknownValue
if status.CurrentCPUUtilizationPercentage != nil {
current = strconv.Itoa(int(*status.CurrentCPUUtilizationPercentage)) + "%"
}
target := "<unknown>"
target := UnknownValue
if spec.TargetCPUUtilizationPercentage != nil {
target = strconv.Itoa(int(*spec.TargetCPUUtilizationPercentage))
}

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strconv"
"strings"
@ -57,7 +58,10 @@ func (r *HorizontalPodAutoscalerV2Beta1) Marshal(path string) (string, error) {
return "", err
}
hpa := i.(*autoscalingv2beta1.HorizontalPodAutoscaler)
hpa, ok := i.(*autoscalingv2beta1.HorizontalPodAutoscaler)
if !ok {
return "", errors.New("expecting hpa resource")
}
hpa.TypeMeta.APIVersion = extractVersion(hpa.Annotations)
hpa.TypeMeta.Kind = "HorizontalPodAutoscaler"
@ -129,7 +133,7 @@ func (r *HorizontalPodAutoscalerV2Beta1) toMetrics(specs []autoscalingv2beta1.Me
}
func (r *HorizontalPodAutoscalerV2Beta1) checkHPAType(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>"
current := UnknownValue
switch spec.Type {
case autoscalingv2beta1.ExternalMetricSourceType:
@ -152,7 +156,7 @@ func (r *HorizontalPodAutoscalerV2Beta1) checkHPAType(i int, spec autoscalingv2b
}
func (*HorizontalPodAutoscalerV2Beta1) externalMetrics(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>"
current := UnknownValue
if spec.External.TargetAverageValue != nil {
if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.CurrentAverageValue != nil {
current = statuses[i].External.CurrentAverageValue.String()
@ -167,7 +171,7 @@ func (*HorizontalPodAutoscalerV2Beta1) externalMetrics(i int, spec autoscalingv2
}
func (*HorizontalPodAutoscalerV2Beta1) resourceMetrics(i int, spec autoscalingv2beta1.MetricSpec, statuses []autoscalingv2beta1.MetricStatus) string {
current := "<unknown>"
current := UnknownValue
if status := checkTargetMetrics(i, spec, statuses); status != "" {
return status

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"regexp"
"strconv"
"strings"
@ -58,7 +59,10 @@ func (r *HorizontalPodAutoscaler) Marshal(path string) (string, error) {
return "", err
}
hpa := i.(*autoscalingv2beta2.HorizontalPodAutoscaler)
hpa, ok := i.(*autoscalingv2beta2.HorizontalPodAutoscaler)
if !ok {
return "", errors.New("expecting hpa resource")
}
hpa.TypeMeta.APIVersion = extractVersion(hpa.Annotations)
hpa.TypeMeta.Kind = "HorizontalPodAutoscaler"
@ -116,6 +120,20 @@ func (r *HorizontalPodAutoscaler) Fields(ns string) Row {
// ----------------------------------------------------------------------------
// Helpers...
func computePodStatus(ss []autoscalingv2beta2.MetricStatus, index int, current string) string {
if len(ss) > index && ss[index].Pods != nil {
return ss[index].Pods.Current.AverageValue.String()
}
return current
}
func computeObjectStatus(ss []autoscalingv2beta2.MetricStatus, index int, current string) string {
if len(ss) > index && ss[index].Object != nil {
return ss[index].Object.Current.Value.String()
}
return current
}
func toMetrics(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string {
if len(specs) == 0 {
return MissingValue
@ -123,21 +141,15 @@ func toMetrics(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2be
list, max, more, count := []string{}, 2, false, 0
for i, spec := range specs {
current := "<unknown>"
current := UnknownValue
switch spec.Type {
case autoscalingv2beta2.ExternalMetricSourceType:
list = append(list, externalMetrics(i, spec, statuses))
case autoscalingv2beta2.PodsMetricSourceType:
if len(statuses) > i && statuses[i].Pods != nil {
current = statuses[i].Pods.Current.AverageValue.String()
}
list = append(list, current+"/"+spec.Pods.Target.AverageValue.String())
list = append(list, computePodStatus(statuses, i, current)+"/"+spec.Pods.Target.AverageValue.String())
case autoscalingv2beta2.ObjectMetricSourceType:
if len(statuses) > i && statuses[i].Object != nil {
current = statuses[i].Object.Current.Value.String()
}
list = append(list, current+"/"+spec.Object.Target.Value.String())
list = append(list, computeObjectStatus(statuses, i, current)+"/"+spec.Object.Target.Value.String())
case autoscalingv2beta2.ResourceMetricSourceType:
list = append(list, resourceMetrics(i, spec, statuses))
default:
@ -159,7 +171,7 @@ func toMetrics(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2be
}
func externalMetrics(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string {
current := "<unknown>"
current := UnknownValue
if spec.External.Target.AverageValue != nil {
if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.Current.AverageValue != nil {
@ -175,7 +187,7 @@ func externalMetrics(i int, spec autoscalingv2beta2.MetricSpec, statuses []autos
}
func resourceMetrics(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string {
current := "<unknown>"
current := UnknownValue
if spec.Resource.Target.AverageValue != nil {
if len(statuses) > i && statuses[i].Resource != nil {

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strings"
"github.com/derailed/k9s/internal/k8s"
@ -57,7 +58,10 @@ func (r *Ingress) Marshal(path string) (string, error) {
return "", err
}
ing := i.(*v1beta1.Ingress)
ing, ok := i.(*v1beta1.Ingress)
if !ok {
return "", errors.New("expecting ing resource")
}
ing.TypeMeta.APIVersion = "extensions/v1beta1"
ing.TypeMeta.Kind = "Ingress"

View File

@ -2,10 +2,10 @@ package resource
import (
"context"
"errors"
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/derailed/k9s/internal/k8s"
@ -20,7 +20,6 @@ type Job struct {
*Base
instance *batchv1.Job
mx sync.RWMutex
}
// NewJobList returns a new resource list.
@ -67,7 +66,10 @@ func (r *Job) Marshal(path string) (string, error) {
return "", err
}
jo := i.(*batchv1.Job)
jo, ok := i.(*batchv1.Job)
if !ok {
return "", errors.New("expecting job resource")
}
jo.TypeMeta.APIVersion = "extensions/v1beta1"
jo.TypeMeta.Kind = "Job"
@ -87,7 +89,10 @@ func (r *Job) Logs(ctx context.Context, c chan<- string, opts LogOptions) error
if err != nil {
return err
}
jo := instance.(*batchv1.Job)
jo, ok := instance.(*batchv1.Job)
if !ok {
return errors.New("expecting job resource")
}
if jo.Spec.Selector == nil || len(jo.Spec.Selector.MatchLabels) == 0 {
return fmt.Errorf("No valid selector found on job %s", opts.FQN())
}

View File

@ -12,7 +12,7 @@ import (
func TestJobToCompletion(t *testing.T) {
t0 := testTime()
t1, t2 := metav1.Time{t0}, metav1.Time{t0.Add(10 * time.Second)}
t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)}
var c, p int32 = 10, 20
uu := []struct {
@ -81,7 +81,7 @@ func TestJobToCompletion(t *testing.T) {
func TestJobToDuration(t *testing.T) {
t0 := testTime().UTC()
t1, t2 := metav1.Time{t0}, metav1.Time{t0.Add(10 * time.Second)}
t1, t2 := metav1.Time{Time: t0}, metav1.Time{Time: t0.Add(10 * time.Second)}
uu := []struct {
s batchv1.JobStatus
@ -96,7 +96,7 @@ func TestJobToDuration(t *testing.T) {
},
{
batchv1.JobStatus{
StartTime: &metav1.Time{time.Now().Add(-10 * time.Second)},
StartTime: &metav1.Time{Time: time.Now().Add(-10 * time.Second)},
},
"10s",
},

View File

@ -254,32 +254,34 @@ func (l *list) load(informer *wa.Informer, ns string) (Columnars, error) {
}
func (l *list) fetchResource(informer *wa.Informer, r interface{}, ns string) (Columnar, error) {
var err error
res := l.resource.New(r)
switch o := r.(type) {
case *v1.Node:
fqn := MetaFQN(o.ObjectMeta)
nmx, err := informer.Get(wa.NodeMXIndex, fqn, metav1.GetOptions{})
if err == nil {
if nmx, err := informer.Get(wa.NodeMXIndex, fqn, metav1.GetOptions{}); err != nil {
return res, err
} else {
res.SetNodeMetrics(nmx.(*mv1beta1.NodeMetrics))
}
case *v1.Pod:
fqn := MetaFQN(o.ObjectMeta)
pmx, err := informer.Get(wa.PodMXIndex, fqn, metav1.GetOptions{})
if err == nil {
if pmx, err := informer.Get(wa.PodMXIndex, fqn, metav1.GetOptions{}); err != nil {
return res, err
} else {
res.SetPodMetrics(pmx.(*mv1beta1.PodMetrics))
}
case v1.Container:
pmx, err := informer.Get(wa.PodMXIndex, ns, metav1.GetOptions{})
if err == nil {
if pmx, err := informer.Get(wa.PodMXIndex, ns, metav1.GetOptions{}); err != nil {
return res, err
} else {
res.SetPodMetrics(pmx.(*mv1beta1.PodMetrics))
}
default:
err = fmt.Errorf("No informer matched %s:%s", l.name, ns)
return res, fmt.Errorf("No informer matched %s:%s", l.name, ns)
}
return res, err
return res, nil
}
// Reconcile previous vs current state and emits delta events.

View File

@ -17,8 +17,8 @@ type (
Fqn
Lines int64
Previous bool
Color color.Paint
Previous bool
SingleContainer bool
MultiPods bool
}

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strings"
"k8s.io/apimachinery/pkg/util/sets"
@ -77,8 +78,14 @@ func (r *Node) List(ns string, opts metav1.ListOptions) (Columnars, error) {
cc := make(Columnars, 0, len(nn))
for i := range nn {
node := nn[i].(v1.Node)
no := r.New(&node).(*Node)
node, ok := nn[i].(v1.Node)
if !ok {
return nil, errors.New("Expecting a node resource")
}
no, ok := r.New(&node).(*Node)
if !ok {
return nil, errors.New("Expecting a node resource")
}
cc = append(cc, no)
}
@ -94,7 +101,10 @@ func (r *Node) Marshal(path string) (string, error) {
return "", err
}
no := i.(*v1.Node)
no, ok := i.(*v1.Node)
if !ok {
return "", errors.New("Expecting a node resource")
}
no.TypeMeta.APIVersion = "v1"
no.TypeMeta.Kind = "Node"
@ -150,8 +160,8 @@ func (r *Node) Fields(ns string) Row {
return append(ff,
no.Name,
join(sta, ","),
join(ro.List(), ","),
join(sta),
join(ro.List()),
no.Status.NodeInfo.KubeletVersion,
no.Status.NodeInfo.KernelVersion,
iIP,
@ -205,7 +215,7 @@ func gatherNodeMX(no *v1.Node, mx *mv1beta1.NodeMetrics) (c metric, a metric, p
return
}
func (_ *Node) findNodeRoles(no *v1.Node, roles *sets.String) []string {
func (_ *Node) findNodeRoles(no *v1.Node, roles *sets.String) {
for k, v := range no.Labels {
switch {
case strings.HasPrefix(k, labelNodeRolePrefix):
@ -220,8 +230,6 @@ func (_ *Node) findNodeRoles(no *v1.Node, roles *sets.String) []string {
if roles.Len() == 0 {
roles.Insert(MissingValue)
}
return roles.List()
}
func (*Node) getIPs(addrs []v1.NodeAddress) (iIP, eIP string) {
@ -268,71 +276,72 @@ func (*Node) status(status v1.NodeStatus, exempt bool, res []string) {
}
}
func (r *Node) podsResources(name string) (v1.ResourceList, v1.ResourceList, error) {
reqs, limits := v1.ResourceList{}, v1.ResourceList{}
pods, err := r.Connection.NodePods(name)
if err != nil {
return reqs, limits, err
}
for _, p := range pods.Items {
preq, plim := podResources(&p)
for k, v := range preq {
if value, ok := reqs[k]; !ok {
reqs[k] = v.DeepCopy()
} else {
value.Add(v)
reqs[k] = value
}
}
for k, v := range plim {
if value, ok := limits[k]; !ok {
limits[k] = v.DeepCopy()
} else {
value.Add(v)
limits[k] = value
}
}
}
// BOZO!!
// func (r *Node) podsResources(name string) (v1.ResourceList, v1.ResourceList, error) {
// reqs, limits := v1.ResourceList{}, v1.ResourceList{}
// pods, err := r.Connection.NodePods(name)
// if err != nil {
// return reqs, limits, err
// }
// for _, p := range pods.Items {
// preq, plim := podResources(&p)
// for k, v := range preq {
// if value, ok := reqs[k]; !ok {
// reqs[k] = v.DeepCopy()
// } else {
// value.Add(v)
// reqs[k] = value
// }
// }
// for k, v := range plim {
// if value, ok := limits[k]; !ok {
// limits[k] = v.DeepCopy()
// } else {
// value.Add(v)
// limits[k] = value
// }
// }
// }
return reqs, limits, nil
}
// return reqs, limits, nil
// }
func podResources(pod *v1.Pod) (v1.ResourceList, v1.ResourceList) {
reqs, limits := v1.ResourceList{}, v1.ResourceList{}
for _, container := range pod.Spec.Containers {
addResources(reqs, container.Resources.Requests)
addResources(limits, container.Resources.Limits)
}
// init containers define the minimum of any resource
for _, container := range pod.Spec.InitContainers {
maxResources(reqs, container.Resources.Requests)
maxResources(limits, container.Resources.Limits)
}
// func podResources(pod *v1.Pod) (v1.ResourceList, v1.ResourceList) {
// reqs, limits := v1.ResourceList{}, v1.ResourceList{}
// for _, container := range pod.Spec.Containers {
// addResources(reqs, container.Resources.Requests)
// addResources(limits, container.Resources.Limits)
// }
// // init containers define the minimum of any resource
// for _, container := range pod.Spec.InitContainers {
// maxResources(reqs, container.Resources.Requests)
// maxResources(limits, container.Resources.Limits)
// }
return reqs, limits
}
// return reqs, limits
// }
// AddResources adds the resources from l2 to l1.
func addResources(l1, l2 v1.ResourceList) {
for name, quantity := range l2 {
if value, ok := l1[name]; ok {
value.Add(quantity)
l1[name] = value
} else {
l1[name] = quantity.DeepCopy()
}
}
}
// // AddResources adds the resources from l2 to l1.
// func addResources(l1, l2 v1.ResourceList) {
// for name, quantity := range l2 {
// if value, ok := l1[name]; ok {
// value.Add(quantity)
// l1[name] = value
// } else {
// l1[name] = quantity.DeepCopy()
// }
// }
// }
// MaxResourceList sets list to the greater of l1/l2 for every resource.
func maxResources(l1, l2 v1.ResourceList) {
for name, quantity := range l2 {
if value, ok := l1[name]; ok {
if quantity.Cmp(value) > 0 {
l1[name] = quantity.DeepCopy()
}
} else {
l1[name] = quantity.DeepCopy()
}
}
}
// // MaxResourceList sets list to the greater of l1/l2 for every resource.
// func maxResources(l1, l2 v1.ResourceList) {
// for name, quantity := range l2 {
// if value, ok := l1[name]; ok {
// if quantity.Cmp(value) > 0 {
// l1[name] = quantity.DeepCopy()
// }
// } else {
// l1[name] = quantity.DeepCopy()
// }
// }
// }

View File

@ -32,7 +32,7 @@ func TestNodeStatus(t *testing.T) {
for _, u := range uu {
res := make([]string, 5)
no.status(u.s, false, res)
assert.Equal(t, "Ready", join(res, ","))
assert.Equal(t, "Ready", join(res))
}
}

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"fmt"
"strings"
@ -58,7 +59,10 @@ func (r *NetworkPolicy) Marshal(path string) (string, error) {
return "", err
}
ds := i.(*networkingv1.NetworkPolicy)
ds, ok := i.(*networkingv1.NetworkPolicy)
if !ok {
return "", errors.New("Expecting a np resource")
}
ds.TypeMeta.APIVersion = "networking.k8s.io/v1"
ds.TypeMeta.Kind = "NetworkPolicy"

View File

@ -1,6 +1,8 @@
package resource
import (
"errors"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
@ -55,7 +57,10 @@ func (r *Namespace) Marshal(path string) (string, error) {
return "", err
}
nss := i.(*v1.Namespace)
nss, ok := i.(*v1.Namespace)
if !ok {
return "", errors.New("Expecting a ns resource")
}
nss.TypeMeta.APIVersion = "v1"
nss.TypeMeta.Kind = "Namespace"

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strconv"
"github.com/derailed/k9s/internal/k8s"
@ -44,7 +45,10 @@ func (r *PodDisruptionBudget) New(i interface{}) Columnar {
c.instance = &instance
case *interface{}:
ptr := *i.(*interface{})
pdbi := ptr.(v1beta1.PodDisruptionBudget)
pdbi, ok := ptr.(v1beta1.PodDisruptionBudget)
if !ok {
log.Fatal().Msg("Expecting a pdb resource")
}
c.instance = &pdbi
default:
log.Fatal().Msgf("unknown PDB type %#v", i)
@ -62,7 +66,10 @@ func (r *PodDisruptionBudget) Marshal(path string) (string, error) {
return "", err
}
pdb := i.(*v1beta1.PodDisruptionBudget)
pdb, ok := i.(*v1beta1.PodDisruptionBudget)
if !ok {
return "", errors.New("Expecting a pdb resource")
}
pdb.TypeMeta.APIVersion = "v1beta1"
pdb.TypeMeta.Kind = "PodDisruptionBudget"

View File

@ -3,6 +3,7 @@ package resource
import (
"bufio"
"context"
"errors"
"fmt"
"io"
"strconv"
@ -21,6 +22,10 @@ import (
const (
defaultTimeout = 1 * time.Second
Terminating = "Terminating"
Running = "Running"
Initialized = "Initialized"
Completed = "Completed"
)
type (
@ -81,7 +86,10 @@ func (r *Pod) New(i interface{}) Columnar {
c.instance = &instance
case *interface{}:
ptr := *instance
po := ptr.(v1.Pod)
po, ok := ptr.(v1.Pod)
if !ok {
log.Fatal().Msgf("Expecting a pod resource")
}
c.instance = &po
default:
log.Fatal().Msgf("unknown Pod type %#v", i)
@ -103,7 +111,10 @@ func (r *Pod) Marshal(path string) (string, error) {
if err != nil {
return "", err
}
po := i.(*v1.Pod)
po, ok := i.(*v1.Pod)
if !ok {
return "", errors.New("Expecting a pod resource")
}
po.TypeMeta.APIVersion = "v1"
po.TypeMeta.Kind = "Pod"
@ -119,13 +130,19 @@ func (r *Pod) Containers(path string, includeInit bool) ([]string, error) {
// PodLogs tail logs for all containers in a running Pod.
func (r *Pod) PodLogs(ctx context.Context, c chan<- string, opts LogOptions) error {
i := ctx.Value(IKey("informer")).(*watch.Informer)
p, err := i.Get(watch.PodIndex, opts.FQN(), metav1.GetOptions{})
inf, ok := ctx.Value(IKey("informer")).(*watch.Informer)
if !ok {
return errors.New("Expecting an informer")
}
p, err := inf.Get(watch.PodIndex, opts.FQN(), metav1.GetOptions{})
if err != nil {
return err
}
po := p.(*v1.Pod)
po, ok := p.(*v1.Pod)
if !ok {
return errors.New("Expecting a pod resource")
}
opts.Color = asColor(po.Name)
if len(po.Spec.InitContainers)+len(po.Spec.Containers) == 1 {
opts.SingleContainer = true
@ -192,19 +209,19 @@ func tailLogs(ctx context.Context, res k8s.Loggable, c chan<- string, opts LogOp
}
func logsTimeout(cancel context.CancelFunc, blocked *int32) {
select {
case <-time.After(defaultTimeout):
if atomic.LoadInt32(blocked) == 1 {
log.Debug().Msg("Timed out reading the log stream")
cancel()
}
<-time.After(defaultTimeout)
if atomic.LoadInt32(blocked) == 1 {
log.Debug().Msg("Timed out reading the log stream")
cancel()
}
}
func readLogs(ctx context.Context, stream io.ReadCloser, c chan<- string, opts LogOptions) {
defer func() {
log.Debug().Msgf(">>> Closing stream `%s", opts.Path())
stream.Close()
if err := stream.Close(); err != nil {
log.Error().Err(err).Msg("Cloing stream")
}
}()
scanner := bufio.NewScanner(stream)
@ -227,7 +244,10 @@ func (r *Pod) List(ns string, opts metav1.ListOptions) (Columnars, error) {
cc := make(Columnars, 0, len(pods))
for i := range pods {
po := r.New(&pods[i]).(*Pod)
po, ok := r.New(&pods[i]).(*Pod)
if !ok {
return nil, errors.New("Expecting a pod resource")
}
cc = append(cc, po)
}
@ -379,10 +399,6 @@ func (r *Pod) statuses(ss []v1.ContainerStatus) (cr, ct, rc int) {
return
}
func isSet(s *string) bool {
return s != nil && *s != ""
}
func (r *Pod) phase(po *v1.Pod) string {
status := string(po.Status.Phase)
if po.Status.Reason != "" {
@ -405,7 +421,7 @@ func (r *Pod) phase(po *v1.Pod) string {
return status
}
return "Terminating"
return Terminating
}
func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) {
@ -433,11 +449,11 @@ func (*Pod) containerPhase(st v1.PodStatus, status string) (bool, string) {
func (*Pod) initContainerPhase(st v1.PodStatus, initCount int, status string) (bool, string) {
for i, cs := range st.InitContainerStatuses {
status := checkContainerStatus(cs, i, initCount)
if status == "" {
if state := checkContainerStatus(cs, i, initCount); state == "" {
continue
} else {
return true, state
}
return true, status
}
return false, status

View File

@ -55,22 +55,22 @@ func TestPodPhase(t *testing.T) {
e string
}{
{makePodStatus("p1", v1.PodRunning, ""), "Running"},
{makePodStatus("p1", v1.PodRunning, "Evicted"), "Evicted"},
{makePodStatus("p2", v1.PodRunning, "Evicted"), "Evicted"},
{makePodStatus("p1", v1.PodPending, ""), "Pending"},
{makePodStatus("p1", v1.PodSucceeded, ""), "Succeeded"},
{makePodStatus("p1", v1.PodFailed, ""), "Failed"},
{makePodStatus("p1", v1.PodUnknown, ""), "Unknown"},
{makePodCoInitTerminated("p1"), "Init:OOMKilled"},
{makePodCoInitWaiting("p1", ""), "Init:0/1"},
{makePodCoInitWaiting("p1", "Waiting"), "Init:Waiting"},
{makePodCoInitWaiting("p2", "Waiting"), "Init:Waiting"},
{makePodCoInitWaiting("p1", "PodInitializing"), "Init:0/1"},
{makePodCoWaiting("p1", "Waiting"), "Waiting"},
{makePodCoWaiting("p1", ""), ""},
{makePodCoTerminated("p1", "OOMKilled", 0, true), "Terminating"},
{makePodCoTerminated("p1", "OOMKilled", 0, false), "OOMKilled"},
{makePodCoTerminated("p1", "", 0, true), "Terminating"},
{makePodCoTerminated("p1", "OOMKilled", 0, true), Terminating},
{makePodCoTerminated("p2", "OOMKilled", 0, false), "OOMKilled"},
{makePodCoTerminated("p1", "", 0, true), Terminating},
{makePodCoTerminated("p1", "", 0, false), "ExitCode:1"},
{makePodCoTerminated("p1", "", 1, true), "Terminating"},
{makePodCoTerminated("p1", "", 1, true), Terminating},
{makePodCoTerminated("p1", "", 1, false), "Signal:1"},
}
@ -127,7 +127,7 @@ func makePodCoTerminated(n, reason string, signal int32, deleted bool) *v1.Pod {
po := makePod(n)
if deleted {
po.DeletionTimestamp = &metav1.Time{time.Now()}
po.DeletionTimestamp = &metav1.Time{Time: time.Now()}
}
po.Status.ContainerStatuses = []v1.ContainerStatus{
{

View File

@ -55,7 +55,7 @@ func TestPodGatherMX(t *testing.T) {
v1.ResourceRequirements{
Requests: makeRes("500m", "512Mi"),
},
makeMxPod("fred", "250m", "256Mi"),
makeMxPod("p1", "250m", "256Mi"),
"150",
"150",
},
@ -63,7 +63,7 @@ func TestPodGatherMX(t *testing.T) {
v1.ResourceRequirements{
Limits: makeRes("1000m", "1024Mi"),
},
makeMxPod("fred", "250m", "256Mi"),
makeMxPod("p2", "250m", "256Mi"),
"75",
"75",
},
@ -72,13 +72,14 @@ func TestPodGatherMX(t *testing.T) {
Requests: makeRes("500m", "512Mi"),
Limits: makeRes("1000m", "1024Mi"),
},
makeMxPod("fred", "250m", "256Mi"),
makeMxPod("p3", "250m", "256Mi"),
"150",
"150",
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
r := NewPodWithMetrics(u.metrics, u.resources).Fields("blee")
@ -109,7 +110,7 @@ func TestPodListData(t *testing.T) {
mx := NewMockMetricsServer()
m.When(mx.HasMetrics()).ThenReturn(true)
m.When(mx.FetchPodsMetrics("blee")).
ThenReturn(&mv1beta1.PodMetricsList{Items: []mv1beta1.PodMetrics{makeMxPod("fred", "100m", "20Mi")}}, nil)
ThenReturn(&mv1beta1.PodMetricsList{Items: []mv1beta1.PodMetrics{makeMxPod("p1", "100m", "20Mi")}}, nil)
l := NewPodListWithArgs("blee", NewPodWithArgs(mc, mr, mx))
// Make sure we mcn get deltas!

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"path"
"strings"
@ -57,7 +58,10 @@ func (r *PersistentVolume) Marshal(path string) (string, error) {
return "", err
}
pv := i.(*v1.PersistentVolume)
pv, ok := i.(*v1.PersistentVolume)
if !ok {
return "", errors.New("Expecting a pv resource")
}
pv.TypeMeta.APIVersion = "v1"
pv.TypeMeta.Kind = "PersistentVolume"
@ -84,7 +88,7 @@ func (r *PersistentVolume) Fields(ns string) Row {
phase := i.Status.Phase
if i.ObjectMeta.DeletionTimestamp != nil {
phase = "Terminating"
phase = Terminating
}
var claim string

View File

@ -1,6 +1,8 @@
package resource
import (
"errors"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/core/v1"
@ -54,7 +56,10 @@ func (r *PersistentVolumeClaim) Marshal(path string) (string, error) {
return "", err
}
pvc := i.(*v1.PersistentVolumeClaim)
pvc, ok := i.(*v1.PersistentVolumeClaim)
if !ok {
return "", errors.New("Expecting a pvc resource")
}
pvc.TypeMeta.APIVersion = "v1"
pvc.TypeMeta.Kind = "PersistentVolumeClaim"
@ -81,7 +86,7 @@ func (r *PersistentVolumeClaim) Fields(ns string) Row {
phase := i.Status.Phase
if i.ObjectMeta.DeletionTimestamp != nil {
phase = "Terminating"
phase = Terminating
}
var pv PersistentVolume

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strconv"
"github.com/derailed/k9s/internal/k8s"
@ -56,7 +57,10 @@ func (r *ReplicationController) Marshal(path string) (string, error) {
return "", err
}
rc := i.(*v1.ReplicationController)
rc, ok := i.(*v1.ReplicationController)
if !ok {
return "", errors.New("Expecting a rc resource")
}
rc.TypeMeta.APIVersion = "v1"
rc.TypeMeta.Kind = "ReplicationController"

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strings"
"github.com/derailed/k9s/internal/k8s"
@ -56,7 +57,10 @@ func (r *Role) Marshal(path string) (string, error) {
return "", err
}
role := i.(*v1.Role)
role, ok := i.(*v1.Role)
if !ok {
return "", errors.New("Expecting a role resource")
}
role.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1"
role.TypeMeta.Kind = "Role"

View File

@ -1,6 +1,8 @@
package resource
import (
"errors"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/rbac/v1"
@ -54,7 +56,10 @@ func (r *RoleBinding) Marshal(path string) (string, error) {
return "", err
}
rb := i.(*v1.RoleBinding)
rb, ok := i.(*v1.RoleBinding)
if !ok {
return "", errors.New("Expecting a rb resource")
}
rb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1"
rb.TypeMeta.Kind = "RoleBinding"

View File

@ -81,11 +81,6 @@ func k8sRB() *v1.RoleBinding {
}
}
func newRB() resource.Columnar {
mc := NewMockConnection()
return resource.NewRoleBinding(mc).New(k8sRB())
}
func rbYaml() string {
return `apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding

View File

@ -69,11 +69,6 @@ func k8sRole() *v1.Role {
}
}
func newRole() resource.Columnar {
mc := NewMockConnection()
return resource.NewRole(mc).New(k8sRole())
}
func roleYaml() string {
return `apiVersion: rbac.authorization.k8s.io/v1
kind: Role

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strconv"
"github.com/derailed/k9s/internal/k8s"
@ -56,7 +57,10 @@ func (r *ReplicaSet) Marshal(path string) (string, error) {
return "", err
}
rs := i.(*v1.ReplicaSet)
rs, ok := i.(*v1.ReplicaSet)
if !ok {
return "", errors.New("Expecting a rs resource")
}
rs.TypeMeta.APIVersion = "apps/v1"
rs.TypeMeta.Kind = "ReplicaSet"

View File

@ -77,11 +77,6 @@ func k8sReplicaSet() *v1.ReplicaSet {
}
}
func newReplicaSet() resource.Columnar {
mc := NewMockConnection()
return resource.NewReplicaSet(mc).New(k8sReplicaSet())
}
func rsYaml() string {
return `apiVersion: apps/v1
kind: ReplicaSet

View File

@ -1,6 +1,7 @@
package resource
import (
"errors"
"strconv"
"github.com/derailed/k9s/internal/k8s"
@ -56,7 +57,10 @@ func (r *ServiceAccount) Marshal(path string) (string, error) {
return "", err
}
sa := i.(*v1.ServiceAccount)
sa, ok := i.(*v1.ServiceAccount)
if !ok {
return "", errors.New("Expecting a sa resource")
}
sa.TypeMeta.APIVersion = "v1"
sa.TypeMeta.Kind = "ServiceAccount"

View File

@ -101,7 +101,7 @@ func k8sSA() *v1.ServiceAccount {
ObjectMeta: metav1.ObjectMeta{
Name: "fred",
Namespace: "blee",
CreationTimestamp: metav1.Time{testTime()},
CreationTimestamp: metav1.Time{Time: testTime()},
},
Secrets: []v1.ObjectReference{{Name: "blee"}},
}

View File

@ -1,6 +1,8 @@
package resource
import (
"errors"
"github.com/derailed/k9s/internal/k8s"
"github.com/rs/zerolog/log"
v1 "k8s.io/api/storage/v1"
@ -54,7 +56,10 @@ func (r *StorageClass) Marshal(path string) (string, error) {
return "", err
}
sc := i.(*v1.StorageClass)
sc, ok := i.(*v1.StorageClass)
if !ok {
return "", errors.New("Expecting a sc resource")
}
sc.TypeMeta.APIVersion = "storage.k8s.io/v1"
sc.TypeMeta.Kind = "StorageClass"

View File

@ -2,6 +2,7 @@ package resource
import (
"context"
"errors"
"fmt"
"strconv"
@ -62,7 +63,10 @@ func (r *StatefulSet) Marshal(path string) (string, error) {
return "", err
}
sts := i.(*appsv1.StatefulSet)
sts, ok := i.(*appsv1.StatefulSet)
if !ok {
return "", errors.New("Expecting an sts resource")
}
sts.TypeMeta.APIVersion = "apps/v1"
sts.TypeMeta.Kind = "StatefulSet"
@ -76,7 +80,10 @@ func (r *StatefulSet) Logs(ctx context.Context, c chan<- string, opts LogOptions
return err
}
sts := instance.(*appsv1.StatefulSet)
sts, ok := instance.(*appsv1.StatefulSet)
if !ok {
return errors.New("Expecting an sts resource")
}
if sts.Spec.Selector == nil || len(sts.Spec.Selector.MatchLabels) == 0 {
return fmt.Errorf("No valid selector found on statefulset %s", opts.FQN())
}

View File

@ -102,7 +102,7 @@ func k8sSTS() *v1.StatefulSet {
ObjectMeta: metav1.ObjectMeta{
Name: "fred",
Namespace: "blee",
CreationTimestamp: metav1.Time{testTime()},
CreationTimestamp: metav1.Time{Time: testTime()},
},
Spec: v1.StatefulSetSpec{
Replicas: new(int32),

View File

@ -12,8 +12,6 @@ import (
v1 "k8s.io/api/core/v1"
)
const lbIPWidth = 16
// Service tracks a kubernetes resource.
type Service struct {
*Base
@ -63,7 +61,10 @@ func (r *Service) Marshal(path string) (string, error) {
return "", err
}
svc := i.(*v1.Service)
svc, ok := i.(*v1.Service)
if !ok {
return "", errors.New("Expecting a service resource")
}
svc.TypeMeta.APIVersion = "v1"
svc.TypeMeta.Kind = "Service"
@ -77,7 +78,10 @@ func (r *Service) Logs(ctx context.Context, c chan<- string, opts LogOptions) er
return err
}
svc := instance.(*v1.Service)
svc, ok := instance.(*v1.Service)
if !ok {
return errors.New("Expecting a service resource")
}
log.Debug().Msgf("Service %s--%s", svc.Name, svc.Spec.Selector)
if len(svc.Spec.Selector) == 0 {
return errors.New("No logs for headless service")

View File

@ -22,8 +22,8 @@ func TestSvcExtIPs(t *testing.T) {
func TestLbIngressIP(t *testing.T) {
lb := v1.LoadBalancerStatus{
Ingress: []v1.LoadBalancerIngress{
{"10.0.0.0", "fred"},
{"10.0.0.1", "blee"},
{IP: "10.0.0.0", Hostname: "fred"},
{IP: "10.0.0.1", Hostname: "blee"},
},
}
@ -89,7 +89,7 @@ func k8sSVCLb() *v1.Service {
ObjectMeta: metav1.ObjectMeta{
Name: "fred",
Namespace: "blee",
CreationTimestamp: metav1.Time{testTime()},
CreationTimestamp: metav1.Time{Time: testTime()},
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeLoadBalancer,

View File

@ -113,7 +113,7 @@ func k8sSVC() *v1.Service {
ObjectMeta: metav1.ObjectMeta{
Name: "fred",
Namespace: "blee",
CreationTimestamp: metav1.Time{testTime()},
CreationTimestamp: metav1.Time{Time: testTime()},
},
Spec: v1.ServiceSpec{
Type: v1.ServiceTypeClusterIP,

View File

@ -18,5 +18,5 @@ func TestKeyActionsHints(t *testing.T) {
hh := kk.Hints()
assert.Equal(t, 3, len(hh))
assert.Equal(t, model.MenuHint{"b", "blee", true}, hh[0])
assert.Equal(t, model.MenuHint{Mnemonic: "b", Description: "blee", Visible: true}, hh[0])
}

View File

@ -59,7 +59,9 @@ func TestAppViews(t *testing.T) {
a := ui.NewApp()
a.Init()
for _, v := range []string{"crumbs", "logo", "cmd", "flash", "menu"} {
vv := []string{"crumbs", "logo", "cmd", "flash", "menu"}
for i := range vv {
v := vv[i]
t.Run(v, func(t *testing.T) {
assert.NotNil(t, a.Views()[v])
})

View File

@ -49,10 +49,6 @@ func (v *CmdView) update(s string) {
v.write(s)
}
func (v *CmdView) append(r rune) {
fmt.Fprintf(v, "%s", string(r))
}
func (v *CmdView) write(s string) {
fmt.Fprintf(v, defaultPrompt, v.icon, s)
}

View File

@ -25,11 +25,11 @@ type (
// CmdBuff represents user command input.
CmdBuff struct {
buff []rune
listeners []BuffWatcher
hotKey rune
kind BufferKind
sticky bool
hotKey rune
active bool
listeners []BuffWatcher
}
)
@ -90,10 +90,6 @@ func (c *CmdBuff) Delete() {
c.fireChanged()
}
func (c *CmdBuff) wipe() {
c.buff = make([]rune, 0, maxBuff)
}
// Clear clears out command buffer.
func (c *CmdBuff) Clear() {
c.buff = make([]rune, 0, maxBuff)

View File

@ -21,7 +21,8 @@ func TestDefaultColorer(t *testing.T) {
"upd": {resource.RowEvent{Action: watch.Modified}, ui.ModColor},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, ui.DefaultColorer("", &u.re))
})

View File

@ -43,7 +43,9 @@ func (c *Configurator) StylesUpdater(ctx context.Context, s synchronizer) error
log.Info().Err(err).Msg("Skin watcher failed")
return
case <-ctx.Done():
w.Close()
if err := w.Close(); err != nil {
log.Error().Err(err).Msg("Closing watcher")
}
return
}
}

View File

@ -21,6 +21,77 @@ const (
var percent = regexp.MustCompile(`\A(\d+)\%\z`)
func deltaNumb(o, n string) (string, bool) {
var delta string
i, ok := numerical(o)
if !ok {
return delta, ok
}
j, _ := numerical(n)
switch {
case i < j:
delta = PlusSign
case i > j:
delta = MinusSign
}
return delta, ok
}
func deltaPerc(o, n string) (string, bool) {
var delta string
i, ok := percentage(o)
if !ok {
return delta, ok
}
j, _ := percentage(n)
switch {
case i < j:
delta = PlusSign
case i > j:
delta = MinusSign
}
return delta, ok
}
func deltaQty(o, n string) (string, bool) {
var delta string
q1, err := resource.ParseQuantity(o)
if err != nil {
return delta, false
}
q2, _ := resource.ParseQuantity(n)
switch q1.Cmp(q2) {
case -1:
delta = PlusSign
case 1:
delta = MinusSign
}
return delta, true
}
func deltaDur(o, n string) (string, bool) {
var delta string
d1, err := time.ParseDuration(o)
if err != nil {
return delta, false
}
d2, _ := time.ParseDuration(n)
switch {
case d2-d1 > 0:
delta = PlusSign
case d2-d1 < 0:
delta = MinusSign
}
return delta, true
}
// Deltas signals diffs between 2 strings.
func Deltas(o, n string) string {
o, n = strings.TrimSpace(o), strings.TrimSpace(n)
@ -28,52 +99,20 @@ func Deltas(o, n string) string {
return ""
}
if i, ok := numerical(o); ok {
j, _ := numerical(n)
switch {
case i < j:
return PlusSign
case i > j:
return MinusSign
default:
return ""
}
if d, ok := deltaNumb(o, n); ok {
return d
}
if i, ok := percentage(o); ok {
j, _ := percentage(n)
switch {
case i < j:
return PlusSign
case i > j:
return MinusSign
default:
return ""
}
if d, ok := deltaPerc(o, n); ok {
return d
}
if q1, err := resource.ParseQuantity(o); err == nil {
q2, _ := resource.ParseQuantity(n)
switch q1.Cmp(q2) {
case -1:
return PlusSign
case 1:
return MinusSign
default:
return ""
}
if d, ok := deltaQty(o, n); ok {
return d
}
if d1, err := time.ParseDuration(o); err == nil {
d2, _ := time.ParseDuration(n)
switch {
case d2-d1 > 0:
return PlusSign
case d2-d1 < 0:
return MinusSign
default:
return ""
}
if d, ok := deltaDur(o, n); ok {
return d
}
switch strings.Compare(o, n) {

View File

@ -37,7 +37,8 @@ func TestStripPort(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, stripPort(u.port))
})

View File

@ -40,7 +40,8 @@ func TestLogoStatus(t *testing.T) {
defaults, _ := config.NewStyles("")
v := NewLogoView(defaults)
for k, u := range uu {
for n := range uu {
k, u := n, uu[n]
t.Run(k, func(t *testing.T) {
switch k {
case "info":

View File

@ -83,16 +83,13 @@ func (v *Menu) hasDigits(hh model.MenuHints) bool {
}
func (v *Menu) buildMenuTable(hh model.MenuHints) [][]string {
table := make([]model.MenuHints, maxRows+1)
table := make([][]string, maxRows+1)
colCount := (len(hh) / maxRows) + 1
if v.hasDigits(hh) {
colCount++
}
for row := 0; row < maxRows; row++ {
table[row] = make(model.MenuHints, colCount)
table[row] = make([]string, colCount)
}
var row, col int
@ -102,35 +99,24 @@ func (v *Menu) buildMenuTable(hh model.MenuHints) [][]string {
if !h.Visible {
continue
}
isDigit := menuRX.MatchString(h.Mnemonic)
if !isDigit && firstCmd {
if !menuRX.MatchString(h.Mnemonic) && firstCmd {
row, col, firstCmd = 0, col+1, false
if table[0][0].IsBlank() {
if table[0][0] == "" {
col = 0
}
}
if maxKeys[col] < len(h.Mnemonic) {
maxKeys[col] = len(h.Mnemonic)
}
table[row][col] = h
table[row][col] = keyConv(v.formatMenu(h, maxKeys[col]))
row++
if row >= maxRows {
col++
row = 0
row, col = 0, col+1
}
}
strTable := make([][]string, maxRows+1)
for r := 0; r < len(table); r++ {
strTable[r] = make([]string, len(table[r]))
}
for row := range strTable {
for col := range strTable[row] {
strTable[row][col] = keyConv(v.formatMenu(table[row][col], maxKeys[col]))
}
}
return strTable
return table
}
// ----------------------------------------------------------------------------

View File

@ -45,7 +45,8 @@ func TestActionHints(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, u.aa.Hints())
})

View File

@ -114,7 +114,8 @@ func TestIsDurationSort(t *testing.T) {
"ascGreater": {"10h10m", "2h5m", true, false},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
less, ok := isDurationSort(u.asc, u.s1, u.s2)
assert.True(t, ok)

View File

@ -59,8 +59,16 @@ func NewTable(title string) *Table {
}
}
func mustExtractSyles(ctx context.Context) *config.Styles {
styles, ok := ctx.Value(KeyStyles).(*config.Styles)
if !ok {
log.Fatal().Msg("Expecting valid styles")
}
return styles
}
func (t *Table) Init(ctx context.Context) {
t.styles = ctx.Value(KeyStyles).(*config.Styles)
t.styles = mustExtractSyles(ctx)
t.SetFixed(1, 0)
t.SetBorder(true)
@ -350,8 +358,8 @@ func (t *Table) buildRow(row int, data resource.TableData, sk string, pads MaxyP
m := t.isMarked(sk)
for col, field := range data.Rows[sk].Fields {
header := data.Header[col]
field, align := t.formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col])
c := tview.NewTableCell(field)
cell, align := t.formatCell(data.NumCols[header], header, field+Deltas(data.Rows[sk].Deltas[col], field), pads[col])
c := tview.NewTableCell(cell)
{
c.SetExpansion(1)
c.SetAlign(align)
@ -418,10 +426,10 @@ func (t *Table) filtered() resource.TableData {
return t.fuzzyFilter(q[2:])
}
return t.rxFilter(q)
return t.rxFilter()
}
func (t *Table) rxFilter(q string) resource.TableData {
func (t *Table) rxFilter() resource.TableData {
rx, err := regexp.Compile(`(?i)` + t.cmdBuff.String())
if err != nil {
log.Error().Err(errors.New("Invalid filter expression")).Msg("Regexp")

View File

@ -18,7 +18,8 @@ func TestIsLabelSelector(t *testing.T) {
"wrongLabel": {"-f app=fred,env=blee", false},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, IsLabelSelector(u.sel))
})
@ -33,7 +34,8 @@ func TestTrimLabelSelector(t *testing.T) {
"noSpace": {"-lapp=fred,env=blee", "app=fred,env=blee"},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, TrimLabelSelector(u.sel))
})

View File

@ -64,10 +64,6 @@ func (a *Alias) registerActions() {
})
}
func (a *Alias) getTitle() string {
return aliasTitle
}
func (a *Alias) resetCmd(evt *tcell.EventKey) *tcell.EventKey {
if !a.SearchBuff().Empty() {
a.SearchBuff().Reset()
@ -83,7 +79,9 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
s := ui.TrimCell(a.Table.Table, r, 1)
tokens := strings.Split(s, ",")
a.app.Content.Pop()
a.app.gotoResource(tokens[0], true)
if !a.app.gotoResource(tokens[0]) {
a.app.Flash().Err(fmt.Errorf("Goto %s failed", tokens[0]))
}
return nil
}
@ -94,7 +92,7 @@ func (a *Alias) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
return evt
}
func (a *Alias) backCmd(evt *tcell.EventKey) *tcell.EventKey {
func (a *Alias) backCmd(_ *tcell.EventKey) *tcell.EventKey {
if a.SearchBuff().IsActive() {
a.SearchBuff().Reset()
} else {

View File

@ -17,7 +17,7 @@ func TestAliasNew(t *testing.T) {
v.Init(makeContext())
assert.Equal(t, 3, v.GetColumnCount())
assert.Equal(t, 16, v.GetRowCount())
assert.Equal(t, 15, v.GetRowCount())
assert.Equal(t, "Aliases", v.Name())
assert.Equal(t, 10, len(v.Hints()))
}

View File

@ -2,6 +2,7 @@ package view
import (
"context"
"errors"
"fmt"
"time"
@ -152,7 +153,10 @@ func (a *App) BufferActive(state bool, _ ui.BufferKind) {
func (a *App) toggleHeader(flag bool) {
a.showHeader = flag
flex := a.Main.GetPrimitive("main").(*tview.Flex)
flex, ok := a.Main.GetPrimitive("main").(*tview.Flex)
if !ok {
log.Fatal().Msg("Expecting valid flex view")
}
if a.showHeader {
flex.RemoveItemAtIndex(0)
flex.AddItemAtIndex(0, a.buildHeader(), 7, 1, false)
@ -262,8 +266,8 @@ func (a *App) switchCtx(ctx string, load bool) error {
log.Error().Err(err).Msg("Config save failed!")
}
a.Flash().Infof("Switching context to %s", ctx)
if load {
a.gotoResource("po", true)
if load && !a.gotoResource("po") {
a.Flash().Err(errors.New("Goto pod failed"))
}
return nil
@ -396,7 +400,9 @@ func (a *App) toggleHeaderCmd(evt *tcell.EventKey) *tcell.EventKey {
func (a *App) gotoCmd(evt *tcell.EventKey) *tcell.EventKey {
if a.CmdBuff().IsActive() && !a.CmdBuff().Empty() {
a.Content.Stack.Reset()
a.gotoResource(a.GetCmd(), true)
if !a.gotoResource(a.GetCmd()) {
a.Flash().Errf("Goto %s failed!", a.GetCmd())
}
a.ResetCmd()
return nil
}
@ -423,7 +429,7 @@ func (a *App) aliasCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
func (a *App) gotoResource(res string, record bool) bool {
func (a *App) gotoResource(res string) bool {
return a.command.run(res)
}

View File

@ -106,10 +106,6 @@ func (b *Bench) keyBindings() {
b.masterPage().AddActions(aa)
}
func (b *Bench) getTitle() string {
return benchTitle
}
func (b *Bench) enterCmd(evt *tcell.EventKey) *tcell.EventKey {
if b.masterPage().SearchBuff().IsActive() {
return b.masterPage().filterCmd(evt)
@ -139,7 +135,7 @@ func (b *Bench) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
sel, file := b.masterPage().GetSelectedItem(), b.benchFile()
dir := filepath.Join(perf.K9sBenchDir, b.app.Config.K9s.CurrentCluster)
showModal(b.Pages, fmt.Sprintf("Delete benchmark `%s?", file), "master", func() {
showModal(b.Pages, fmt.Sprintf("Delete benchmark `%s?", file), func() {
if err := os.Remove(filepath.Join(dir, file)); err != nil {
b.app.Flash().Errf("Unable to delete file %s", err)
return
@ -150,11 +146,6 @@ func (b *Bench) deleteCmd(evt *tcell.EventKey) *tcell.EventKey {
return nil
}
func (b *Bench) backCmd(evt *tcell.EventKey) *tcell.EventKey {
b.showMaster()
return nil
}
func (b *Bench) benchFile() string {
r := b.masterPage().GetSelectedRowIndex()
return ui.TrimCell(b.masterPage().Table, r, 7)
@ -221,7 +212,9 @@ func (b *Bench) watchBenchDir(ctx context.Context) error {
return
case <-ctx.Done():
log.Debug().Msg("!!!! FS WATCHER DONE!!")
w.Close()
if err := w.Close(); err != nil {
log.Error().Err(err).Msg("Closing bench watched")
}
return
}
}
@ -273,29 +266,25 @@ func augmentRow(fields resource.Row, data string) {
col++
ms := okRx.FindAllStringSubmatch(data, -1)
fields[col] = "0"
if len(ms) > 0 {
var sum int
for _, m := range ms {
if m, err := strconv.Atoi(string(m[1])); err == nil {
sum += m
}
}
fields[col] = asNum(sum)
}
fields[col] = countReq(ms)
col++
me := errRx.FindAllStringSubmatch(data, -1)
fields[col] = "0"
if len(me) > 0 {
var sum int
for _, m := range me {
if m, err := strconv.Atoi(string(m[1])); err == nil {
sum += m
}
}
fields[col] = asNum(sum)
fields[col] = countReq(me)
}
func countReq(rr [][]string) string {
if len(rr) == 0 {
return "0"
}
var sum int
for _, m := range rr {
if m, err := strconv.Atoi(string(m[1])); err == nil {
sum += m
}
}
return asNum(sum)
}
func benchDir(cfg *config.Config) string {

View File

@ -31,7 +31,8 @@ func TestAugmentRow(t *testing.T) {
},
}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
data, err := ioutil.ReadFile(u.file)

View File

@ -43,7 +43,7 @@ func newClusterInfoView(app *App, mx resource.MetricsServer) *clusterInfoView {
func (v *clusterInfoView) init(version string) {
cluster := resource.NewCluster(v.app.Conn(), &log.Logger, v.mxs)
row := v.initInfo(version, cluster)
row := v.initInfo(cluster)
row = v.initVersion(row, version, cluster)
v.SetCell(row, 0, v.sectionCell("CPU"))
@ -55,7 +55,7 @@ func (v *clusterInfoView) init(version string) {
v.refresh()
}
func (v *clusterInfoView) initInfo(version string, cluster *resource.Cluster) int {
func (v *clusterInfoView) initInfo(cluster *resource.Cluster) int {
var row int
v.SetCell(row, 0, v.sectionCell("Context"))
v.SetCell(row, 1, v.infoCell(cluster.ContextName()))

View File

@ -36,6 +36,18 @@ func rbacColorer(ns string, r *resource.RowEvent) tcell.Color {
return ui.DefaultColorer(ns, r)
}
func checkReadyCol(readyCol, statusCol string, c tcell.Color) tcell.Color {
if statusCol == "Completed" {
return c
}
tokens := strings.Split(readyCol, "/")
if len(tokens) == 2 && (tokens[0] == "0" || tokens[0] != tokens[1]) {
return ui.ErrColor
}
return c
}
func podColorer(ns string, r *resource.RowEvent) tcell.Color {
c := ui.DefaultColorer(ns, r)
@ -45,25 +57,21 @@ func podColorer(ns string, r *resource.RowEvent) tcell.Color {
}
statusCol := readyCol + 1
tokens := strings.Split(strings.TrimSpace(r.Fields[readyCol]), "/")
if len(tokens) == 2 && (tokens[0] == "0" || tokens[0] != tokens[1]) {
if strings.TrimSpace(r.Fields[statusCol]) != "Completed" {
c = ui.ErrColor
}
}
ready, status := strings.TrimSpace(r.Fields[readyCol]), strings.TrimSpace(r.Fields[statusCol])
c = checkReadyCol(ready, status, c)
switch strings.TrimSpace(r.Fields[statusCol]) {
switch status {
case "ContainerCreating", "PodInitializing":
return ui.AddColor
case "Initialized":
case resource.Initialized:
return ui.HighlightColor
case "Completed":
case resource.Completed:
return ui.CompletedColor
case "Running":
case "Terminating":
case resource.Running:
case resource.Terminating:
return ui.KillColor
default:
c = ui.ErrColor
return ui.ErrColor
}
return c
@ -81,11 +89,11 @@ func containerColorer(ns string, r *resource.RowEvent) tcell.Color {
switch strings.TrimSpace(r.Fields[stateCol]) {
case "ContainerCreating", "PodInitializing":
return ui.AddColor
case "Terminating", "Initialized":
case resource.Terminating, resource.Initialized:
return ui.HighlightColor
case "Completed":
case resource.Completed:
return ui.CompletedColor
case "Running":
case resource.Running:
default:
c = ui.ErrColor
}
@ -236,7 +244,7 @@ func nsColorer(ns string, r *resource.RowEvent) tcell.Color {
}
switch strings.TrimSpace(r.Fields[1]) {
case "Inactive", "Terminating":
case "Inactive", resource.Terminating:
c = ui.ErrColor
}

View File

@ -22,7 +22,7 @@ type (
func TestNSColorer(t *testing.T) {
var (
ns = resource.Row{"blee", "Active"}
term = resource.Row{"blee", "Terminating"}
term = resource.Row{"blee", resource.Terminating}
dead = resource.Row{"blee", "Inactive"}
)

View File

@ -99,15 +99,12 @@ func (c *command) run(cmd string) bool {
}
switch cmds[0] {
case "ctx", "context", "contexts":
if len(cmds) == 2 {
if err := c.app.switchCtx(cmds[1], true); err != nil {
log.Error().Err(err).Msg("Context switch failed!")
return false
}
return true
if len(cmds) == 2 && c.app.switchCtx(cmds[1], true) != nil {
log.Error().Msg("Context switch failed!")
return false
}
view := c.componentFor(gvr, v)
return c.exec(gvr, "", view)
return c.exec(gvr, view)
default:
ns := c.app.Config.ActiveNamespace()
if len(cmds) == 2 {
@ -116,7 +113,7 @@ func (c *command) run(cmd string) bool {
if !c.app.switchNS(ns) {
return false
}
return c.exec(gvr, ns, c.componentFor(gvr, v))
return c.exec(gvr, c.componentFor(gvr, v))
}
}
@ -145,7 +142,7 @@ func (c *command) componentFor(gvr string, v *viewer) ResourceViewer {
return view
}
func (c *command) exec(gvr string, ns string, comp model.Component) bool {
func (c *command) exec(gvr string, comp model.Component) bool {
if comp == nil {
log.Error().Err(fmt.Errorf("No component given for %s", gvr))
return false

View File

@ -56,10 +56,10 @@ func (c *Container) extraActions(aa ui.KeyActions) {
aa[ui.KeyShiftF] = ui.NewKeyAction("PortForward", c.portFwdCmd, true)
aa[ui.KeyShiftL] = ui.NewKeyAction("Logs Previous", c.prevLogsCmd, true)
aa[ui.KeyS] = ui.NewKeyAction("Shell", c.shellCmd, true)
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", c.sortColCmd(6, false), false)
aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", c.sortColCmd(7, false), false)
aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", c.sortColCmd(8, false), false)
aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", c.sortColCmd(9, false), false)
aa[ui.KeyShiftC] = ui.NewKeyAction("Sort CPU", c.sortColCmd(6), false)
aa[ui.KeyShiftM] = ui.NewKeyAction("Sort MEM", c.sortColCmd(7), false)
aa[ui.KeyShiftX] = ui.NewKeyAction("Sort CPU%", c.sortColCmd(8), false)
aa[ui.KeyShiftZ] = ui.NewKeyAction("Sort MEM%", c.sortColCmd(9), false)
}
func (c *Container) k9sEnv() K9sEnv {

View File

@ -2,6 +2,7 @@ package view
import (
"context"
"errors"
"strings"
"github.com/derailed/k9s/internal/resource"
@ -37,7 +38,9 @@ func (c *Context) useCtx(app *App, _, res, sel string) {
app.Flash().Err(err)
return
}
app.gotoResource("po", true)
if !app.gotoResource("po") {
app.Flash().Err(errors.New("Goto pod failed"))
}
}
func (*Context) cleanser(s string) string {

View File

@ -16,7 +16,8 @@ func TestCleaner(t *testing.T) {
}
v := Context{}
for k, u := range uu {
for k := range uu {
u := uu[k]
t.Run(k, func(t *testing.T) {
assert.Equal(t, u.e, v.cleanser(u.s))
})

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