diff --git a/cmd/root.go b/cmd/root.go index 9890a941..6a12744b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -58,13 +58,12 @@ func run(cmd *cobra.Command, args []string) { log.Error().Msg(string(debug.Stack())) fmt.Printf(printer.Colorize("Boom!! ", printer.ColorRed)) fmt.Println(printer.Colorize(fmt.Sprintf("%v.", err), printer.ColorDarkGray)) - // debug.PrintStack() } }() zerolog.SetGlobalLevel(parseLevel(logLevel)) - loadConfiguration() - app := views.NewApp() + cfg := loadConfiguration() + app := views.NewApp(cfg) { defer app.Stop() app.Init(version, refreshRate, k8sFlags) @@ -72,52 +71,22 @@ func run(cmd *cobra.Command, args []string) { } } -func loadConfiguration() { +func loadConfiguration() *config.Config { log.Info().Msg("🐶 K9s starting up...") // Load K9s config file... - cfg := k8s.NewConfig(k8sFlags) - config.Root = config.NewConfig(cfg) - if err := config.Root.Load(config.K9sConfigFile); err != nil { + 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...") } - config.Root.K9s.RefreshRate = refreshRate - mergeConfigs() - // Init K8s connection... - k8s.InitConnectionOrDie(cfg) + k9sCfg.K9s.RefreshRate = refreshRate + k9sCfg.Refine(k8sFlags) + k9sCfg.SetConnection(k8s.InitConnectionOrDie(k8sCfg, log.Logger)) log.Info().Msg("✅ Kubernetes connectivity") - config.Root.Save() -} + k9sCfg.Save() -func mergeConfigs() { - cfg, err := k8sFlags.ToRawKubeConfigLoader().RawConfig() - if err != nil { - panic("Invalid configuration. Unable to connect to api") - } - - if isSet(k8sFlags.Context) { - config.Root.K9s.CurrentContext = *k8sFlags.Context - } else { - config.Root.K9s.CurrentContext = cfg.CurrentContext - } - log.Debug().Msgf("Active Context `%v`", config.Root.K9s.CurrentContext) - - if c, ok := cfg.Contexts[config.Root.K9s.CurrentContext]; ok { - config.Root.K9s.CurrentCluster = c.Cluster - if len(c.Namespace) != 0 { - config.Root.SetActiveNamespace(c.Namespace) - } - } else { - log.Panic().Msg(fmt.Sprintf("The specified context `%s does not exists in kubeconfig", config.Root.K9s.CurrentContext)) - } - - if isSet(k8sFlags.Namespace) { - config.Root.SetActiveNamespace(*k8sFlags.Namespace) - } - - if isSet(k8sFlags.ClusterName) { - config.Root.K9s.CurrentCluster = *k8sFlags.ClusterName - } + return k9sCfg } func parseLevel(level string) zerolog.Level { @@ -251,7 +220,3 @@ func initK8sFlags() { func clearScreen() { fmt.Print("\033[H\033[2J") } - -func isSet(s *string) bool { - return s != nil && len(*s) > 0 -} diff --git a/go.mod b/go.mod index 63c4db5b..289cb216 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,12 @@ require ( github.com/Azure/go-autorest v11.4.0+incompatible // indirect github.com/derailed/tview v0.1.4 github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect + github.com/docker/distribution v2.7.1+incompatible // indirect github.com/evanphx/json-patch v4.1.0+incompatible // indirect github.com/fatih/camelcase v1.0.0 // indirect github.com/gdamore/tcell v1.1.1 github.com/gogo/protobuf v1.1.1 // indirect + github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c // indirect github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect github.com/googleapis/gnostic v0.2.0 // indirect @@ -22,8 +24,10 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect github.com/onsi/gomega v1.4.3 // indirect + github.com/opencontainers/go-digest v1.0.0-rc1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81 + github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 // indirect github.com/rs/zerolog v1.12.0 github.com/sirupsen/logrus v1.4.0 // indirect github.com/spf13/cobra v0.0.3 @@ -35,9 +39,12 @@ 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-20190322231200-1c09d17c1352 // indirect k8s.io/apimachinery v0.0.0-20190207091153-095b9d203467 + k8s.io/apiserver v0.0.0-20190322225753-5eae03fa38e7 // indirect k8s.io/cli-runtime v0.0.0-20190207094101-a32b78e5dd0a k8s.io/client-go v10.0.0+incompatible + k8s.io/component-base v0.0.0-20190313120452-4727f38490bc // indirect k8s.io/klog v0.1.0 // indirect k8s.io/kube-openapi v0.0.0-20190215190454-ea82251f3668 // indirect k8s.io/kubernetes v1.13.3 diff --git a/go.sum b/go.sum index 757495d6..1ffa49e6 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/derailed/tview v0.1.4 h1:6ZtMtb5+2bbGNH7SldHGcVB8GnSTXKIQwKxWRNb6DxY= github.com/derailed/tview v0.1.4/go.mod h1:oLBQyhVeXqeUYWDZk7/5NJVbbq/JFXm3W7oEoEtpmSc= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= 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/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= @@ -35,6 +37,8 @@ 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/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 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= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -85,12 +89,15 @@ 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= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81 h1:MhSbvsIs4KvpPYr4taOvb6j+r9VNbj/08AfjsKi+Ui0= github.com/petergtz/pegomock v0.0.0-20181206220228-b113d17a7e81/go.mod h1:nuBLWZpVyv/fLo56qTwt/AUau7jgouO1h7bEvZCq82o= +github.com/petergtz/pegomock v2.1.0+incompatible h1:oqn+Amy6vhpiuQ5PPaGd2PP2B839GC6DcV4AP/BOVCU= 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= @@ -106,6 +113,8 @@ github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R 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-20190213202703-b373355e9db4/go.mod h1:J4W+hErFfITUbyFAEXizpmkuxX7ZN56dopxHB4XQhMw= +github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967 h1:x7xEyJDP7Hv3LVgvWhzioQqbC/KtuUhTigKlH/8ehhE= +github.com/robfig/cron v0.0.0-20180505203441-b41be1df6967/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rs/zerolog v1.12.0 h1:aqZ1XRadoS8IBknR5IDFvGzbHly1X9ApIqOroooQF/c= github.com/rs/zerolog v1.12.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -185,12 +194,18 @@ 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-20190322231200-1c09d17c1352 h1:zukcRaXWkBtPh9YLgveTpIvNYzZ+DgAmpQAOxzIKB5c= +k8s.io/apiextensions-apiserver v0.0.0-20190322231200-1c09d17c1352/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/apiserver v0.0.0-20190322225753-5eae03fa38e7 h1:7ZrRe4kdzs+uEbmIRPaOrpinO3CBtQiY77I1FOv3nrU= +k8s.io/apiserver v0.0.0-20190322225753-5eae03fa38e7/go.mod h1:6bqaTSOSJavUIXUtfaR9Os9JtTCm8ZqH2SUl2S60C4w= k8s.io/cli-runtime v0.0.0-20190207094101-a32b78e5dd0a h1:MrGQxLLZ09Bl5hYYU9VlKnhY60bpPlYd9yXOPnxkdc0= k8s.io/cli-runtime v0.0.0-20190207094101-a32b78e5dd0a/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= k8s.io/client-go v10.0.0+incompatible h1:F1IqCqw7oMBzDkqlcBymRq1450wD0eNqLE9jzUrIi34= k8s.io/client-go v10.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/component-base v0.0.0-20190313120452-4727f38490bc h1:wECJj/THUnRfyHajZrU4SmU2EIrSAHb3S9d2jTItVmo= +k8s.io/component-base v0.0.0-20190313120452-4727f38490bc/go.mod h1:DMaomcf3j3MM2j1FsvlLVVlc7wA2jPytEur3cP9zRxQ= k8s.io/klog v0.1.0 h1:I5HMfc/DtuVaGR1KPwUrTc476K8NCqNBldC7H4dYEzk= k8s.io/klog v0.1.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/kube-openapi v0.0.0-20190215190454-ea82251f3668 h1:M80qeWaBNOX2Uc4plRHcb6k+3YE5VWMaJXKZo+tX9aU= diff --git a/internal/config/cluster.go b/internal/config/cluster.go index a581d9d3..4e1e2ad6 100644 --- a/internal/config/cluster.go +++ b/internal/config/cluster.go @@ -1,5 +1,7 @@ package config +import "github.com/derailed/k9s/internal/k8s" + // Cluster tracks K9s cluster configuration. type Cluster struct { Namespace *Namespace `yaml:"namespace"` @@ -12,11 +14,11 @@ func NewCluster() *Cluster { } // Validate a cluster config. -func (c *Cluster) Validate(ks KubeSettings) { +func (c *Cluster) Validate(conn k8s.Connection, ks KubeSettings) { if c.Namespace == nil { c.Namespace = NewNamespace() } - c.Namespace.Validate(ks) + c.Namespace.Validate(conn, ks) if c.View == nil { c.View = NewView() diff --git a/internal/config/config.go b/internal/config/config.go index b88709b5..c1da8269 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -9,14 +9,15 @@ import ( "os" "path/filepath" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/resource" "github.com/rs/zerolog/log" "gopkg.in/yaml.v2" + v1 "k8s.io/api/core/v1" + "k8s.io/cli-runtime/pkg/genericclioptions" ) var ( - // Root K9s configuration. - Root *Config // K9sHome represent K9s home directory. K9sHome = filepath.Join(mustK9sHome(), ".k9s") // K9sConfigFile represents K9s config file location. @@ -31,12 +32,13 @@ type KubeSettings interface { CurrentClusterName() (string, error) CurrentNamespaceName() (string, error) ClusterNames() ([]string, error) - NamespaceNames() ([]string, error) + NamespaceNames(nn []v1.Namespace) []string } // Config tracks K9s configuration options. type Config struct { K9s *K9s `yaml:"k9s"` + client k8s.Connection settings KubeSettings } @@ -45,6 +47,42 @@ func NewConfig(ks KubeSettings) *Config { return &Config{K9s: NewK9s(), settings: ks} } +func isSet(s *string) bool { + return s != nil && len(*s) > 0 +} + +// Refine the configuration based on cli args. +func (c *Config) Refine(flags *genericclioptions.ConfigFlags) { + cfg, err := flags.ToRawKubeConfigLoader().RawConfig() + if err != nil { + panic("Invalid configuration. Unable to connect to api") + } + + if isSet(flags.Context) { + c.K9s.CurrentContext = *flags.Context + } else { + c.K9s.CurrentContext = cfg.CurrentContext + } + log.Debug().Msgf("Active Context `%v`", c.K9s.CurrentContext) + + if ctx, ok := cfg.Contexts[c.K9s.CurrentContext]; ok { + c.K9s.CurrentCluster = ctx.Cluster + if len(ctx.Namespace) != 0 { + c.SetActiveNamespace(ctx.Namespace) + } + } else { + log.Panic().Msg(fmt.Sprintf("The specified context `%s does not exists in kubeconfig", c.K9s.CurrentContext)) + } + + if isSet(flags.Namespace) { + c.SetActiveNamespace(*flags.Namespace) + } + + if isSet(flags.ClusterName) { + c.K9s.CurrentCluster = *flags.ClusterName + } +} + // Reset the context to the new current context/cluster. // if it does not exist. func (c *Config) Reset() { @@ -96,6 +134,16 @@ func (c *Config) SetActiveView(view string) { c.K9s.ActiveCluster().View.Active = view } +// GetConnection return an api server connection. +func (c *Config) GetConnection() k8s.Connection { + return c.client +} + +// SetConnection set an api server connection. +func (c *Config) SetConnection(conn k8s.Connection) { + c.client = conn +} + // Load K9s configuration from file func (c *Config) Load(path string) error { f, err := ioutil.ReadFile(path) @@ -134,7 +182,7 @@ func (c *Config) SaveFile(path string) error { // Validate the configuration. func (c *Config) Validate() { - c.K9s.Validate(c.settings) + c.K9s.Validate(c.client, c.settings) } // Dump debug... diff --git a/internal/config/k9s.go b/internal/config/k9s.go index 75af8461..04f67bdc 100644 --- a/internal/config/k9s.go +++ b/internal/config/k9s.go @@ -1,5 +1,7 @@ package config +import "github.com/derailed/k9s/internal/k8s" + const ( defaultRefreshRate = 2 defaultLogRequestSize = 200 @@ -45,7 +47,7 @@ func (k *K9s) ActiveCluster() *Cluster { } // Validate the current configuration. -func (k *K9s) Validate(ks KubeSettings) { +func (k *K9s) Validate(c k8s.Connection, ks KubeSettings) { if k.RefreshRate <= 0 { k.RefreshRate = defaultRefreshRate } @@ -87,5 +89,5 @@ func (k *K9s) Validate(ks KubeSettings) { if _, ok := k.Clusters[k.CurrentCluster]; !ok { k.Clusters[k.CurrentCluster] = NewCluster() } - k.Clusters[k.CurrentCluster].Validate(ks) + k.Clusters[k.CurrentCluster].Validate(c, ks) } diff --git a/internal/config/ns.go b/internal/config/ns.go index 58370ff7..fb8c1592 100644 --- a/internal/config/ns.go +++ b/internal/config/ns.go @@ -1,7 +1,9 @@ package config import ( + "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog/log" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) const ( @@ -26,12 +28,13 @@ func NewNamespace() *Namespace { } // Validate a namespace is setup correctly -func (n *Namespace) Validate(ks KubeSettings) { - nn, err := ks.NamespaceNames() +func (n *Namespace) Validate(c k8s.Connection, ks KubeSettings) { + nns, err := c.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{}) if err != nil { return } + nn := ks.NamespaceNames(nns.Items) if !n.isAllNamespace() && !InList(nn, n.Active) { log.Debug().Msg("[Config] Validation error active namespace resetting to `default") n.Active = defaultNS diff --git a/internal/resource/base.go b/internal/resource/base.go index 95c13eda..40952fd2 100644 --- a/internal/resource/base.go +++ b/internal/resource/base.go @@ -8,34 +8,50 @@ import ( "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions/printers" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + restclient "k8s.io/client-go/rest" "k8s.io/kubernetes/pkg/kubectl/describe" versioned "k8s.io/kubernetes/pkg/kubectl/describe/versioned" + v "k8s.io/metrics/pkg/client/clientset/versioned" ) type ( - // Creator can create a new resources. - Creator interface { - NewInstance(interface{}) Columnar + // Cruder represent a crudable Kubernetes resource. + Cruder interface { + Get(ns string, name string) (interface{}, error) + List(ns string) (k8s.Collection, error) + Delete(ns string, name string) error } - // Caller can call Kubernetes verbs on a resource. - Caller interface { - k8s.Res + // Connection represents a Kubenetes apiserver connection. + Connection interface { + Config() *k8s.Config + DialOrDie() kubernetes.Interface + SwitchContextOrDie(ctx string) + NSDialOrDie() dynamic.NamespaceableResourceInterface + RestConfigOrDie() *restclient.Config + MXDial() (*v.Clientset, error) + DynDialOrDie() dynamic.Interface + HasMetrics() bool + IsNamespaced(n string) bool + SupportsResource(group string) bool } - // APIFn knows how to call K8s api server. - APIFn func() k8s.Res - - // InstanceFn instantiates a concrete resource. - InstanceFn func(interface{}) Columnar + // Factory creates new tabular resources. + Factory interface { + New(interface{}) Columnar + } // Base resource. Base struct { - path string + Factory - caller Caller - creator Creator + connection Connection + path string + resource Cruder } ) @@ -44,32 +60,39 @@ func (b *Base) Name() string { return b.path } +// ExtFields returns extended fields in relation to headers. +func (*Base) ExtFields() Properties { + return Properties{} +} + // Get a resource by name func (b *Base) Get(path string) (Columnar, error) { ns, n := namespaced(path) - i, err := b.caller.Get(ns, n) + i, err := b.resource.Get(ns, n) if err != nil { return nil, err } - return b.creator.NewInstance(i), nil + + return b.New(i), nil } // List all resources func (b *Base) List(ns string) (Columnars, error) { - ii, err := b.caller.List(ns) + ii, err := b.resource.List(ns) if err != nil { return nil, err } cc := make(Columnars, 0, len(ii)) for i := 0; i < len(ii); i++ { - cc = append(cc, b.creator.NewInstance(ii[i])) + cc = append(cc, b.New(ii[i])) } + return cc, nil } // Describe a given resource. -func (b *Base) Describe(kind, pa string) (string, error) { +func (b *Base) Describe(kind, pa string, flags *genericclioptions.ConfigFlags) (string, error) { ns, n := namespaced(pa) mapping, err := k8s.RestMapping.Find(kind) @@ -77,20 +100,22 @@ func (b *Base) Describe(kind, pa string) (string, error) { return "", err } - d, err := versioned.Describer(k8s.KubeConfig.Flags(), mapping) + d, err := versioned.Describer(flags, mapping) if err != nil { return "", err } opts := describe.DescriberSettings{ ShowEvents: true, } + return d.Describe(ns, n, opts) } // Delete a resource by name. func (b *Base) Delete(path string) error { ns, n := namespaced(path) - return b.caller.Delete(ns, n) + + return b.resource.Delete(ns, n) } func (*Base) namespacedName(m metav1.ObjectMeta) string { @@ -107,5 +132,6 @@ func (*Base) marshalObject(o runtime.Object) (string, error) { log.Error().Msgf("Marshal Error %v", err) return "", err } + return buff.String(), nil } diff --git a/internal/resource/cluster.go b/internal/resource/cluster.go index 6806c2df..5fd2521a 100644 --- a/internal/resource/cluster.go +++ b/internal/resource/cluster.go @@ -2,39 +2,52 @@ package resource import ( "github.com/derailed/k9s/internal/k8s" + "github.com/rs/zerolog" v1 "k8s.io/api/core/v1" + mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) type ( - // ClusterIfc represents a cluster. - ClusterIfc interface { + // ClusterMeta represents metadata about a Kubernetes cluster. + ClusterMeta interface { + Connection + Version() (string, error) ContextName() string ClusterName() string UserName() string } - // MetricsIfc represents a metrics server. - MetricsIfc interface { - NodeMetrics() (k8s.Metric, error) - PerNodeMetrics([]v1.Node) (map[string]k8s.Metric, error) - PodMetrics() (map[string]k8s.Metric, error) + // MetricsServer gather metrics information from pods and nodes. + MetricsServer interface { + MetricsService + + ClusterLoad([]v1.Node, []mv1beta1.NodeMetrics) k8s.ClusterMetrics + NodesMetrics([]v1.Node, []mv1beta1.NodeMetrics, k8s.NodesMetrics) + PodsMetrics([]mv1beta1.PodMetrics, k8s.PodsMetrics) + } + + // MetricsService calls the metrics server for metrics info. + MetricsService interface { + HasMetrics() bool + FetchNodesMetrics() ([]mv1beta1.NodeMetrics, error) + FetchPodsMetrics(ns string) ([]mv1beta1.PodMetrics, error) } // Cluster represents a kubernetes resource. Cluster struct { - api ClusterIfc - mx MetricsIfc + api ClusterMeta + mx MetricsServer } ) // NewCluster returns a new cluster info resource. -func NewCluster() *Cluster { - return NewClusterWithArgs(k8s.NewCluster(), k8s.NewMetricsServer()) +func NewCluster(c Connection, log *zerolog.Logger) *Cluster { + return NewClusterWithArgs(k8s.NewCluster(c, log), k8s.NewMetricsServer(c)) } // NewClusterWithArgs for tests only! -func NewClusterWithArgs(ci ClusterIfc, mx MetricsIfc) *Cluster { +func NewClusterWithArgs(ci ClusterMeta, mx MetricsServer) *Cluster { return &Cluster{api: ci, mx: mx} } @@ -44,6 +57,7 @@ func (c *Cluster) Version() string { if err != nil { return "n/a" } + return info } @@ -63,6 +77,25 @@ func (c *Cluster) UserName() string { } // Metrics gathers node level metrics and compute utilization percentages. -func (c *Cluster) Metrics() (k8s.Metric, error) { - return c.mx.NodeMetrics() +func (c *Cluster) Metrics(nodes []v1.Node, nmx []mv1beta1.NodeMetrics) k8s.ClusterMetrics { + return c.mx.ClusterLoad(nodes, nmx) +} + +// GetNodesMetrics fetch all nodes metrics. +func (c *Cluster) GetNodesMetrics() ([]mv1beta1.NodeMetrics, error) { + return c.mx.FetchNodesMetrics() +} + +// GetNodes fetch all available nodes. +func (c *Cluster) GetNodes() ([]v1.Node, error) { + nn, err := k8s.NewNode(c.api).List("") + if err != nil { + return nil, err + } + nodes := make([]v1.Node, 0, len(nn)) + for _, n := range nn { + nodes = append(nodes, n.(v1.Node)) + } + + return nodes, nil } diff --git a/internal/resource/cluster_test.go b/internal/resource/cluster_test.go index 6afaa602..d144d992 100644 --- a/internal/resource/cluster_test.go +++ b/internal/resource/cluster_test.go @@ -8,12 +8,14 @@ import ( "github.com/derailed/k9s/internal/resource" m "github.com/petergtz/pegomock" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) func TestClusterVersion(t *testing.T) { setup(t) - cIfc, mxIfc := NewMockClusterIfc(), NewMockMetricsIfc() + cIfc, mxIfc := NewMockClusterMeta(), NewMockMetricsServer() m.When(cIfc.Version()).ThenReturn("1.2.3", nil) ci := resource.NewClusterWithArgs(cIfc, mxIfc) @@ -23,7 +25,7 @@ func TestClusterVersion(t *testing.T) { func TestClusterNoVersion(t *testing.T) { setup(t) - cIfc, mxIfc := NewMockClusterIfc(), NewMockMetricsIfc() + cIfc, mxIfc := NewMockClusterMeta(), NewMockMetricsServer() m.When(cIfc.Version()).ThenReturn("bad", fmt.Errorf("No data")) ci := resource.NewClusterWithArgs(cIfc, mxIfc) @@ -33,7 +35,7 @@ func TestClusterNoVersion(t *testing.T) { func TestClusterName(t *testing.T) { setup(t) - cIfc, mxIfc := NewMockClusterIfc(), NewMockMetricsIfc() + cIfc, mxIfc := NewMockClusterMeta(), NewMockMetricsServer() m.When(cIfc.ClusterName()).ThenReturn("fred") ci := resource.NewClusterWithArgs(cIfc, mxIfc) @@ -43,13 +45,11 @@ func TestClusterName(t *testing.T) { func TestClusterMetrics(t *testing.T) { setup(t) - cIfc, mxIfc := NewMockClusterIfc(), NewMockMetricsIfc() - m.When(mxIfc.NodeMetrics()).ThenReturn(testMetric(), nil) + cIfc, mxIfc := NewMockClusterMeta(), NewMockMetricsServer() + m.When(mxIfc.ClusterLoad([]v1.Node{}, []mv1beta1.NodeMetrics{})).ThenReturn(clusterMetric()) c := resource.NewClusterWithArgs(cIfc, mxIfc) - m, err := c.Metrics() - assert.Nil(t, err) - assert.Equal(t, testMetric(), m) + assert.Equal(t, clusterMetric(), c.Metrics([]v1.Node{}, []mv1beta1.NodeMetrics{})) } // Helpers... @@ -61,11 +61,9 @@ func setup(t *testing.T) { }) } -func testMetric() k8s.Metric { - return k8s.Metric{ - CPU: "100m", - AvailCPU: "1000m", - Mem: "256Gi", - AvailMem: "512Gi", +func clusterMetric() k8s.ClusterMetrics { + return k8s.ClusterMetrics{ + PercCPU: 100, + PercMEM: 1000, } } diff --git a/internal/resource/cm.go b/internal/resource/cm.go index 0d50881b..8b4cf4e8 100644 --- a/internal/resource/cm.go +++ b/internal/resource/cm.go @@ -15,51 +15,43 @@ type ConfigMap struct { } // NewConfigMapList returns a new resource list. -func NewConfigMapList(ns string) List { - return NewConfigMapListWithArgs(ns, NewConfigMap()) -} - -// NewConfigMapListWithArgs returns a new resource list. -func NewConfigMapListWithArgs(ns string, res Resource) List { - return newList(ns, "cm", res, AllVerbsAccess|DescribeAccess) +func NewConfigMapList(c k8s.Connection, ns string) List { + return newList( + ns, + "cm", + NewConfigMap(c), + AllVerbsAccess|DescribeAccess, + ) } // NewConfigMap instantiates a new ConfigMap. -func NewConfigMap() *ConfigMap { - return NewConfigMapWithArgs(k8s.NewConfigMap()) +func NewConfigMap(c k8s.Connection) *ConfigMap { + m := &ConfigMap{&Base{connection: c, resource: k8s.NewConfigMap(c)}, nil} + m.Factory = m + + return m } -// NewConfigMapWithArgs instantiates a new ConfigMap. -func NewConfigMapWithArgs(r k8s.Res) *ConfigMap { - cm := &ConfigMap{ - Base: &Base{ - caller: r, - }, - } - cm.creator = cm - return cm -} - -// NewInstance builds a new ConfigMap instance from a k8s resource. -func (*ConfigMap) NewInstance(i interface{}) Columnar { - cm := NewConfigMap() - switch i.(type) { +// New builds a new ConfigMap instance from a k8s resource. +func (r *ConfigMap) New(i interface{}) Columnar { + cm := NewConfigMap(r.connection) + switch instance := i.(type) { case *v1.ConfigMap: - cm.instance = i.(*v1.ConfigMap) + cm.instance = instance case v1.ConfigMap: - ii := i.(v1.ConfigMap) - cm.instance = &ii + cm.instance = &instance default: log.Fatalf("Unknown %#v", i) } cm.path = cm.namespacedName(cm.instance.ObjectMeta) + return cm } // Marshal resource to yaml. func (r *ConfigMap) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *ConfigMap) Marshal(path string) (string, error) { cm := i.(*v1.ConfigMap) cm.TypeMeta.APIVersion = "v1" cm.TypeMeta.Kind = "ConfigMap" + return r.marshalObject(cm) } @@ -76,6 +69,7 @@ func (*ConfigMap) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "DATA", "AGE") } @@ -93,8 +87,3 @@ func (r *ConfigMap) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*ConfigMap) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/cm_test.go b/internal/resource/cm_test.go index 9f8ed3e3..6ecb14a8 100644 --- a/internal/resource/cm_test.go +++ b/internal/resource/cm_test.go @@ -41,36 +41,39 @@ func TestCMFields(t *testing.T) { func TestCMGet(t *testing.T) { setup(t) - ca := NewMockCaller() + conn := NewMockConnection() + ca := NewMockCruder() m.When(ca.Get("blee", "fred")).ThenReturn(k8sCM(), nil) - cm := resource.NewConfigMapWithArgs(ca) + cm := resource.NewConfigMap(conn) ma, err := cm.Get("blee/fred") assert.Nil(t, err) ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Equal(t, cm.NewInstance(k8sCM()), ma) + assert.Equal(t, cm.New(k8sCM()), ma) } func TestCMList(t *testing.T) { setup(t) - ca := NewMockCaller() + conn := NewMockConnection() + ca := NewMockCruder() m.When(ca.List("blee")).ThenReturn(k8s.Collection{*k8sCM()}, nil) - cm := resource.NewConfigMapWithArgs(ca) + cm := resource.NewConfigMap(conn) ma, err := cm.List("blee") assert.Nil(t, err) ca.VerifyWasCalledOnce().List("blee") - assert.Equal(t, resource.Columnars{cm.NewInstance(k8sCM())}, ma) + assert.Equal(t, resource.Columnars{cm.New(k8sCM())}, ma) } func TestCMDelete(t *testing.T) { setup(t) - ca := NewMockCaller() + conn := NewMockConnection() + ca := NewMockCruder() m.When(ca.Delete("blee", "fred")).ThenReturn(nil) - cm := resource.NewConfigMapWithArgs(ca) + cm := resource.NewConfigMap(conn) assert.Nil(t, cm.Delete("blee/fred")) ca.VerifyWasCalledOnce().Delete("blee", "fred") } @@ -78,10 +81,11 @@ func TestCMDelete(t *testing.T) { func TestCMMarshal(t *testing.T) { setup(t) - ca := NewMockCaller() + conn := NewMockConnection() + ca := NewMockCruder() m.When(ca.Get("blee", "fred")).ThenReturn(k8sCM(), nil) - cm := resource.NewConfigMapWithArgs(ca) + cm := resource.NewConfigMap(conn) ma, err := cm.Marshal("blee/fred") ca.VerifyWasCalledOnce().Get("blee", "fred") @@ -92,48 +96,38 @@ func TestCMMarshal(t *testing.T) { func TestCMListHasName(t *testing.T) { setup(t) - ca := NewMockCaller() - l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca)) + conn := NewMockConnection() + ca := NewMockCruder() + l := resource.NewConfigMapList(conn, "blee") assert.Equal(t, "cm", l.GetName()) } func TestCMListHasNamespace(t *testing.T) { setup(t) - ca := NewMockCaller() - l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca)) + conn := NewMockConnection() + ca := NewMockCruder() + l := resource.NewConfigMapList(conn, "blee") assert.Equal(t, "blee", l.GetNamespace()) } func TestCMListHasResource(t *testing.T) { setup(t) - ca := NewMockCaller() - l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca)) + conn := NewMockConnection() + ca := NewMockCruder() + l := resource.NewConfigMapList(conn, "blee") assert.NotNil(t, l.Resource()) } -func TestCMListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sCM(), nil) - - l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - func TestCMListData(t *testing.T) { setup(t) - ca := NewMockCaller() + conn := NewMockConnection() + ca := NewMockCruder() m.When(ca.List("blee")).ThenReturn(k8s.Collection{*k8sCM()}, nil) - l := resource.NewConfigMapListWithArgs("blee", resource.NewConfigMapWithArgs(ca)) + l := resource.NewConfigMapList(conn, "blee") // Make sure we can get deltas! for i := 0; i < 2; i++ { err := l.Reconcile() @@ -146,7 +140,6 @@ func TestCMListData(t *testing.T) { assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, "blee", l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 3, len(row.Deltas)) for _, d := range row.Deltas { @@ -158,7 +151,8 @@ func TestCMListData(t *testing.T) { // Helpers... func newConfigMap() resource.Columnar { - return resource.NewConfigMap().NewInstance(k8sCM()) + conn := NewMockConnection() + return resource.NewConfigMap(conn).New(k8sCM()) } func k8sCM() *v1.ConfigMap { diff --git a/internal/resource/context.go b/internal/resource/context.go index 61f281bb..1dd7be34 100644 --- a/internal/resource/context.go +++ b/internal/resource/context.go @@ -7,6 +7,7 @@ import ( // SwitchableRes represents a resource that can be switched. type SwitchableRes interface { + k8s.Connection k8s.ContextRes } @@ -17,50 +18,37 @@ type Context struct { } // NewContextList returns a new resource list. -func NewContextList(ns string) List { - return NewContextListWithArgs(ns, NewContext()) -} - -// NewContextListWithArgs returns a new resource list. -func NewContextListWithArgs(ns string, res Resource) List { - return newList(NotNamespaced, "ctx", res, SwitchAccess) +func NewContextList(c k8s.Connection, ns string) List { + return newList(NotNamespaced, "ctx", NewContext(c), SwitchAccess) } // NewContext instantiates a new Context. -func NewContext() *Context { - return NewContextWithArgs(k8s.NewContext().(SwitchableRes)) -} +func NewContext(c k8s.Connection) *Context { + ctx := &Context{&Base{connection: c, resource: k8s.NewContext(c)}, nil} + ctx.Factory = ctx -// NewContextWithArgs instantiates a new Context. -func NewContextWithArgs(r SwitchableRes) *Context { - ctx := &Context{ - Base: &Base{ - caller: r, - }, - } - ctx.creator = ctx return ctx } -// NewInstance builds a new Context instance from a k8s resource. -func (r *Context) NewInstance(i interface{}) Columnar { - c := NewContext() - switch i.(type) { +// New builds a new Context instance from a k8s resource. +func (r *Context) New(i interface{}) Columnar { + c := NewContext(r.connection) + switch instance := i.(type) { case *k8s.NamedContext: - c.instance = i.(*k8s.NamedContext) + c.instance = instance case k8s.NamedContext: - ii := i.(k8s.NamedContext) - c.instance = &ii + c.instance = &instance default: log.Fatal().Msgf("unknown context type %#v", i) } c.path = c.instance.Name + return c } // Switch out current context. func (r *Context) Switch(c string) error { - return r.caller.(k8s.ContextRes).Switch(c) + return r.resource.(k8s.ContextRes).Switch(c) } // Marshal the resource to yaml. @@ -90,8 +78,3 @@ func (r *Context) Fields(ns string) Row { i.Context.Namespace, ) } - -// ExtFields returns extended fields in relation to headers. -func (*Context) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/context_test.go b/internal/resource/context_test.go index 595482ec..9e778d04 100644 --- a/internal/resource/context_test.go +++ b/internal/resource/context_test.go @@ -78,19 +78,6 @@ func TestCTXListHasResource(t *testing.T) { assert.NotNil(t, l.Resource()) } -func TestCTXListDescribe(t *testing.T) { - setup(t) - - ca := NewMockSwitchableRes() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sNamedCTX(), nil) - - l := resource.NewContextListWithArgs("blee", resource.NewContextWithArgs(ca)) - props, err := l.Describe("blee/fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) - ca.VerifyWasCalledOnce().Get("blee", "fred") -} - // Helpers... func newContext() resource.Columnar { diff --git a/internal/resource/cr.go b/internal/resource/cr.go index aad70e50..e1ec11cf 100644 --- a/internal/resource/cr.go +++ b/internal/resource/cr.go @@ -13,51 +13,43 @@ type ClusterRole struct { } // NewClusterRoleList returns a new resource list. -func NewClusterRoleList(ns string) List { - return NewClusterRoleListWithArgs(ns, NewClusterRole()) -} - -// NewClusterRoleListWithArgs returns a new resource list. -func NewClusterRoleListWithArgs(ns string, res Resource) List { - return newList(NotNamespaced, "clusterrole", res, CRUDAccess|DescribeAccess) +func NewClusterRoleList(c k8s.Connection, ns string) List { + return newList( + NotNamespaced, + "clusterrole", + NewClusterRole(c), + CRUDAccess|DescribeAccess, + ) } // NewClusterRole instantiates a new ClusterRole. -func NewClusterRole() *ClusterRole { - return NewClusterRoleWithArgs(k8s.NewClusterRole()) +func NewClusterRole(c k8s.Connection) *ClusterRole { + cr := &ClusterRole{&Base{connection: c, resource: k8s.NewClusterRole(c)}, nil} + cr.Factory = cr + + return cr } -// NewClusterRoleWithArgs instantiates a new Context. -func NewClusterRoleWithArgs(r k8s.Res) *ClusterRole { - ctx := &ClusterRole{ - Base: &Base{ - caller: r, - }, - } - ctx.creator = ctx - return ctx -} - -// NewInstance builds a new Context instance from a k8s resource. -func (r *ClusterRole) NewInstance(i interface{}) Columnar { - c := NewClusterRole() - switch i.(type) { +// New builds a new ClusterRole instance from a k8s resource. +func (r *ClusterRole) New(i interface{}) Columnar { + c := NewClusterRole(r.connection) + switch instance := i.(type) { case *v1.ClusterRole: - c.instance = i.(*v1.ClusterRole) + c.instance = instance case v1.ClusterRole: - ii := i.(v1.ClusterRole) - c.instance = &ii + c.instance = &instance default: log.Fatal().Msgf("unknown context type %#v", i) } c.path = c.instance.Name + return c } // Marshal resource to yaml. func (r *ClusterRole) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -65,6 +57,7 @@ func (r *ClusterRole) Marshal(path string) (string, error) { cr := i.(*v1.ClusterRole) cr.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" cr.TypeMeta.Kind = "ClusterRole" + return r.marshalObject(cr) } @@ -83,8 +76,3 @@ func (r *ClusterRole) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*ClusterRole) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/cr_binding.go b/internal/resource/cr_binding.go index 95943992..06e6c3d1 100644 --- a/internal/resource/cr_binding.go +++ b/internal/resource/cr_binding.go @@ -13,51 +13,43 @@ type ClusterRoleBinding struct { } // NewClusterRoleBindingList returns a new resource list. -func NewClusterRoleBindingList(ns string) List { - return NewClusterRoleBindingListWithArgs(ns, NewClusterRoleBinding()) -} - -// NewClusterRoleBindingListWithArgs returns a new resource list. -func NewClusterRoleBindingListWithArgs(ns string, res Resource) List { - return newList(NotNamespaced, "ctx", res, SwitchAccess|ViewAccess|DeleteAccess|DescribeAccess) +func NewClusterRoleBindingList(c k8s.Connection, _ string) List { + return newList( + NotNamespaced, + "ctx", + NewClusterRoleBinding(c), + SwitchAccess|ViewAccess|DeleteAccess|DescribeAccess, + ) } // NewClusterRoleBinding instantiates a new ClusterRoleBinding. -func NewClusterRoleBinding() *ClusterRoleBinding { - return NewClusterRoleBindingWithArgs(k8s.NewClusterRoleBinding()) +func NewClusterRoleBinding(c k8s.Connection) *ClusterRoleBinding { + crb := &ClusterRoleBinding{&Base{connection: c, resource: k8s.NewClusterRoleBinding(c)}, nil} + crb.Factory = crb + + return crb } -// NewClusterRoleBindingWithArgs instantiates a new Context. -func NewClusterRoleBindingWithArgs(r k8s.Res) *ClusterRoleBinding { - ctx := &ClusterRoleBinding{ - Base: &Base{ - caller: r, - }, - } - ctx.creator = ctx - return ctx -} - -// NewInstance builds a new Context instance from a k8s resource. -func (r *ClusterRoleBinding) NewInstance(i interface{}) Columnar { - c := NewClusterRoleBinding() - switch i.(type) { +// New builds a new tabular instance from a k8s resource. +func (r *ClusterRoleBinding) New(i interface{}) Columnar { + crb := NewClusterRoleBinding(r.connection) + switch instance := i.(type) { case *v1.ClusterRoleBinding: - c.instance = i.(*v1.ClusterRoleBinding) + crb.instance = instance case v1.ClusterRoleBinding: - ii := i.(v1.ClusterRoleBinding) - c.instance = &ii + crb.instance = &instance default: log.Fatal().Msgf("unknown context type %#v", i) } - c.path = c.instance.Name - return c + crb.path = crb.instance.Name + + return crb } // Marshal resource to yaml. func (r *ClusterRoleBinding) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -65,6 +57,7 @@ func (r *ClusterRoleBinding) Marshal(path string) (string, error) { crb := i.(*v1.ClusterRoleBinding) crb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" crb.TypeMeta.Kind = "ClusterRoleBinding" + return r.marshalObject(crb) } @@ -76,15 +69,9 @@ func (*ClusterRoleBinding) Header(_ string) Row { // Fields retrieves displayable fields. func (r *ClusterRoleBinding) Fields(ns string) Row { ff := make(Row, 0, len(r.Header(ns))) - i := r.instance return append(ff, - i.Name, - toAge(i.ObjectMeta.CreationTimestamp), + r.instance.Name, + toAge(r.instance.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*ClusterRoleBinding) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/cr_binding_test.go b/internal/resource/cr_binding_test.go index 33ad0102..c846d138 100644 --- a/internal/resource/cr_binding_test.go +++ b/internal/resource/cr_binding_test.go @@ -55,7 +55,6 @@ func TestCRBListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["fred"] assert.Equal(t, 2, len(row.Deltas)) for _, d := range row.Deltas { diff --git a/internal/resource/cr_test.go b/internal/resource/cr_test.go index 575ffc49..a7ed5c4a 100644 --- a/internal/resource/cr_test.go +++ b/internal/resource/cr_test.go @@ -74,7 +74,6 @@ func TestCRListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["fred"] assert.Equal(t, 2, len(row.Deltas)) for _, d := range row.Deltas { @@ -83,19 +82,6 @@ func TestCRListData(t *testing.T) { assert.Equal(t, resource.Row{"fred" + strings.Repeat(" ", 66)}, row.Fields[:1]) } -func TestCRListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sCR(), nil) - l := resource.NewClusterRoleListWithArgs("blee", resource.NewClusterRoleWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sCR() *rbacv1.ClusterRole { diff --git a/internal/resource/crd.go b/internal/resource/crd.go index 8c12d523..6e6fe2ec 100644 --- a/internal/resource/crd.go +++ b/internal/resource/crd.go @@ -17,42 +17,33 @@ type CRD struct { } // NewCRDList returns a new resource list. -func NewCRDList(ns string) List { - return NewCRDListWithArgs(ns, NewCRD()) -} - -// NewCRDListWithArgs returns a new resource list. -func NewCRDListWithArgs(ns string, res Resource) List { - return newList(NotNamespaced, "crd", res, CRUDAccess|DescribeAccess) +func NewCRDList(c k8s.Connection, ns string) List { + return newList( + NotNamespaced, + "crd", + NewCRD(c), + CRUDAccess|DescribeAccess, + ) } // NewCRD instantiates a new CRD. -func NewCRD() *CRD { - return NewCRDWithArgs(k8s.NewCRD()) +func NewCRD(c k8s.Connection) *CRD { + crd := &CRD{&Base{connection: c, resource: k8s.NewCRD(c)}, nil} + crd.Factory = crd + + return crd } -// NewCRDWithArgs instantiates a new Context. -func NewCRDWithArgs(r k8s.Res) *CRD { - ctx := &CRD{ - Base: &Base{ - caller: r, - }, - } - ctx.creator = ctx - return ctx -} - -// NewInstance builds a new Context instance from a k8s resource. -func (r *CRD) NewInstance(i interface{}) Columnar { - c := NewCRD() - switch i.(type) { +// New builds a new CRD instance from a k8s resource. +func (r *CRD) New(i interface{}) Columnar { + c := NewCRD(r.connection) + switch instance := i.(type) { case *unstructured.Unstructured: - c.instance = i.(*unstructured.Unstructured) + c.instance = instance case unstructured.Unstructured: - ii := i.(unstructured.Unstructured) - c.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("unknown context type %#v", i) + log.Fatal().Msgf("unknown CRD type %#v", i) } meta := c.instance.Object["metadata"].(map[string]interface{}) c.path = meta["name"].(string) @@ -63,7 +54,7 @@ func (r *CRD) NewInstance(i interface{}) Columnar { // Marshal a resource. func (r *CRD) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -72,9 +63,10 @@ func (r *CRD) Marshal(path string) (string, error) { if err != nil { return "", err } - return string(raw), nil + // BOZO!! Need to figure out apiGroup+Version // return r.marshalObject(i.(*unstructured.Unstructured)) + return string(raw), nil } // Header return the resource header. @@ -92,6 +84,7 @@ func (r *CRD) Fields(ns string) Row { if err != nil { log.Error().Msgf("Fields timestamp %v", err) } + return append(ff, meta["name"].(string), toAge(metav1.Time{t})) } @@ -112,5 +105,6 @@ func (r *CRD) ExtFields() Properties { pp["singular"], pp["plural"] = names["singular"], names["plural"] pp["aliases"] = names["shortNames"] } + return pp } diff --git a/internal/resource/crd_test.go b/internal/resource/crd_test.go index 1d04856f..f8e0177d 100644 --- a/internal/resource/crd_test.go +++ b/internal/resource/crd_test.go @@ -70,7 +70,6 @@ func TestCRDListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["fred"] assert.Equal(t, 2, len(row.Deltas)) for _, d := range row.Deltas { @@ -79,19 +78,6 @@ func TestCRDListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestCRDListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sCRD(), nil) - l := resource.NewCRDListWithArgs("blee", resource.NewCRDWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sCRD() *unstructured.Unstructured { diff --git a/internal/resource/cronjob.go b/internal/resource/cronjob.go index e23173eb..ea800e1d 100644 --- a/internal/resource/cronjob.go +++ b/internal/resource/cronjob.go @@ -28,51 +28,43 @@ type ( ) // NewCronJobList returns a new resource list. -func NewCronJobList(ns string) List { - return NewCronJobListWithArgs(ns, NewCronJob()) -} - -// NewCronJobListWithArgs returns a new resource list. -func NewCronJobListWithArgs(ns string, res Resource) List { - return newList(ns, "cronjob", res, AllVerbsAccess|DescribeAccess) +func NewCronJobList(c k8s.Connection, ns string) List { + return newList( + ns, + "cronjob", + NewCronJob(c), + AllVerbsAccess|DescribeAccess, + ) } // NewCronJob instantiates a new CronJob. -func NewCronJob() *CronJob { - return NewCronJobWithArgs(k8s.NewCronJob()) +func NewCronJob(c k8s.Connection) *CronJob { + cj := &CronJob{&Base{connection: c, resource: k8s.NewCronJob(c)}, nil} + cj.Factory = cj + + return cj } -// NewCronJobWithArgs instantiates a new CronJob. -func NewCronJobWithArgs(r k8s.Res) *CronJob { - cm := &CronJob{ - Base: &Base{ - caller: r, - }, - } - cm.creator = cm - return cm -} - -// NewInstance builds a new CronJob instance from a k8s resource. -func (*CronJob) NewInstance(i interface{}) Columnar { - job := NewCronJob() - switch i.(type) { +// New builds a new CronJob instance from a k8s resource. +func (r *CronJob) New(i interface{}) Columnar { + c := NewCronJob(r.connection) + switch instance := i.(type) { case *batchv1beta1.CronJob: - job.instance = i.(*batchv1beta1.CronJob) + c.instance = instance case batchv1beta1.CronJob: - ii := i.(batchv1beta1.CronJob) - job.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown CronJob type %#v", i) } - job.path = job.namespacedName(job.instance.ObjectMeta) - return job + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *CronJob) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -80,15 +72,17 @@ func (r *CronJob) Marshal(path string) (string, error) { cj := i.(*batchv1beta1.CronJob) cj.TypeMeta.APIVersion = "extensions/batchv1beta1" cj.TypeMeta.Kind = "CronJob" + return r.marshalObject(cj) } // Run a given cronjob. func (r *CronJob) Run(pa string) error { ns, n := namespaced(pa) - if c, ok := r.caller.(Runnable); ok { + if c, ok := r.resource.(Runnable); ok { return c.Run(ns, n) } + return fmt.Errorf("unable to run cronjob %s", pa) } @@ -98,6 +92,7 @@ func (*CronJob) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "SCHEDULE", "SUSPEND", "ACTIVE", "LAST_SCHEDULE", "AGE") } @@ -124,8 +119,3 @@ func (r *CronJob) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*CronJob) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/cronjob_test.go b/internal/resource/cronjob_test.go index d55ea25d..02640cf6 100644 --- a/internal/resource/cronjob_test.go +++ b/internal/resource/cronjob_test.go @@ -62,7 +62,6 @@ func TestCronJobListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 6, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestCronJobListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestCronJobListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sCronJob(), nil) - l := resource.NewCronJobListWithArgs("blee", resource.NewCronJobWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sCronJob() *batchv1beta1.CronJob { diff --git a/internal/resource/custom.go b/internal/resource/custom.go index ab6ac3eb..ac1ae140 100644 --- a/internal/resource/custom.go +++ b/internal/resource/custom.go @@ -16,49 +16,41 @@ import ( // Custom tracks a kubernetes resource. type Custom struct { *Base - // instance *unstructured.Unstructured + instance *metav1beta1.TableRow group, version, name string headers Row } // NewCustomList returns a new resource list. -func NewCustomList(ns, g, v, n string) List { - return NewCustomListWithArgs(ns, n, NewCustom(g, v, n)) -} - -// NewCustomListWithArgs returns a new resource list. -func NewCustomListWithArgs(ns, n string, res Resource) List { - return newList(ns, n, res, AllVerbsAccess) +func NewCustomList(c k8s.Connection, ns, group, version, name string) List { + if !c.IsNamespaced(name) { + ns = NotNamespaced + } + return newList( + ns, + name, + NewCustom(c, group, version, name), AllVerbsAccess, + ) } // NewCustom instantiates a new Kubernetes Resource. -func NewCustom(g, v, n string) *Custom { - return NewCustomWithArgs(k8s.NewResource(g, v, n)) -} +func NewCustom(c k8s.Connection, group, version, name string) *Custom { + cr := &Custom{Base: &Base{connection: c, resource: k8s.NewResource(c, group, version, name)}} + cr.Factory = cr + cr.group, cr.version, cr.name = cr.resource.(*k8s.Resource).GetInfo() -// NewCustomWithArgs instantiates a new Custom. -func NewCustomWithArgs(r k8s.Res) *Custom { - cr := &Custom{ - Base: &Base{ - caller: r, - }, - } - cr.creator = cr - - cr.group, cr.version, cr.name = r.(*k8s.Resource).GetInfo() return cr } -// NewInstance builds a new Custom instance from a k8s resource. -func (*Custom) NewInstance(i interface{}) Columnar { - cr := NewCustom("", "", "") - switch i.(type) { +// New builds a new Custom instance from a k8s resource. +func (r *Custom) New(i interface{}) Columnar { + cr := NewCustom(r.connection, "", "", "") + switch instance := i.(type) { case *metav1beta1.TableRow: - cr.instance = i.(*metav1beta1.TableRow) + cr.instance = instance case metav1beta1.TableRow: - t := i.(metav1beta1.TableRow) - cr.instance = &t + cr.instance = &instance default: log.Fatal().Msgf("Unknown %#v", i) } @@ -75,13 +67,14 @@ func (*Custom) NewInstance(i interface{}) Columnar { name := meta["name"].(string) cr.path = path.Join(ns, name) cr.group, cr.version, cr.name = obj["kind"].(string), obj["apiVersion"].(string), name + return cr } // Marshal resource to yaml. func (r *Custom) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -90,12 +83,13 @@ func (r *Custom) Marshal(path string) (string, error) { if err != nil { return "", err } + return string(raw), nil } // List all resources func (r *Custom) List(ns string) (Columnars, error) { - ii, err := r.caller.List(ns) + ii, err := r.resource.List(ns) if err != nil { return nil, err } @@ -112,21 +106,23 @@ func (r *Custom) List(ns string) (Columnars, error) { rows := table.Rows cc := make(Columnars, 0, len(rows)) for i := 0; i < len(rows); i++ { - cc = append(cc, r.creator.NewInstance(rows[i])) + cc = append(cc, r.New(rows[i])) } + return cc, nil } // Header return resource header. func (r *Custom) Header(ns string) Row { hh := make(Row, 0, len(r.headers)+1) + if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } - for _, h := range r.headers { hh = append(hh, strings.ToUpper(h)) } + return hh } @@ -142,55 +138,17 @@ func (r *Custom) Fields(ns string) Row { } meta := obj["metadata"].(map[string]interface{}) + rns, ok := meta["namespace"].(string) if ns == AllNamespaces { - ff = append(ff, meta["namespace"].(string)) + if ok { + ff = append(ff, rns) + } } + for _, c := range r.instance.Cells { ff = append(ff, fmt.Sprintf("%v", c)) } return ff } - -// ExtFields returns extended fields in relation to headers. -func (*Custom) ExtFields() Properties { - return Properties{} -} - -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{ - Resource: ff["name"].(string), - Version: ff["version"].(string), - Group: ff["group"].(string), - Kind: ff["kind"].(string), - } - if aa, ok := ff["aliases"].([]interface{}); ok { - if n, ok := ff["plural"].(string); ok { - grp.Plural = n - } - if n, ok := ff["singular"].(string); ok { - grp.Singular = n - } - aliases := make([]string, len(aa)) - for i, a := range aa { - aliases[i] = a.(string) - } - grp.Aliases = aliases - } else if s, ok := ff["singular"].(string); ok { - grp.Singular = s - if p, ok := ff["plural"].(string); ok { - grp.Plural = p - } - } else if s, ok := ff["plural"].(string); ok { - grp.Plural = s - } - m[grp.Kind] = grp - } - return m -} diff --git a/internal/resource/dp.go b/internal/resource/dp.go index 4af65d9f..3a6d1319 100644 --- a/internal/resource/dp.go +++ b/internal/resource/dp.go @@ -15,51 +15,43 @@ type Deployment struct { } // NewDeploymentList returns a new resource list. -func NewDeploymentList(ns string) List { - return NewDeploymentListWithArgs(ns, NewDeployment()) -} - -// NewDeploymentListWithArgs returns a new resource list. -func NewDeploymentListWithArgs(ns string, res Resource) List { - return newList(ns, "deploy", res, AllVerbsAccess|DescribeAccess) +func NewDeploymentList(c k8s.Connection, ns string) List { + return newList( + ns, + "deploy", + NewDeployment(c), + AllVerbsAccess|DescribeAccess, + ) } // NewDeployment instantiates a new Deployment. -func NewDeployment() *Deployment { - return NewDeploymentWithArgs(k8s.NewDeployment()) +func NewDeployment(c k8s.Connection) *Deployment { + d := &Deployment{&Base{connection: c, resource: k8s.NewDeployment(c)}, nil} + d.Factory = d + + return d } -// NewDeploymentWithArgs instantiates a new Deployment. -func NewDeploymentWithArgs(r k8s.Res) *Deployment { - cm := &Deployment{ - Base: &Base{ - caller: r, - }, - } - cm.creator = cm - return cm -} - -// NewInstance builds a new Deployment instance from a k8s resource. -func (*Deployment) NewInstance(i interface{}) Columnar { - cm := NewDeployment() - switch i.(type) { +// New builds a new Deployment instance from a k8s resource. +func (r *Deployment) New(i interface{}) Columnar { + c := NewDeployment(r.connection) + switch instance := i.(type) { case *v1.Deployment: - cm.instance = i.(*v1.Deployment) + c.instance = instance case v1.Deployment: - ii := i.(v1.Deployment) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Deployment type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *Deployment) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *Deployment) Marshal(path string) (string, error) { dp := i.(*v1.Deployment) dp.TypeMeta.APIVersion = "apps/v1" dp.TypeMeta.Kind = "Deployment" + return r.marshalObject(dp) } @@ -76,6 +69,7 @@ func (*Deployment) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "DESIRED", "CURRENT", "UP-TO-DATE", "AVAILABLE", "AGE") } @@ -87,6 +81,7 @@ func (r *Deployment) Fields(ns string) Row { if ns == AllNamespaces { ff = append(ff, i.Namespace) } + return append(ff, i.Name, strconv.Itoa(int(*i.Spec.Replicas)), @@ -96,8 +91,3 @@ func (r *Deployment) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*Deployment) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/dp_test.go b/internal/resource/dp_test.go index 4ca7b7d0..60f82c3d 100644 --- a/internal/resource/dp_test.go +++ b/internal/resource/dp_test.go @@ -62,7 +62,6 @@ func TestDeploymentListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 6, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestDeploymentListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestDeploymentListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sDeployment(), nil) - l := resource.NewDeploymentListWithArgs("blee", resource.NewDeploymentWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sDeployment() *appsv1.Deployment { diff --git a/internal/resource/ds.go b/internal/resource/ds.go index 373db328..047f9b70 100644 --- a/internal/resource/ds.go +++ b/internal/resource/ds.go @@ -15,51 +15,43 @@ type DaemonSet struct { } // NewDaemonSetList returns a new resource list. -func NewDaemonSetList(ns string) List { - return NewDaemonSetListWithArgs(ns, NewDaemonSet()) -} - -// NewDaemonSetListWithArgs returns a new resource list. -func NewDaemonSetListWithArgs(ns string, res Resource) List { - return newList(ns, "ds", res, AllVerbsAccess|DescribeAccess) +func NewDaemonSetList(c k8s.Connection, ns string) List { + return newList( + ns, + "ds", + NewDaemonSet(c), + AllVerbsAccess|DescribeAccess, + ) } // NewDaemonSet instantiates a new DaemonSet. -func NewDaemonSet() *DaemonSet { - return NewDaemonSetWithArgs(k8s.NewDaemonSet()) +func NewDaemonSet(c k8s.Connection) *DaemonSet { + ds := &DaemonSet{&Base{connection: c, resource: k8s.NewDaemonSet(c)}, nil} + ds.Factory = ds + + return ds } -// NewDaemonSetWithArgs instantiates a new DaemonSet. -func NewDaemonSetWithArgs(r k8s.Res) *DaemonSet { - cm := &DaemonSet{ - Base: &Base{ - caller: r, - }, - } - cm.creator = cm - return cm -} - -// NewInstance builds a new DaemonSet instance from a k8s resource. -func (*DaemonSet) NewInstance(i interface{}) Columnar { - cm := NewDaemonSet() - switch i.(type) { +// New builds a new DaemonSet instance from a k8s resource. +func (r *DaemonSet) New(i interface{}) Columnar { + c := NewDaemonSet(r.connection) + switch instance := i.(type) { case *extv1beta1.DaemonSet: - cm.instance = i.(*extv1beta1.DaemonSet) + c.instance = instance case extv1beta1.DaemonSet: - ii := i.(extv1beta1.DaemonSet) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown DaemonSet type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *DaemonSet) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *DaemonSet) Marshal(path string) (string, error) { ds := i.(*extv1beta1.DaemonSet) ds.TypeMeta.APIVersion = "extensions/v1beta1" ds.TypeMeta.Kind = "DaemonSet" + return r.marshalObject(ds) } @@ -78,6 +71,7 @@ func (*DaemonSet) Header(ns string) Row { } hh = append(hh, "NAME", "DESIRED", "CURRENT", "READY", "UP-TO-DATE") hh = append(hh, "AVAILABLE", "NODE_SELECTOR", "AGE") + return hh } @@ -89,6 +83,7 @@ func (r *DaemonSet) Fields(ns string) Row { if ns == AllNamespaces { ff = append(ff, i.Namespace) } + return append(ff, i.Name, strconv.Itoa(int(i.Status.DesiredNumberScheduled)), @@ -100,8 +95,3 @@ func (r *DaemonSet) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*DaemonSet) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/ds_test.go b/internal/resource/ds_test.go index e9dcc4c4..117ff5a4 100644 --- a/internal/resource/ds_test.go +++ b/internal/resource/ds_test.go @@ -62,7 +62,6 @@ func TestDSListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 8, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestDSListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestDSListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sDS(), nil) - l := resource.NewDaemonSetListWithArgs("blee", resource.NewDaemonSetWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sDS() *extv1beta1.DaemonSet { diff --git a/internal/resource/ep.go b/internal/resource/ep.go index 208e569f..ca3c7b3d 100644 --- a/internal/resource/ep.go +++ b/internal/resource/ep.go @@ -17,51 +17,43 @@ type Endpoints struct { } // NewEndpointsList returns a new resource list. -func NewEndpointsList(ns string) List { - return NewEndpointsListWithArgs(ns, NewEndpoints()) +func NewEndpointsList(c k8s.Connection, ns string) List { + return newList( + ns, + "ep", + NewEndpoints(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewEndpointsListWithArgs returns a new resource list. -func NewEndpointsListWithArgs(ns string, res Resource) List { - return newList(ns, "ep", res, AllVerbsAccess|DescribeAccess) -} +// NewEndpoints instantiates a new Endpoints. +func NewEndpoints(c k8s.Connection) *Endpoints { + ep := &Endpoints{&Base{connection: c, resource: k8s.NewEndpoints(c)}, nil} + ep.Factory = ep -// NewEndpoints instantiates a new Endpoint. -func NewEndpoints() *Endpoints { - return NewEndpointsWithArgs(k8s.NewEndpoints()) -} - -// NewEndpointsWithArgs instantiates a new Endpoint. -func NewEndpointsWithArgs(r k8s.Res) *Endpoints { - ep := &Endpoints{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep return ep } -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*Endpoints) NewInstance(i interface{}) Columnar { - cm := NewEndpoints() - switch i.(type) { +// New builds a new Endpoints instance from a k8s resource. +func (r *Endpoints) New(i interface{}) Columnar { + c := NewEndpoints(r.connection) + switch instance := i.(type) { case *v1.Endpoints: - cm.instance = i.(*v1.Endpoints) + c.instance = instance case v1.Endpoints: - ii := i.(v1.Endpoints) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Endpoints type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *Endpoints) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -69,6 +61,7 @@ func (r *Endpoints) Marshal(path string) (string, error) { ep := i.(*v1.Endpoints) ep.TypeMeta.APIVersion = "v1" ep.TypeMeta.Kind = "Endpoint" + return r.marshalObject(ep) } @@ -78,6 +71,7 @@ func (*Endpoints) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "ENDPOINTS", "AGE") } @@ -96,10 +90,8 @@ func (r *Endpoints) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (*Endpoints) ExtFields() Properties { - return Properties{} -} +// ---------------------------------------------------------------------------- +// Helpers... func (r *Endpoints) toEPs(ss []v1.EndpointSubset) string { aa := make([]string, 0, len(ss)) @@ -124,5 +116,6 @@ func (r *Endpoints) toEPs(ss []v1.EndpointSubset) string { } } } + return strings.Join(aa, ",") } diff --git a/internal/resource/ep_test.go b/internal/resource/ep_test.go index 561173ac..890bfc92 100644 --- a/internal/resource/ep_test.go +++ b/internal/resource/ep_test.go @@ -62,7 +62,6 @@ func TestEndpointsListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 3, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestEndpointsListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestEndpointsListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sEndpoints(), nil) - l := resource.NewEndpointsListWithArgs("blee", resource.NewEndpointsWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sEndpoints() *v1.Endpoints { diff --git a/internal/resource/evt.go b/internal/resource/evt.go index 59886901..b8307b95 100644 --- a/internal/resource/evt.go +++ b/internal/resource/evt.go @@ -16,51 +16,43 @@ type Event struct { } // NewEventList returns a new resource list. -func NewEventList(ns string) List { - return NewEventListWithArgs(ns, NewEvent()) +func NewEventList(c k8s.Connection, ns string) List { + return newList( + ns, + "ev", + NewEvent(c), + ListAccess+NamespaceAccess, + ) } -// NewEventListWithArgs returns a new resource list. -func NewEventListWithArgs(ns string, res Resource) List { - return newList(ns, "event", res, ListAccess+NamespaceAccess) +// NewEvent instantiates a new Event. +func NewEvent(c k8s.Connection) *Event { + ev := &Event{&Base{connection: c, resource: k8s.NewEvent(c)}, nil} + ev.Factory = ev + + return ev } -// NewEvent instantiates a new Endpoint. -func NewEvent() *Event { - return NewEventWithArgs(k8s.NewEvent()) -} - -// NewEventWithArgs instantiates a new Endpoint. -func NewEventWithArgs(r k8s.Res) *Event { - ep := &Event{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*Event) NewInstance(i interface{}) Columnar { - cm := NewEvent() - switch i.(type) { +// New builds a new Event instance from a k8s resource. +func (r *Event) New(i interface{}) Columnar { + c := NewEvent(r.connection) + switch instance := i.(type) { case *v1.Event: - cm.instance = i.(*v1.Event) + c.instance = instance case v1.Event: - ii := i.(v1.Event) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Event type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *Event) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -68,19 +60,10 @@ func (r *Event) Marshal(path string) (string, error) { ev := i.(*v1.Event) ev.TypeMeta.APIVersion = "v1" ev.TypeMeta.Kind = "Event" + return r.marshalObject(ev) } -// // Get resource given a namespaced name. -// func (r *Event) Get(path string) (Columnar, error) { -// ns, n := namespaced(path) -// i, err := r.caller.Get(ns, n) -// if err != nil { -// return nil, err -// } -// return r.NewInstance(i), nil -// } - // Delete a resource by name. func (r *Event) Delete(path string) error { return nil @@ -92,6 +75,7 @@ func (*Event) Header(ns string) Row { if ns == AllNamespaces { ff = append(ff, "NAMESPACE") } + return append(ff, "NAME", "REASON", "SOURCE", "COUNT", "MESSAGE", "AGE") } @@ -116,11 +100,7 @@ func (r *Event) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (*Event) ExtFields() Properties { - return Properties{} -} - +// ---------------------------------------------------------------------------- // Helpers... func (*Event) toEmoji(t, r string) string { diff --git a/internal/resource/evt_test.go b/internal/resource/evt_test.go index fbc795bc..53ffaadf 100644 --- a/internal/resource/evt_test.go +++ b/internal/resource/evt_test.go @@ -62,7 +62,6 @@ func TestEventListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 6, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestEventListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestEventListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sEvent(), nil) - l := resource.NewEventListWithArgs("blee", resource.NewEventWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sEvent() *v1.Event { diff --git a/internal/resource/helpers.go b/internal/resource/helpers.go index ec727d11..ea46cdd6 100644 --- a/internal/resource/helpers.go +++ b/internal/resource/helpers.go @@ -38,6 +38,7 @@ const ( func namespaced(n string) (string, string) { ns, po := path.Split(n) + return strings.Trim(ns, "/"), po } @@ -53,6 +54,7 @@ func check(s, sub string) string { if len(s) == 0 { return sub } + return s } @@ -73,12 +75,14 @@ func toAge(timestamp metav1.Time) string { if timestamp.IsZero() { return "" } + return duration.HumanDuration(time.Since(timestamp.Time)) } // Pad a string up to the given length. func Pad(s string, l int) string { fmat := "%-" + strconv.Itoa(l) + "s" + return fmt.Sprintf(fmat, s) } @@ -104,5 +108,16 @@ func mapToStr(m map[string]string) (s string) { s += "," } } + return } + +// ToMillicore shows cpu reading for human. +func ToMillicore(v int64) string { + return strconv.Itoa(int(v)) + "m" +} + +// ToMi shows mem reading for human. +func ToMi(v float64) string { + return strconv.Itoa(int(v)) + "Mi" +} diff --git a/internal/resource/helpers_test.go b/internal/resource/helpers_test.go index 39353353..f2fcc1b8 100644 --- a/internal/resource/helpers_test.go +++ b/internal/resource/helpers_test.go @@ -117,3 +117,33 @@ func BenchmarkMapToStr(b *testing.B) { mapToStr(ll) } } + +func TestToMillicore(t *testing.T) { + uu := []struct { + v int64 + e string + }{ + {0, "0m"}, + {2, "2m"}, + {1000, "1000m"}, + } + + for _, u := range uu { + assert.Equal(t, u.e, ToMillicore(u.v)) + } +} + +func TestToMi(t *testing.T) { + uu := []struct { + v float64 + e string + }{ + {0, "0Mi"}, + {2, "2Mi"}, + {1000, "1000Mi"}, + } + + for _, u := range uu { + assert.Equal(t, u.e, ToMi(u.v)) + } +} diff --git a/internal/resource/hpa.go b/internal/resource/hpa.go index be8cbc05..0e1c7bac 100644 --- a/internal/resource/hpa.go +++ b/internal/resource/hpa.go @@ -17,51 +17,43 @@ type HPA struct { } // NewHPAList returns a new resource list. -func NewHPAList(ns string) List { - return NewHPAListWithArgs(ns, NewHPA()) +func NewHPAList(c k8s.Connection, ns string) List { + return newList( + ns, + "hpa", + NewHPA(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewHPAListWithArgs returns a new resource list. -func NewHPAListWithArgs(ns string, res Resource) List { - return newList(ns, "hpa", res, AllVerbsAccess|DescribeAccess) +// NewHPA instantiates a new HPA. +func NewHPA(c k8s.Connection) *HPA { + hpa := &HPA{&Base{connection: c, resource: k8s.NewHPA(c)}, nil} + hpa.Factory = hpa + + return hpa } -// NewHPA instantiates a new Endpoint. -func NewHPA() *HPA { - return NewHPAWithArgs(k8s.NewHPA()) -} - -// NewHPAWithArgs instantiates a new Endpoint. -func NewHPAWithArgs(r k8s.Res) *HPA { - ep := &HPA{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*HPA) NewInstance(i interface{}) Columnar { - cm := NewHPA() - switch i.(type) { +// New builds a new HPA instance from a k8s resource. +func (r *HPA) New(i interface{}) Columnar { + c := NewHPA(r.connection) + switch instance := i.(type) { case *autoscalingv2beta2.HorizontalPodAutoscaler: - cm.instance = i.(*autoscalingv2beta2.HorizontalPodAutoscaler) + c.instance = instance case autoscalingv2beta2.HorizontalPodAutoscaler: - ii := i.(autoscalingv2beta2.HorizontalPodAutoscaler) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown HPA type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *HPA) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -69,6 +61,7 @@ func (r *HPA) Marshal(path string) (string, error) { hpa := i.(*autoscalingv2beta2.HorizontalPodAutoscaler) hpa.TypeMeta.APIVersion = "autoscaling/v2beta2" hpa.TypeMeta.Kind = "HorizontalPodAutoscaler" + return r.marshalObject(hpa) } @@ -78,6 +71,7 @@ func (*HPA) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "REFERENCE", @@ -108,60 +102,33 @@ func (r *HPA) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (*HPA) ExtFields() Properties { - return Properties{} -} +// ---------------------------------------------------------------------------- +// Helpers... func toMetrics(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { if len(specs) == 0 { return "" } + list, max, more, count := []string{}, 2, false, 0 for i, spec := range specs { + current := "" + switch spec.Type { case autoscalingv2beta2.ExternalMetricSourceType: - current := "" - if spec.External.Target.AverageValue != nil { - if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.Current.AverageValue != nil { - current = statuses[i].External.Current.AverageValue.String() - } - list = append(list, fmt.Sprintf("%s/%s (avg)", current, spec.External.Target.AverageValue.String())) - } else { - if len(statuses) > i && statuses[i].External != nil { - current = statuses[i].External.Current.Value.String() - } - list = append(list, fmt.Sprintf("%s/%s", current, spec.External.Target.Value.String())) - } + list = append(list, externalMetrics(i, spec, statuses)) case autoscalingv2beta2.PodsMetricSourceType: - current := "" if len(statuses) > i && statuses[i].Pods != nil { current = statuses[i].Pods.Current.AverageValue.String() } list = append(list, fmt.Sprintf("%s/%s", current, spec.Pods.Target.AverageValue.String())) case autoscalingv2beta2.ObjectMetricSourceType: - current := "" if len(statuses) > i && statuses[i].Object != nil { current = statuses[i].Object.Current.Value.String() } list = append(list, fmt.Sprintf("%s/%s", current, spec.Object.Target.Value.String())) case autoscalingv2beta2.ResourceMetricSourceType: - current := "" - if spec.Resource.Target.AverageValue != nil { - if len(statuses) > i && statuses[i].Resource != nil { - current = statuses[i].Resource.Current.AverageValue.String() - } - list = append(list, fmt.Sprintf("%s/%s", current, spec.Resource.Target.AverageValue.String())) - } else { - if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.Current.AverageUtilization != nil { - current = fmt.Sprintf("%d%%", *statuses[i].Resource.Current.AverageUtilization) - } - target := "" - if spec.Resource.Target.AverageUtilization != nil { - target = fmt.Sprintf("%d%%", *spec.Resource.Target.AverageUtilization) - } - list = append(list, fmt.Sprintf("%s/%s", current, target)) - } + list = append(list, resourceMetrics(i, spec, statuses)) default: list = append(list, "") } @@ -169,13 +136,50 @@ func toMetrics(specs []autoscalingv2beta2.MetricSpec, statuses []autoscalingv2be } if count > max { - list = list[:max] - more = true + list, more = list[:max], true } ret := strings.Join(list, ", ") if more { return fmt.Sprintf("%s + %d more...", ret, count-max) } + return ret } + +func externalMetrics(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { + current := "" + + if spec.External.Target.AverageValue != nil { + if len(statuses) > i && statuses[i].External != nil && &statuses[i].External.Current.AverageValue != nil { + current = statuses[i].External.Current.AverageValue.String() + } + return fmt.Sprintf("%s/%s (avg)", current, spec.External.Target.AverageValue.String()) + } + if len(statuses) > i && statuses[i].External != nil { + current = statuses[i].External.Current.Value.String() + } + + return fmt.Sprintf("%s/%s", current, spec.External.Target.Value.String()) +} + +func resourceMetrics(i int, spec autoscalingv2beta2.MetricSpec, statuses []autoscalingv2beta2.MetricStatus) string { + current := "" + + if spec.Resource.Target.AverageValue != nil { + if len(statuses) > i && statuses[i].Resource != nil { + current = statuses[i].Resource.Current.AverageValue.String() + } + return fmt.Sprintf("%s/%s", current, spec.Resource.Target.AverageValue.String()) + } + + if len(statuses) > i && statuses[i].Resource != nil && statuses[i].Resource.Current.AverageUtilization != nil { + current = fmt.Sprintf("%d%%", *statuses[i].Resource.Current.AverageUtilization) + } + + target := "" + if spec.Resource.Target.AverageUtilization != nil { + target = fmt.Sprintf("%d%%", *spec.Resource.Target.AverageUtilization) + } + return fmt.Sprintf("%s/%s", current, target) +} diff --git a/internal/resource/hpa_test.go b/internal/resource/hpa_test.go index c4817c70..7d3ea2bf 100644 --- a/internal/resource/hpa_test.go +++ b/internal/resource/hpa_test.go @@ -65,7 +65,6 @@ func TestHPAListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 7, len(row.Deltas)) for _, d := range row.Deltas { @@ -74,19 +73,6 @@ func TestHPAListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestHPAListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sHPA(), nil) - l := resource.NewHPAListWithArgs("blee", resource.NewHPAWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sHPA() *autoscalingv2beta2.HorizontalPodAutoscaler { diff --git a/internal/resource/ing.go b/internal/resource/ing.go index 0dbfa860..3ab020b8 100644 --- a/internal/resource/ing.go +++ b/internal/resource/ing.go @@ -16,51 +16,43 @@ type Ingress struct { } // NewIngressList returns a new resource list. -func NewIngressList(ns string) List { - return NewIngressListWithArgs(ns, NewIngress()) +func NewIngressList(c k8s.Connection, ns string) List { + return newList( + ns, + "ing", + NewIngress(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewIngressListWithArgs returns a new resource list. -func NewIngressListWithArgs(ns string, res Resource) List { - return newList(ns, "ing", res, AllVerbsAccess|DescribeAccess) +// NewIngress instantiates a new Ingress. +func NewIngress(c k8s.Connection) *Ingress { + ing := &Ingress{&Base{connection: c, resource: k8s.NewIngress(c)}, nil} + ing.Factory = ing + + return ing } -// NewIngress instantiates a new Endpoint. -func NewIngress() *Ingress { - return NewIngressWithArgs(k8s.NewIngress()) -} - -// NewIngressWithArgs instantiates a new Endpoint. -func NewIngressWithArgs(r k8s.Res) *Ingress { - ep := &Ingress{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*Ingress) NewInstance(i interface{}) Columnar { - cm := NewIngress() - switch i.(type) { +// New builds a new Ingress instance from a k8s resource. +func (r *Ingress) New(i interface{}) Columnar { + c := NewIngress(r.connection) + switch instance := i.(type) { case *v1beta1.Ingress: - cm.instance = i.(*v1beta1.Ingress) + c.instance = instance case v1beta1.Ingress: - ii := i.(v1beta1.Ingress) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Ingress type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *Ingress) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -68,6 +60,7 @@ func (r *Ingress) Marshal(path string) (string, error) { ing := i.(*v1beta1.Ingress) ing.TypeMeta.APIVersion = "extensions/v1beta1" ing.TypeMeta.Kind = "Ingress" + return r.marshalObject(ing) } @@ -77,6 +70,7 @@ func (*Ingress) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "HOSTS", "ADDRESS", "PORT", "AGE") } @@ -98,11 +92,7 @@ func (r *Ingress) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (*Ingress) ExtFields() Properties { - return Properties{} -} - +// ---------------------------------------------------------------------------- // Helpers... func (*Ingress) toAddress(lbs v1.LoadBalancerStatus) string { @@ -115,6 +105,7 @@ func (*Ingress) toAddress(lbs v1.LoadBalancerStatus) string { res = append(res, lb.Hostname) } } + return strings.Join(res, ",") } @@ -122,6 +113,7 @@ func (*Ingress) toPorts(tls []v1beta1.IngressTLS) string { if len(tls) != 0 { return "80, 443" } + return "80" } @@ -135,5 +127,6 @@ func (*Ingress) toHosts(rr []v1beta1.IngressRule) string { } i++ } + return s } diff --git a/internal/resource/ing_test.go b/internal/resource/ing_test.go index da40f79c..76a6ae02 100644 --- a/internal/resource/ing_test.go +++ b/internal/resource/ing_test.go @@ -62,7 +62,6 @@ func TestIngressListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 5, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestIngressListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestIngressListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sIngress(), nil) - l := resource.NewIngressListWithArgs("blee", resource.NewIngressWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sIngress() *v1beta1.Ingress { diff --git a/internal/resource/job.go b/internal/resource/job.go index b9f199f1..65f9cb48 100644 --- a/internal/resource/job.go +++ b/internal/resource/job.go @@ -19,51 +19,43 @@ type Job struct { } // NewJobList returns a new resource list. -func NewJobList(ns string) List { - return NewJobListWithArgs(ns, NewJob()) -} - -// NewJobListWithArgs returns a new resource list. -func NewJobListWithArgs(ns string, res Resource) List { - return newList(ns, "job", res, AllVerbsAccess|DescribeAccess) +func NewJobList(c k8s.Connection, ns string) List { + return newList( + ns, + "job", + NewJob(c), + AllVerbsAccess|DescribeAccess, + ) } // NewJob instantiates a new Job. -func NewJob() *Job { - return NewJobWithArgs(k8s.NewJob()) +func NewJob(c k8s.Connection) *Job { + j := &Job{&Base{connection: c, resource: k8s.NewJob(c)}, nil} + j.Factory = j + + return j } -// NewJobWithArgs instantiates a new Job. -func NewJobWithArgs(r k8s.Res) *Job { - cm := &Job{ - Base: &Base{ - caller: r, - }, - } - cm.creator = cm - return cm -} - -// NewInstance builds a new Job instance from a k8s resource. -func (*Job) NewInstance(i interface{}) Columnar { - job := NewJob() - switch i.(type) { +// New builds a new Job instance from a k8s resource. +func (r *Job) New(i interface{}) Columnar { + c := NewJob(r.connection) + switch instance := i.(type) { case *v1.Job: - job.instance = i.(*v1.Job) + c.instance = instance case v1.Job: - ii := i.(v1.Job) - job.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Job type %#v", i) } - job.path = job.namespacedName(job.instance.ObjectMeta) - return job + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *Job) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -71,18 +63,20 @@ func (r *Job) Marshal(path string) (string, error) { jo := i.(*v1.Job) jo.TypeMeta.APIVersion = "extensions/v1beta1" jo.TypeMeta.Kind = "Job" + return r.marshalObject(jo) } // Containers fetch all the containers on this job, may include init containers. func (r *Job) Containers(path string, includeInit bool) ([]string, error) { ns, n := namespaced(path) - return r.caller.(k8s.Loggable).Containers(ns, n, includeInit) + + return r.resource.(k8s.Loggable).Containers(ns, n, includeInit) } // Logs retrieves logs for a given container. func (r *Job) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (context.CancelFunc, error) { - req := r.caller.(k8s.Loggable).Logs(ns, n, co, lines, prev) + req := r.resource.(k8s.Loggable).Logs(ns, n, co, lines, prev) ctx, cancel := context.WithCancel(context.TODO()) req.Context(ctx) @@ -116,6 +110,7 @@ func (r *Job) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (c c <- scanner.Text() } }() + return cancel, nil } @@ -125,6 +120,7 @@ func (*Job) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "COMPLETIONS", "DURATION", "AGE") } @@ -136,6 +132,7 @@ func (r *Job) Fields(ns string) Row { if ns == AllNamespaces { ff = append(ff, i.Namespace) } + return append(ff, i.Name, r.toCompletion(i.Spec, i.Status), @@ -144,11 +141,7 @@ func (r *Job) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (*Job) ExtFields() Properties { - return Properties{} -} - +// ---------------------------------------------------------------------------- // Helpers... func (*Job) toCompletion(spec v1.JobSpec, status v1.JobStatus) (s string) { @@ -162,6 +155,7 @@ func (*Job) toCompletion(spec v1.JobSpec, status v1.JobStatus) (s string) { if parallelism > 1 { return fmt.Sprintf("%d/1 of %d", status.Succeeded, parallelism) } + return fmt.Sprintf("%d/1", status.Succeeded) } @@ -171,5 +165,6 @@ func (*Job) toDuration(status v1.JobStatus) string { case status.CompletionTime == nil: return duration.HumanDuration(time.Since(status.StartTime.Time)) } + return duration.HumanDuration(status.CompletionTime.Sub(status.StartTime.Time)) } diff --git a/internal/resource/job_test.go b/internal/resource/job_test.go index 54989f29..bc662132 100644 --- a/internal/resource/job_test.go +++ b/internal/resource/job_test.go @@ -62,7 +62,6 @@ func TestJobListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 4, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestJobListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestJobListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sJob(), nil) - l := resource.NewJobListWithArgs("blee", resource.NewJobWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sJob() *v1.Job { diff --git a/internal/resource/list.go b/internal/resource/list.go index c67746ae..e69e5ca0 100644 --- a/internal/resource/list.go +++ b/internal/resource/list.go @@ -3,8 +3,8 @@ package resource import ( "reflect" - "github.com/derailed/k9s/internal/k8s" "k8s.io/apimachinery/pkg/watch" + "k8s.io/cli-runtime/pkg/genericclioptions" ) const ( @@ -63,10 +63,8 @@ type ( GetNamespace() string SetNamespace(string) Reconcile() error - Describe(pa string) (Properties, error) GetName() string Access(flag int) bool - HasXRay() bool } // Columnar tracks resources that can be diplayed in a tabular fashion. @@ -86,20 +84,13 @@ type ( // Columnars a collection of columnars. Columnars []Columnar - // MxColumnar tracks resource metrics. - MxColumnar interface { - Columnar - Metrics() k8s.Metric - SetMetrics(k8s.Metric) - } - // Resource tracks generic Kubernetes resources. Resource interface { - NewInstance(interface{}) Columnar + New(interface{}) Columnar Get(path string) (Columnar, error) List(ns string) (Columnars, error) Delete(path string) error - Describe(kind, pa string) (string, error) + Describe(kind, pa string, flags *genericclioptions.ConfigFlags) (string, error) Marshal(pa string) (string, error) Header(ns string) Row } @@ -107,8 +98,7 @@ type ( list struct { namespace, name string verbs int - xray bool - api Resource + resource Resource cache RowEvents } ) @@ -117,20 +107,16 @@ func newRowEvent(a watch.EventType, f, d Row) *RowEvent { return &RowEvent{Action: a, Fields: f, Deltas: d} } -func newList(ns, name string, api Resource, v int) *list { +func newList(ns, name string, res Resource, verbs int) *list { return &list{ namespace: ns, name: name, - verbs: v, - api: api, + verbs: verbs, + resource: res, cache: RowEvents{}, } } -func (l *list) HasXRay() bool { - return l.xray -} - // Access check access control on a given resource. func (l *list) Access(f int) bool { return l.verbs&f == f @@ -151,6 +137,7 @@ func (l *list) GetNamespace() string { if !l.Access(NamespaceAccess) { l.namespace = NotNamespaced } + return l.namespace } @@ -179,28 +166,18 @@ func (l *list) GetName() string { // Resource returns a resource api connection. func (l *list) Resource() Resource { - return l.api + return l.resource } // Cache tracks previous resource state. func (l *list) Data() TableData { return TableData{ - Header: l.api.Header(l.namespace), + Header: l.resource.Header(l.namespace), Rows: l.cache, Namespace: l.namespace, } } -func (l *list) Describe(pa string) (Properties, error) { - var p Properties - i, err := l.api.Get(pa) - if err != nil { - return p, err - } - - return i.ExtFields(), nil -} - // Reconcile previous vs current state and emits delta events. func (l *list) Reconcile() error { var ( @@ -208,7 +185,7 @@ func (l *list) Reconcile() error { err error ) - if items, err = l.api.List(l.namespace); err != nil { + if items, err = l.resource.List(l.namespace); err != nil { return err } @@ -254,5 +231,6 @@ func (l *list) Reconcile() error { delete(l.cache, k) } } + return nil } diff --git a/internal/resource/mock_clusterifc_test.go b/internal/resource/mock_clusterifc_test.go deleted file mode 100644 index 88ac1f26..00000000 --- a/internal/resource/mock_clusterifc_test.go +++ /dev/null @@ -1,187 +0,0 @@ -// Code generated by pegomock. DO NOT EDIT. -// Source: github.com/derailed/k9s/internal/resource (interfaces: ClusterIfc) - -package resource_test - -import ( - pegomock "github.com/petergtz/pegomock" - "reflect" - "time" -) - -type MockClusterIfc struct { - fail func(message string, callerSkip ...int) -} - -func NewMockClusterIfc() *MockClusterIfc { - return &MockClusterIfc{fail: pegomock.GlobalFailHandler} -} - -func (mock *MockClusterIfc) ClusterName() string { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterIfc().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterName", 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 *MockClusterIfc) ContextName() string { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterIfc().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("ContextName", 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 *MockClusterIfc) UserName() string { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterIfc().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("UserName", 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 *MockClusterIfc) Version() (string, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockClusterIfc().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("Version", 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 *MockClusterIfc) VerifyWasCalledOnce() *VerifierClusterIfc { - return &VerifierClusterIfc{ - mock: mock, - invocationCountMatcher: pegomock.Times(1), - } -} - -func (mock *MockClusterIfc) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierClusterIfc { - return &VerifierClusterIfc{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - } -} - -func (mock *MockClusterIfc) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierClusterIfc { - return &VerifierClusterIfc{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - inOrderContext: inOrderContext, - } -} - -func (mock *MockClusterIfc) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierClusterIfc { - return &VerifierClusterIfc{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - timeout: timeout, - } -} - -type VerifierClusterIfc struct { - mock *MockClusterIfc - invocationCountMatcher pegomock.Matcher - inOrderContext *pegomock.InOrderContext - timeout time.Duration -} - -func (verifier *VerifierClusterIfc) ClusterName() *ClusterIfc_ClusterName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterName", params, verifier.timeout) - return &ClusterIfc_ClusterName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type ClusterIfc_ClusterName_OngoingVerification struct { - mock *MockClusterIfc - methodInvocations []pegomock.MethodInvocation -} - -func (c *ClusterIfc_ClusterName_OngoingVerification) GetCapturedArguments() { -} - -func (c *ClusterIfc_ClusterName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierClusterIfc) ContextName() *ClusterIfc_ContextName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ContextName", params, verifier.timeout) - return &ClusterIfc_ContextName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type ClusterIfc_ContextName_OngoingVerification struct { - mock *MockClusterIfc - methodInvocations []pegomock.MethodInvocation -} - -func (c *ClusterIfc_ContextName_OngoingVerification) GetCapturedArguments() { -} - -func (c *ClusterIfc_ContextName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierClusterIfc) UserName() *ClusterIfc_UserName_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UserName", params, verifier.timeout) - return &ClusterIfc_UserName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type ClusterIfc_UserName_OngoingVerification struct { - mock *MockClusterIfc - methodInvocations []pegomock.MethodInvocation -} - -func (c *ClusterIfc_UserName_OngoingVerification) GetCapturedArguments() { -} - -func (c *ClusterIfc_UserName_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierClusterIfc) Version() *ClusterIfc_Version_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout) - return &ClusterIfc_Version_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type ClusterIfc_Version_OngoingVerification struct { - mock *MockClusterIfc - methodInvocations []pegomock.MethodInvocation -} - -func (c *ClusterIfc_Version_OngoingVerification) GetCapturedArguments() { -} - -func (c *ClusterIfc_Version_OngoingVerification) GetAllCapturedArguments() { -} diff --git a/internal/resource/mock_clustermeta_test.go b/internal/resource/mock_clustermeta_test.go new file mode 100644 index 00000000..a6ed4259 --- /dev/null +++ b/internal/resource/mock_clustermeta_test.go @@ -0,0 +1,455 @@ +// Code generated by pegomock. DO NOT EDIT. +// Source: github.com/derailed/k9s/internal/resource (interfaces: ClusterMeta) + +package resource_test + +import ( + k8s "github.com/derailed/k9s/internal/k8s" + pegomock "github.com/petergtz/pegomock" + dynamic "k8s.io/client-go/dynamic" + kubernetes "k8s.io/client-go/kubernetes" + rest "k8s.io/client-go/rest" + versioned "k8s.io/metrics/pkg/client/clientset/versioned" + "reflect" + "time" +) + +type MockClusterMeta struct { + fail func(message string, callerSkip ...int) +} + +func NewMockClusterMeta() *MockClusterMeta { + return &MockClusterMeta{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockClusterMeta) ClusterName() string { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterName", 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 *MockClusterMeta) Config() *k8s.Config { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**k8s.Config)(nil)).Elem()}) + var ret0 *k8s.Config + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*k8s.Config) + } + } + return ret0 +} + +func (mock *MockClusterMeta) ContextName() string { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("ContextName", 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 *MockClusterMeta) DialOrDie() kubernetes.Interface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("DialOrDie", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem()}) + var ret0 kubernetes.Interface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(kubernetes.Interface) + } + } + return ret0 +} + +func (mock *MockClusterMeta) DynDialOrDie() dynamic.Interface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("DynDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.Interface)(nil)).Elem()}) + var ret0 dynamic.Interface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(dynamic.Interface) + } + } + return ret0 +} + +func (mock *MockClusterMeta) HasMetrics() bool { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) + var ret0 bool + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + } + return ret0 +} + +func (mock *MockClusterMeta) MXDial() (*versioned.Clientset, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("MXDial", params, []reflect.Type{reflect.TypeOf((**versioned.Clientset)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 *versioned.Clientset + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*versioned.Clientset) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockClusterMeta) NSDialOrDie() dynamic.NamespaceableResourceInterface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("NSDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.NamespaceableResourceInterface)(nil)).Elem()}) + var ret0 dynamic.NamespaceableResourceInterface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(dynamic.NamespaceableResourceInterface) + } + } + return ret0 +} + +func (mock *MockClusterMeta) RestConfigOrDie() *rest.Config { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("RestConfigOrDie", params, []reflect.Type{reflect.TypeOf((**rest.Config)(nil)).Elem()}) + var ret0 *rest.Config + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*rest.Config) + } + } + return ret0 +} + +func (mock *MockClusterMeta) SwitchContextOrDie(_param0 string) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{_param0} + pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{}) +} + +func (mock *MockClusterMeta) UserName() string { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("UserName", 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 *MockClusterMeta) Version() (string, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockClusterMeta().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("Version", params, []reflect.Type{reflect.TypeOf((*string)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 string + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(string) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockClusterMeta) VerifyWasCalledOnce() *VerifierClusterMeta { + return &VerifierClusterMeta{ + mock: mock, + invocationCountMatcher: pegomock.Times(1), + } +} + +func (mock *MockClusterMeta) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierClusterMeta { + return &VerifierClusterMeta{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + } +} + +func (mock *MockClusterMeta) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierClusterMeta { + return &VerifierClusterMeta{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + inOrderContext: inOrderContext, + } +} + +func (mock *MockClusterMeta) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierClusterMeta { + return &VerifierClusterMeta{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + timeout: timeout, + } +} + +type VerifierClusterMeta struct { + mock *MockClusterMeta + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext + timeout time.Duration +} + +func (verifier *VerifierClusterMeta) ClusterName() *ClusterMeta_ClusterName_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterName", params, verifier.timeout) + return &ClusterMeta_ClusterName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_ClusterName_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_ClusterName_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_ClusterName_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) Config() *ClusterMeta_Config_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout) + return &ClusterMeta_Config_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_Config_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_Config_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_Config_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) ContextName() *ClusterMeta_ContextName_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ContextName", params, verifier.timeout) + return &ClusterMeta_ContextName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_ContextName_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_ContextName_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_ContextName_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) DialOrDie() *ClusterMeta_DialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout) + return &ClusterMeta_DialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_DialOrDie_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_DialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_DialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) DynDialOrDie() *ClusterMeta_DynDialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DynDialOrDie", params, verifier.timeout) + return &ClusterMeta_DynDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_DynDialOrDie_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_DynDialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) HasMetrics() *ClusterMeta_HasMetrics_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) + return &ClusterMeta_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_HasMetrics_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_HasMetrics_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_HasMetrics_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) MXDial() *ClusterMeta_MXDial_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "MXDial", params, verifier.timeout) + return &ClusterMeta_MXDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_MXDial_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_MXDial_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_MXDial_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) NSDialOrDie() *ClusterMeta_NSDialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NSDialOrDie", params, verifier.timeout) + return &ClusterMeta_NSDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_NSDialOrDie_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_NSDialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_NSDialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) RestConfigOrDie() *ClusterMeta_RestConfigOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RestConfigOrDie", params, verifier.timeout) + return &ClusterMeta_RestConfigOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_RestConfigOrDie_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_RestConfigOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_RestConfigOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) SwitchContextOrDie(_param0 string) *ClusterMeta_SwitchContextOrDie_OngoingVerification { + params := []pegomock.Param{_param0} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SwitchContextOrDie", params, verifier.timeout) + return &ClusterMeta_SwitchContextOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_SwitchContextOrDie_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_SwitchContextOrDie_OngoingVerification) GetCapturedArguments() string { + _param0 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1] +} + +func (c *ClusterMeta_SwitchContextOrDie_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + } + return +} + +func (verifier *VerifierClusterMeta) UserName() *ClusterMeta_UserName_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "UserName", params, verifier.timeout) + return &ClusterMeta_UserName_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_UserName_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_UserName_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_UserName_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierClusterMeta) Version() *ClusterMeta_Version_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Version", params, verifier.timeout) + return &ClusterMeta_Version_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type ClusterMeta_Version_OngoingVerification struct { + mock *MockClusterMeta + methodInvocations []pegomock.MethodInvocation +} + +func (c *ClusterMeta_Version_OngoingVerification) GetCapturedArguments() { +} + +func (c *ClusterMeta_Version_OngoingVerification) GetAllCapturedArguments() { +} diff --git a/internal/resource/mock_connection_test.go b/internal/resource/mock_connection_test.go new file mode 100644 index 00000000..ee870740 --- /dev/null +++ b/internal/resource/mock_connection_test.go @@ -0,0 +1,323 @@ +// Code generated by pegomock. DO NOT EDIT. +// Source: github.com/derailed/k9s/internal/resource (interfaces: Connection) + +package resource_test + +import ( + k8s "github.com/derailed/k9s/internal/k8s" + pegomock "github.com/petergtz/pegomock" + dynamic "k8s.io/client-go/dynamic" + kubernetes "k8s.io/client-go/kubernetes" + rest "k8s.io/client-go/rest" + versioned "k8s.io/metrics/pkg/client/clientset/versioned" + "reflect" + "time" +) + +type MockConnection struct { + fail func(message string, callerSkip ...int) +} + +func NewMockConnection() *MockConnection { + return &MockConnection{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockConnection) Config() *k8s.Config { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("Config", params, []reflect.Type{reflect.TypeOf((**k8s.Config)(nil)).Elem()}) + var ret0 *k8s.Config + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*k8s.Config) + } + } + return ret0 +} + +func (mock *MockConnection) DialOrDie() kubernetes.Interface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("DialOrDie", params, []reflect.Type{reflect.TypeOf((*kubernetes.Interface)(nil)).Elem()}) + var ret0 kubernetes.Interface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(kubernetes.Interface) + } + } + return ret0 +} + +func (mock *MockConnection) DynDialOrDie() dynamic.Interface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("DynDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.Interface)(nil)).Elem()}) + var ret0 dynamic.Interface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(dynamic.Interface) + } + } + return ret0 +} + +func (mock *MockConnection) HasMetrics() bool { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) + var ret0 bool + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + } + return ret0 +} + +func (mock *MockConnection) MXDial() (*versioned.Clientset, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("MXDial", params, []reflect.Type{reflect.TypeOf((**versioned.Clientset)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 *versioned.Clientset + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*versioned.Clientset) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockConnection) NSDialOrDie() dynamic.NamespaceableResourceInterface { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("NSDialOrDie", params, []reflect.Type{reflect.TypeOf((*dynamic.NamespaceableResourceInterface)(nil)).Elem()}) + var ret0 dynamic.NamespaceableResourceInterface + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(dynamic.NamespaceableResourceInterface) + } + } + return ret0 +} + +func (mock *MockConnection) RestConfigOrDie() *rest.Config { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("RestConfigOrDie", params, []reflect.Type{reflect.TypeOf((**rest.Config)(nil)).Elem()}) + var ret0 *rest.Config + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(*rest.Config) + } + } + return ret0 +} + +func (mock *MockConnection) SwitchContextOrDie(_param0 string) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockConnection().") + } + params := []pegomock.Param{_param0} + pegomock.GetGenericMockFrom(mock).Invoke("SwitchContextOrDie", params, []reflect.Type{}) +} + +func (mock *MockConnection) VerifyWasCalledOnce() *VerifierConnection { + return &VerifierConnection{ + mock: mock, + invocationCountMatcher: pegomock.Times(1), + } +} + +func (mock *MockConnection) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierConnection { + return &VerifierConnection{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + } +} + +func (mock *MockConnection) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierConnection { + return &VerifierConnection{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + inOrderContext: inOrderContext, + } +} + +func (mock *MockConnection) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierConnection { + return &VerifierConnection{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + timeout: timeout, + } +} + +type VerifierConnection struct { + mock *MockConnection + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext + timeout time.Duration +} + +func (verifier *VerifierConnection) Config() *Connection_Config_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Config", params, verifier.timeout) + return &Connection_Config_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_Config_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_Config_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_Config_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) DialOrDie() *Connection_DialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DialOrDie", params, verifier.timeout) + return &Connection_DialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_DialOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_DialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_DialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) DynDialOrDie() *Connection_DynDialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "DynDialOrDie", params, verifier.timeout) + return &Connection_DynDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_DynDialOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_DynDialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_DynDialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) HasMetrics() *Connection_HasMetrics_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) + return &Connection_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_HasMetrics_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_HasMetrics_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_HasMetrics_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) MXDial() *Connection_MXDial_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "MXDial", params, verifier.timeout) + return &Connection_MXDial_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_MXDial_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_MXDial_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_MXDial_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) NSDialOrDie() *Connection_NSDialOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NSDialOrDie", params, verifier.timeout) + return &Connection_NSDialOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_NSDialOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_NSDialOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_NSDialOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) RestConfigOrDie() *Connection_RestConfigOrDie_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "RestConfigOrDie", params, verifier.timeout) + return &Connection_RestConfigOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_RestConfigOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_RestConfigOrDie_OngoingVerification) GetCapturedArguments() { +} + +func (c *Connection_RestConfigOrDie_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierConnection) SwitchContextOrDie(_param0 string) *Connection_SwitchContextOrDie_OngoingVerification { + params := []pegomock.Param{_param0} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "SwitchContextOrDie", params, verifier.timeout) + return &Connection_SwitchContextOrDie_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type Connection_SwitchContextOrDie_OngoingVerification struct { + mock *MockConnection + methodInvocations []pegomock.MethodInvocation +} + +func (c *Connection_SwitchContextOrDie_OngoingVerification) GetCapturedArguments() string { + _param0 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1] +} + +func (c *Connection_SwitchContextOrDie_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + } + return +} diff --git a/internal/resource/mock_caller_test.go b/internal/resource/mock_cruder_test.go similarity index 67% rename from internal/resource/mock_caller_test.go rename to internal/resource/mock_cruder_test.go index 9cea7321..fffbed9f 100644 --- a/internal/resource/mock_caller_test.go +++ b/internal/resource/mock_cruder_test.go @@ -1,5 +1,5 @@ // Code generated by pegomock. DO NOT EDIT. -// Source: github.com/derailed/k9s/internal/resource (interfaces: Caller) +// Source: github.com/derailed/k9s/internal/resource (interfaces: Cruder) package resource_test @@ -10,17 +10,17 @@ import ( "time" ) -type MockCaller struct { +type MockCruder struct { fail func(message string, callerSkip ...int) } -func NewMockCaller() *MockCaller { - return &MockCaller{fail: pegomock.GlobalFailHandler} +func NewMockCruder() *MockCruder { + return &MockCruder{fail: pegomock.GlobalFailHandler} } -func (mock *MockCaller) Delete(_param0 string, _param1 string) error { +func (mock *MockCruder) Delete(_param0 string, _param1 string) error { if mock == nil { - panic("mock must not be nil. Use myMock := NewMockCaller().") + panic("mock must not be nil. Use myMock := NewMockCruder().") } params := []pegomock.Param{_param0, _param1} result := pegomock.GetGenericMockFrom(mock).Invoke("Delete", params, []reflect.Type{reflect.TypeOf((*error)(nil)).Elem()}) @@ -33,9 +33,9 @@ func (mock *MockCaller) Delete(_param0 string, _param1 string) error { return ret0 } -func (mock *MockCaller) Get(_param0 string, _param1 string) (interface{}, error) { +func (mock *MockCruder) Get(_param0 string, _param1 string) (interface{}, error) { if mock == nil { - panic("mock must not be nil. Use myMock := NewMockCaller().") + panic("mock must not be nil. Use myMock := NewMockCruder().") } params := []pegomock.Param{_param0, _param1} result := pegomock.GetGenericMockFrom(mock).Invoke("Get", params, []reflect.Type{reflect.TypeOf((*interface{})(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) @@ -52,9 +52,9 @@ func (mock *MockCaller) Get(_param0 string, _param1 string) (interface{}, error) return ret0, ret1 } -func (mock *MockCaller) List(_param0 string) (k8s.Collection, error) { +func (mock *MockCruder) List(_param0 string) (k8s.Collection, error) { if mock == nil { - panic("mock must not be nil. Use myMock := NewMockCaller().") + panic("mock must not be nil. Use myMock := NewMockCruder().") } params := []pegomock.Param{_param0} result := pegomock.GetGenericMockFrom(mock).Invoke("List", params, []reflect.Type{reflect.TypeOf((*k8s.Collection)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) @@ -71,60 +71,60 @@ func (mock *MockCaller) List(_param0 string) (k8s.Collection, error) { return ret0, ret1 } -func (mock *MockCaller) VerifyWasCalledOnce() *VerifierCaller { - return &VerifierCaller{ +func (mock *MockCruder) VerifyWasCalledOnce() *VerifierCruder { + return &VerifierCruder{ mock: mock, invocationCountMatcher: pegomock.Times(1), } } -func (mock *MockCaller) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierCaller { - return &VerifierCaller{ +func (mock *MockCruder) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierCruder { + return &VerifierCruder{ mock: mock, invocationCountMatcher: invocationCountMatcher, } } -func (mock *MockCaller) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierCaller { - return &VerifierCaller{ +func (mock *MockCruder) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierCruder { + return &VerifierCruder{ mock: mock, invocationCountMatcher: invocationCountMatcher, inOrderContext: inOrderContext, } } -func (mock *MockCaller) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierCaller { - return &VerifierCaller{ +func (mock *MockCruder) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierCruder { + return &VerifierCruder{ mock: mock, invocationCountMatcher: invocationCountMatcher, timeout: timeout, } } -type VerifierCaller struct { - mock *MockCaller +type VerifierCruder struct { + mock *MockCruder invocationCountMatcher pegomock.Matcher inOrderContext *pegomock.InOrderContext timeout time.Duration } -func (verifier *VerifierCaller) Delete(_param0 string, _param1 string) *Caller_Delete_OngoingVerification { +func (verifier *VerifierCruder) Delete(_param0 string, _param1 string) *Cruder_Delete_OngoingVerification { params := []pegomock.Param{_param0, _param1} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Delete", params, verifier.timeout) - return &Caller_Delete_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} + return &Cruder_Delete_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type Caller_Delete_OngoingVerification struct { - mock *MockCaller +type Cruder_Delete_OngoingVerification struct { + mock *MockCruder methodInvocations []pegomock.MethodInvocation } -func (c *Caller_Delete_OngoingVerification) GetCapturedArguments() (string, string) { +func (c *Cruder_Delete_OngoingVerification) GetCapturedArguments() (string, string) { _param0, _param1 := c.GetAllCapturedArguments() return _param0[len(_param0)-1], _param1[len(_param1)-1] } -func (c *Caller_Delete_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) { +func (c *Cruder_Delete_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) { params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(params) > 0 { _param0 = make([]string, len(params[0])) @@ -139,23 +139,23 @@ func (c *Caller_Delete_OngoingVerification) GetAllCapturedArguments() (_param0 [ return } -func (verifier *VerifierCaller) Get(_param0 string, _param1 string) *Caller_Get_OngoingVerification { +func (verifier *VerifierCruder) Get(_param0 string, _param1 string) *Cruder_Get_OngoingVerification { params := []pegomock.Param{_param0, _param1} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "Get", params, verifier.timeout) - return &Caller_Get_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} + return &Cruder_Get_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type Caller_Get_OngoingVerification struct { - mock *MockCaller +type Cruder_Get_OngoingVerification struct { + mock *MockCruder methodInvocations []pegomock.MethodInvocation } -func (c *Caller_Get_OngoingVerification) GetCapturedArguments() (string, string) { +func (c *Cruder_Get_OngoingVerification) GetCapturedArguments() (string, string) { _param0, _param1 := c.GetAllCapturedArguments() return _param0[len(_param0)-1], _param1[len(_param1)-1] } -func (c *Caller_Get_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) { +func (c *Cruder_Get_OngoingVerification) GetAllCapturedArguments() (_param0 []string, _param1 []string) { params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(params) > 0 { _param0 = make([]string, len(params[0])) @@ -170,23 +170,23 @@ func (c *Caller_Get_OngoingVerification) GetAllCapturedArguments() (_param0 []st return } -func (verifier *VerifierCaller) List(_param0 string) *Caller_List_OngoingVerification { +func (verifier *VerifierCruder) List(_param0 string) *Cruder_List_OngoingVerification { params := []pegomock.Param{_param0} methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "List", params, verifier.timeout) - return &Caller_List_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} + return &Cruder_List_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} } -type Caller_List_OngoingVerification struct { - mock *MockCaller +type Cruder_List_OngoingVerification struct { + mock *MockCruder methodInvocations []pegomock.MethodInvocation } -func (c *Caller_List_OngoingVerification) GetCapturedArguments() string { +func (c *Cruder_List_OngoingVerification) GetCapturedArguments() string { _param0 := c.GetAllCapturedArguments() return _param0[len(_param0)-1] } -func (c *Caller_List_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { +func (c *Cruder_List_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) if len(params) > 0 { _param0 = make([]string, len(params[0])) diff --git a/internal/resource/mock_metricsifc_test.go b/internal/resource/mock_metricsifc_test.go deleted file mode 100644 index 666ba4b4..00000000 --- a/internal/resource/mock_metricsifc_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Code generated by pegomock. DO NOT EDIT. -// Source: github.com/derailed/k9s/internal/resource (interfaces: MetricsIfc) - -package resource_test - -import ( - k8s "github.com/derailed/k9s/internal/k8s" - pegomock "github.com/petergtz/pegomock" - v1 "k8s.io/api/core/v1" - "reflect" - "time" -) - -type MockMetricsIfc struct { - fail func(message string, callerSkip ...int) -} - -func NewMockMetricsIfc() *MockMetricsIfc { - return &MockMetricsIfc{fail: pegomock.GlobalFailHandler} -} - -func (mock *MockMetricsIfc) NodeMetrics() (k8s.Metric, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsIfc().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("NodeMetrics", params, []reflect.Type{reflect.TypeOf((*k8s.Metric)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 k8s.Metric - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(k8s.Metric) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockMetricsIfc) PerNodeMetrics(_param0 []v1.Node) (map[string]k8s.Metric, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsIfc().") - } - params := []pegomock.Param{_param0} - result := pegomock.GetGenericMockFrom(mock).Invoke("PerNodeMetrics", params, []reflect.Type{reflect.TypeOf((*map[string]k8s.Metric)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 map[string]k8s.Metric - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(map[string]k8s.Metric) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockMetricsIfc) PodMetrics() (map[string]k8s.Metric, error) { - if mock == nil { - panic("mock must not be nil. Use myMock := NewMockMetricsIfc().") - } - params := []pegomock.Param{} - result := pegomock.GetGenericMockFrom(mock).Invoke("PodMetrics", params, []reflect.Type{reflect.TypeOf((*map[string]k8s.Metric)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) - var ret0 map[string]k8s.Metric - var ret1 error - if len(result) != 0 { - if result[0] != nil { - ret0 = result[0].(map[string]k8s.Metric) - } - if result[1] != nil { - ret1 = result[1].(error) - } - } - return ret0, ret1 -} - -func (mock *MockMetricsIfc) VerifyWasCalledOnce() *VerifierMetricsIfc { - return &VerifierMetricsIfc{ - mock: mock, - invocationCountMatcher: pegomock.Times(1), - } -} - -func (mock *MockMetricsIfc) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMetricsIfc { - return &VerifierMetricsIfc{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - } -} - -func (mock *MockMetricsIfc) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMetricsIfc { - return &VerifierMetricsIfc{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - inOrderContext: inOrderContext, - } -} - -func (mock *MockMetricsIfc) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMetricsIfc { - return &VerifierMetricsIfc{ - mock: mock, - invocationCountMatcher: invocationCountMatcher, - timeout: timeout, - } -} - -type VerifierMetricsIfc struct { - mock *MockMetricsIfc - invocationCountMatcher pegomock.Matcher - inOrderContext *pegomock.InOrderContext - timeout time.Duration -} - -func (verifier *VerifierMetricsIfc) NodeMetrics() *MetricsIfc_NodeMetrics_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NodeMetrics", params, verifier.timeout) - return &MetricsIfc_NodeMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MetricsIfc_NodeMetrics_OngoingVerification struct { - mock *MockMetricsIfc - methodInvocations []pegomock.MethodInvocation -} - -func (c *MetricsIfc_NodeMetrics_OngoingVerification) GetCapturedArguments() { -} - -func (c *MetricsIfc_NodeMetrics_OngoingVerification) GetAllCapturedArguments() { -} - -func (verifier *VerifierMetricsIfc) PerNodeMetrics(_param0 []v1.Node) *MetricsIfc_PerNodeMetrics_OngoingVerification { - params := []pegomock.Param{_param0} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PerNodeMetrics", params, verifier.timeout) - return &MetricsIfc_PerNodeMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MetricsIfc_PerNodeMetrics_OngoingVerification struct { - mock *MockMetricsIfc - methodInvocations []pegomock.MethodInvocation -} - -func (c *MetricsIfc_PerNodeMetrics_OngoingVerification) GetCapturedArguments() []v1.Node { - _param0 := c.GetAllCapturedArguments() - return _param0[len(_param0)-1] -} - -func (c *MetricsIfc_PerNodeMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 [][]v1.Node) { - params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) - if len(params) > 0 { - _param0 = make([][]v1.Node, len(params[0])) - for u, param := range params[0] { - _param0[u] = param.([]v1.Node) - } - } - return -} - -func (verifier *VerifierMetricsIfc) PodMetrics() *MetricsIfc_PodMetrics_OngoingVerification { - params := []pegomock.Param{} - methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PodMetrics", params, verifier.timeout) - return &MetricsIfc_PodMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} -} - -type MetricsIfc_PodMetrics_OngoingVerification struct { - mock *MockMetricsIfc - methodInvocations []pegomock.MethodInvocation -} - -func (c *MetricsIfc_PodMetrics_OngoingVerification) GetCapturedArguments() { -} - -func (c *MetricsIfc_PodMetrics_OngoingVerification) GetAllCapturedArguments() { -} diff --git a/internal/resource/mock_metricsserver_test.go b/internal/resource/mock_metricsserver_test.go new file mode 100644 index 00000000..be661338 --- /dev/null +++ b/internal/resource/mock_metricsserver_test.go @@ -0,0 +1,300 @@ +// Code generated by pegomock. DO NOT EDIT. +// Source: github.com/derailed/k9s/internal/resource (interfaces: MetricsServer) + +package resource_test + +import ( + k8s "github.com/derailed/k9s/internal/k8s" + pegomock "github.com/petergtz/pegomock" + v1 "k8s.io/api/core/v1" + v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" + "reflect" + "time" +) + +type MockMetricsServer struct { + fail func(message string, callerSkip ...int) +} + +func NewMockMetricsServer() *MockMetricsServer { + return &MockMetricsServer{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockMetricsServer) ClusterLoad(_param0 []v1.Node, _param1 []v1beta1.NodeMetrics) k8s.ClusterMetrics { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsServer().") + } + params := []pegomock.Param{_param0, _param1} + result := pegomock.GetGenericMockFrom(mock).Invoke("ClusterLoad", params, []reflect.Type{reflect.TypeOf((*k8s.ClusterMetrics)(nil)).Elem()}) + var ret0 k8s.ClusterMetrics + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(k8s.ClusterMetrics) + } + } + return ret0 +} + +func (mock *MockMetricsServer) FetchNodesMetrics() ([]v1beta1.NodeMetrics, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsServer().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodesMetrics", params, []reflect.Type{reflect.TypeOf((*[]v1beta1.NodeMetrics)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []v1beta1.NodeMetrics + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]v1beta1.NodeMetrics) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockMetricsServer) FetchPodsMetrics(_param0 string) ([]v1beta1.PodMetrics, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsServer().") + } + params := []pegomock.Param{_param0} + result := pegomock.GetGenericMockFrom(mock).Invoke("FetchPodsMetrics", params, []reflect.Type{reflect.TypeOf((*[]v1beta1.PodMetrics)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []v1beta1.PodMetrics + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]v1beta1.PodMetrics) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockMetricsServer) HasMetrics() bool { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsServer().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) + var ret0 bool + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + } + return ret0 +} + +func (mock *MockMetricsServer) NodesMetrics(_param0 []v1.Node, _param1 []v1beta1.NodeMetrics, _param2 k8s.NodesMetrics) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsServer().") + } + params := []pegomock.Param{_param0, _param1, _param2} + pegomock.GetGenericMockFrom(mock).Invoke("NodesMetrics", params, []reflect.Type{}) +} + +func (mock *MockMetricsServer) PodsMetrics(_param0 []v1beta1.PodMetrics, _param1 k8s.PodsMetrics) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsServer().") + } + params := []pegomock.Param{_param0, _param1} + pegomock.GetGenericMockFrom(mock).Invoke("PodsMetrics", params, []reflect.Type{}) +} + +func (mock *MockMetricsServer) VerifyWasCalledOnce() *VerifierMetricsServer { + return &VerifierMetricsServer{ + mock: mock, + invocationCountMatcher: pegomock.Times(1), + } +} + +func (mock *MockMetricsServer) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMetricsServer { + return &VerifierMetricsServer{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + } +} + +func (mock *MockMetricsServer) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMetricsServer { + return &VerifierMetricsServer{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + inOrderContext: inOrderContext, + } +} + +func (mock *MockMetricsServer) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMetricsServer { + return &VerifierMetricsServer{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + timeout: timeout, + } +} + +type VerifierMetricsServer struct { + mock *MockMetricsServer + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext + timeout time.Duration +} + +func (verifier *VerifierMetricsServer) ClusterLoad(_param0 []v1.Node, _param1 []v1beta1.NodeMetrics) *MetricsServer_ClusterLoad_OngoingVerification { + params := []pegomock.Param{_param0, _param1} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "ClusterLoad", params, verifier.timeout) + return &MetricsServer_ClusterLoad_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsServer_ClusterLoad_OngoingVerification struct { + mock *MockMetricsServer + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsServer_ClusterLoad_OngoingVerification) GetCapturedArguments() ([]v1.Node, []v1beta1.NodeMetrics) { + _param0, _param1 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1], _param1[len(_param1)-1] +} + +func (c *MetricsServer_ClusterLoad_OngoingVerification) GetAllCapturedArguments() (_param0 [][]v1.Node, _param1 [][]v1beta1.NodeMetrics) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([][]v1.Node, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.([]v1.Node) + } + _param1 = make([][]v1beta1.NodeMetrics, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.([]v1beta1.NodeMetrics) + } + } + return +} + +func (verifier *VerifierMetricsServer) FetchNodesMetrics() *MetricsServer_FetchNodesMetrics_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodesMetrics", params, verifier.timeout) + return &MetricsServer_FetchNodesMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsServer_FetchNodesMetrics_OngoingVerification struct { + mock *MockMetricsServer + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsServer_FetchNodesMetrics_OngoingVerification) GetCapturedArguments() { +} + +func (c *MetricsServer_FetchNodesMetrics_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierMetricsServer) FetchPodsMetrics(_param0 string) *MetricsServer_FetchPodsMetrics_OngoingVerification { + params := []pegomock.Param{_param0} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchPodsMetrics", params, verifier.timeout) + return &MetricsServer_FetchPodsMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsServer_FetchPodsMetrics_OngoingVerification struct { + mock *MockMetricsServer + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsServer_FetchPodsMetrics_OngoingVerification) GetCapturedArguments() string { + _param0 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1] +} + +func (c *MetricsServer_FetchPodsMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + } + return +} + +func (verifier *VerifierMetricsServer) HasMetrics() *MetricsServer_HasMetrics_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) + return &MetricsServer_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsServer_HasMetrics_OngoingVerification struct { + mock *MockMetricsServer + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsServer_HasMetrics_OngoingVerification) GetCapturedArguments() { +} + +func (c *MetricsServer_HasMetrics_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierMetricsServer) NodesMetrics(_param0 []v1.Node, _param1 []v1beta1.NodeMetrics, _param2 k8s.NodesMetrics) *MetricsServer_NodesMetrics_OngoingVerification { + params := []pegomock.Param{_param0, _param1, _param2} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "NodesMetrics", params, verifier.timeout) + return &MetricsServer_NodesMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsServer_NodesMetrics_OngoingVerification struct { + mock *MockMetricsServer + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsServer_NodesMetrics_OngoingVerification) GetCapturedArguments() ([]v1.Node, []v1beta1.NodeMetrics, k8s.NodesMetrics) { + _param0, _param1, _param2 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1], _param1[len(_param1)-1], _param2[len(_param2)-1] +} + +func (c *MetricsServer_NodesMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 [][]v1.Node, _param1 [][]v1beta1.NodeMetrics, _param2 []k8s.NodesMetrics) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([][]v1.Node, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.([]v1.Node) + } + _param1 = make([][]v1beta1.NodeMetrics, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.([]v1beta1.NodeMetrics) + } + _param2 = make([]k8s.NodesMetrics, len(params[2])) + for u, param := range params[2] { + _param2[u] = param.(k8s.NodesMetrics) + } + } + return +} + +func (verifier *VerifierMetricsServer) PodsMetrics(_param0 []v1beta1.PodMetrics, _param1 k8s.PodsMetrics) *MetricsServer_PodsMetrics_OngoingVerification { + params := []pegomock.Param{_param0, _param1} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "PodsMetrics", params, verifier.timeout) + return &MetricsServer_PodsMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsServer_PodsMetrics_OngoingVerification struct { + mock *MockMetricsServer + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsServer_PodsMetrics_OngoingVerification) GetCapturedArguments() ([]v1beta1.PodMetrics, k8s.PodsMetrics) { + _param0, _param1 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1], _param1[len(_param1)-1] +} + +func (c *MetricsServer_PodsMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 [][]v1beta1.PodMetrics, _param1 []k8s.PodsMetrics) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([][]v1beta1.PodMetrics, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.([]v1beta1.PodMetrics) + } + _param1 = make([]k8s.PodsMetrics, len(params[1])) + for u, param := range params[1] { + _param1[u] = param.(k8s.PodsMetrics) + } + } + return +} diff --git a/internal/resource/mock_metricsservice_test.go b/internal/resource/mock_metricsservice_test.go new file mode 100644 index 00000000..3b00dd98 --- /dev/null +++ b/internal/resource/mock_metricsservice_test.go @@ -0,0 +1,170 @@ +// Code generated by pegomock. DO NOT EDIT. +// Source: github.com/derailed/k9s/internal/resource (interfaces: MetricsService) + +package resource_test + +import ( + pegomock "github.com/petergtz/pegomock" + v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" + "reflect" + "time" +) + +type MockMetricsService struct { + fail func(message string, callerSkip ...int) +} + +func NewMockMetricsService() *MockMetricsService { + return &MockMetricsService{fail: pegomock.GlobalFailHandler} +} + +func (mock *MockMetricsService) FetchNodesMetrics() ([]v1beta1.NodeMetrics, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsService().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("FetchNodesMetrics", params, []reflect.Type{reflect.TypeOf((*[]v1beta1.NodeMetrics)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []v1beta1.NodeMetrics + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]v1beta1.NodeMetrics) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockMetricsService) FetchPodsMetrics(_param0 string) ([]v1beta1.PodMetrics, error) { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsService().") + } + params := []pegomock.Param{_param0} + result := pegomock.GetGenericMockFrom(mock).Invoke("FetchPodsMetrics", params, []reflect.Type{reflect.TypeOf((*[]v1beta1.PodMetrics)(nil)).Elem(), reflect.TypeOf((*error)(nil)).Elem()}) + var ret0 []v1beta1.PodMetrics + var ret1 error + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].([]v1beta1.PodMetrics) + } + if result[1] != nil { + ret1 = result[1].(error) + } + } + return ret0, ret1 +} + +func (mock *MockMetricsService) HasMetrics() bool { + if mock == nil { + panic("mock must not be nil. Use myMock := NewMockMetricsService().") + } + params := []pegomock.Param{} + result := pegomock.GetGenericMockFrom(mock).Invoke("HasMetrics", params, []reflect.Type{reflect.TypeOf((*bool)(nil)).Elem()}) + var ret0 bool + if len(result) != 0 { + if result[0] != nil { + ret0 = result[0].(bool) + } + } + return ret0 +} + +func (mock *MockMetricsService) VerifyWasCalledOnce() *VerifierMetricsService { + return &VerifierMetricsService{ + mock: mock, + invocationCountMatcher: pegomock.Times(1), + } +} + +func (mock *MockMetricsService) VerifyWasCalled(invocationCountMatcher pegomock.Matcher) *VerifierMetricsService { + return &VerifierMetricsService{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + } +} + +func (mock *MockMetricsService) VerifyWasCalledInOrder(invocationCountMatcher pegomock.Matcher, inOrderContext *pegomock.InOrderContext) *VerifierMetricsService { + return &VerifierMetricsService{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + inOrderContext: inOrderContext, + } +} + +func (mock *MockMetricsService) VerifyWasCalledEventually(invocationCountMatcher pegomock.Matcher, timeout time.Duration) *VerifierMetricsService { + return &VerifierMetricsService{ + mock: mock, + invocationCountMatcher: invocationCountMatcher, + timeout: timeout, + } +} + +type VerifierMetricsService struct { + mock *MockMetricsService + invocationCountMatcher pegomock.Matcher + inOrderContext *pegomock.InOrderContext + timeout time.Duration +} + +func (verifier *VerifierMetricsService) FetchNodesMetrics() *MetricsService_FetchNodesMetrics_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchNodesMetrics", params, verifier.timeout) + return &MetricsService_FetchNodesMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsService_FetchNodesMetrics_OngoingVerification struct { + mock *MockMetricsService + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsService_FetchNodesMetrics_OngoingVerification) GetCapturedArguments() { +} + +func (c *MetricsService_FetchNodesMetrics_OngoingVerification) GetAllCapturedArguments() { +} + +func (verifier *VerifierMetricsService) FetchPodsMetrics(_param0 string) *MetricsService_FetchPodsMetrics_OngoingVerification { + params := []pegomock.Param{_param0} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "FetchPodsMetrics", params, verifier.timeout) + return &MetricsService_FetchPodsMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsService_FetchPodsMetrics_OngoingVerification struct { + mock *MockMetricsService + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsService_FetchPodsMetrics_OngoingVerification) GetCapturedArguments() string { + _param0 := c.GetAllCapturedArguments() + return _param0[len(_param0)-1] +} + +func (c *MetricsService_FetchPodsMetrics_OngoingVerification) GetAllCapturedArguments() (_param0 []string) { + params := pegomock.GetGenericMockFrom(c.mock).GetInvocationParams(c.methodInvocations) + if len(params) > 0 { + _param0 = make([]string, len(params[0])) + for u, param := range params[0] { + _param0[u] = param.(string) + } + } + return +} + +func (verifier *VerifierMetricsService) HasMetrics() *MetricsService_HasMetrics_OngoingVerification { + params := []pegomock.Param{} + methodInvocations := pegomock.GetGenericMockFrom(verifier.mock).Verify(verifier.inOrderContext, verifier.invocationCountMatcher, "HasMetrics", params, verifier.timeout) + return &MetricsService_HasMetrics_OngoingVerification{mock: verifier.mock, methodInvocations: methodInvocations} +} + +type MetricsService_HasMetrics_OngoingVerification struct { + mock *MockMetricsService + methodInvocations []pegomock.MethodInvocation +} + +func (c *MetricsService_HasMetrics_OngoingVerification) GetCapturedArguments() { +} + +func (c *MetricsService_HasMetrics_OngoingVerification) GetAllCapturedArguments() { +} diff --git a/internal/resource/no.go b/internal/resource/no.go index d4a288ad..32e22aef 100644 --- a/internal/resource/no.go +++ b/internal/resource/no.go @@ -18,86 +18,77 @@ const ( // Node tracks a kubernetes resource. type Node struct { *Base - instance *v1.Node - metricSvc MetricsIfc - metrics k8s.Metric + instance *v1.Node + metricsServer MetricsServer + metrics k8s.NodeMetrics } // NewNodeList returns a new resource list. -func NewNodeList(ns string) List { - return NewNodeListWithArgs(ns, NewNode()) +func NewNodeList(c k8s.Connection, ns string) List { + return newList( + NotNamespaced, + "no", + NewNode(c), + ViewAccess|DescribeAccess, + ) } -// NewNodeListWithArgs returns a new resource list. -func NewNodeListWithArgs(ns string, res Resource) List { - return newList(NotNamespaced, "no", res, ViewAccess|DescribeAccess) +// NewNode instantiates a new Node. +func NewNode(c k8s.Connection) *Node { + n := &Node{&Base{connection: c, resource: k8s.NewNode(c)}, nil, k8s.NewMetricsServer(c), k8s.NodeMetrics{}} + n.Factory = n + + return n } -// NewNode instantiates a new Endpoint. -func NewNode() *Node { - return NewNodeWithArgs(k8s.NewNode(), k8s.NewMetricsServer()) -} - -// NewNodeWithArgs instantiates a new Endpoint. -func NewNodeWithArgs(r k8s.Res, mx MetricsIfc) *Node { - ep := &Node{ - metricSvc: mx, - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*Node) NewInstance(i interface{}) Columnar { - cm := NewNode() - switch i.(type) { +// New builds a new Node instance from a k8s resource. +func (r *Node) New(i interface{}) Columnar { + c := NewNode(r.connection) + switch instance := i.(type) { case *v1.Node: - cm.instance = i.(*v1.Node) + c.instance = instance case v1.Node: - ii := i.(v1.Node) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Node type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // List all resources for a given namespace. func (r *Node) List(ns string) (Columnars, error) { - ii, err := r.caller.List(AllNamespaces) + nn, err := r.resource.List(ns) if err != nil { return nil, err } - nn := make([]v1.Node, len(ii)) - for k, i := range ii { - nn[k] = i.(v1.Node) + nodes := make([]v1.Node, 0, len(nn)) + for _, n := range nn { + nodes = append(nodes, n.(v1.Node)) } - cc := make(Columnars, 0, len(nn)) - mx, err := r.metricSvc.PerNodeMetrics(nn) - if err != nil { - log.Warn().Msgf("No metrics: %#v", err) + mx := make(k8s.NodesMetrics, len(nodes)) + if r.metricsServer.HasMetrics() { + nmx, _ := r.metricsServer.FetchNodesMetrics() + r.metricsServer.NodesMetrics(nodes, nmx, mx) } - for i := 0; i < len(nn); i++ { - n := r.NewInstance(&nn[i]).(*Node) - if err == nil { - n.metrics = mx[nn[i].Name] - } - cc = append(cc, n) + cc := make(Columnars, 0, len(nodes)) + for i := range nodes { + no := r.New(&nodes[i]).(*Node) + no.metrics = mx[nodes[i].Name] + cc = append(cc, no) } + return cc, nil } // Marshal a resource to yaml. func (r *Node) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { log.Error().Err(err) return "", err @@ -106,6 +97,7 @@ func (r *Node) Marshal(path string) (string, error) { no := i.(*v1.Node) no.TypeMeta.APIVersion = "v1" no.TypeMeta.Kind = "Node" + return r.marshalObject(no) } @@ -116,12 +108,15 @@ func (*Node) Header(ns string) Row { "STATUS", "ROLES", "VERSION", + "KERNEL", "INTERNAL-IP", "EXTERNAL-IP", "CPU", "MEM", - "AVAILABLE_CPU", - "AVAILABLE_MEM", + "AVA CPU", + "AVA MEM", + "CAP CPU", + "CAP MEM", "AGE", } } @@ -131,33 +126,28 @@ func (r *Node) Fields(ns string) Row { ff := make(Row, 0, len(r.Header(ns))) i := r.instance - status := r.status(i) iIP, eIP := r.getIPs(i.Status.Addresses) iIP, eIP = missing(iIP), missing(eIP) - roles := missing(strings.Join(findNodeRoles(i), ",")) - cpu, mem, acpu, amem := na(r.metrics.CPU), na(r.metrics.Mem), na(r.metrics.AvailCPU), na(r.metrics.AvailMem) - return append(ff, i.Name, - status, - roles, + r.status(i), + missing(strings.Join(findNodeRoles(i), ",")), + i.Status.NodeInfo.KubeletVersion, i.Status.NodeInfo.KernelVersion, iIP, eIP, - cpu, - mem, - acpu, - amem, + ToMillicore(r.metrics.CurrentCPU), + ToMi(r.metrics.CurrentMEM), + ToMillicore(r.metrics.AvailCPU), + ToMi(r.metrics.AvailMEM), + ToMillicore(r.metrics.TotalCPU), + ToMi(r.metrics.TotalMEM), toAge(i.ObjectMeta.CreationTimestamp), ) } -// ExtFields returns extended fields in relation to headers. -func (*Node) ExtFields() Properties { - return Properties{} -} - +// ---------------------------------------------------------------------------- // Helpers... func (*Node) getIPs(addrs []v1.NodeAddress) (iIP, eIP string) { @@ -169,6 +159,7 @@ func (*Node) getIPs(addrs []v1.NodeAddress) (iIP, eIP string) { iIP = a.Address } } + return } @@ -195,6 +186,7 @@ func (r *Node) status(i *v1.Node) string { if i.Spec.Unschedulable { status = append(status, "SchedulingDisabled") } + return strings.Join(status, ",") } @@ -210,5 +202,6 @@ func findNodeRoles(i *v1.Node) []string { roles.Insert(v) } } + return roles.List() } diff --git a/internal/resource/no_test.go b/internal/resource/no_test.go index 4f3a0cb4..2eb0fa4c 100644 --- a/internal/resource/no_test.go +++ b/internal/resource/no_test.go @@ -8,7 +8,10 @@ import ( m "github.com/petergtz/pegomock" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" + res "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" + v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) func TestNodeListAccess(t *testing.T) { @@ -31,9 +34,9 @@ func TestNodeFields(t *testing.T) { func TestNodeMarshal(t *testing.T) { setup(t) - mx := NewMockMetricsIfc() - m.When(mx.PerNodeMetrics([]v1.Node{*k8sNode()})). - ThenReturn(map[string]k8s.Metric{"fred": {}}, nil) + mx := NewMockMetricsServer() + // m.When(mx.NodesMetrics([]v1.Node{*k8sNode()})). + // ThenReturn(map[string]k8s.RawMetric{"fred": {}}, nil) ca := NewMockCaller() m.When(ca.Get("blee", "fred")).ThenReturn(k8sNode(), nil) @@ -47,48 +50,34 @@ func TestNodeMarshal(t *testing.T) { func TestNodeListData(t *testing.T) { setup(t) - mx := NewMockMetricsIfc() - m.When(mx.PerNodeMetrics([]v1.Node{*k8sNode()})). - ThenReturn(map[string]k8s.Metric{"fred": {}}, nil) - ca := NewMockCaller() - m.When(ca.List("")).ThenReturn(k8s.Collection{*k8sNode()}, nil) + mockServer := NewMockMetricsServer() + m.When(mockServer.HasMetrics()).ThenReturn(true) + m.When(mockServer.FetchNodesMetrics()). + ThenReturn([]mv1beta1.NodeMetrics{makeMxNode("fred", "100m", "100Mi")}, nil) - l := resource.NewNodeListWithArgs("", resource.NewNodeWithArgs(ca, mx)) + ca := NewMockCaller() + m.When(ca.List("-")).ThenReturn(k8s.Collection{*k8sNode()}, nil) + + l := resource.NewNodeListWithArgs("", resource.NewNodeWithArgs(ca, mockServer)) // Make sure we can get deltas! for i := 0; i < 2; i++ { err := l.Reconcile() assert.Nil(t, err) } - ca.VerifyWasCalled(m.Times(2)).List("") + ca.VerifyWasCalled(m.Times(2)).List("-") td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) - row := td.Rows["fred"] - assert.Equal(t, 11, len(row.Deltas)) + row, ok := td.Rows["fred"] + assert.True(t, ok) + assert.Equal(t, 12, len(row.Deltas)) for _, d := range row.Deltas { assert.Equal(t, "", d) } assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestNodeListDescribe(t *testing.T) { - setup(t) - - mx := NewMockMetricsIfc() - m.When(mx.PerNodeMetrics([]v1.Node{*k8sNode()})). - ThenReturn(map[string]k8s.Metric{"fred": {}}, nil) - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sNode(), nil) - l := resource.NewNodeListWithArgs("blee", resource.NewNodeWithArgs(ca, mx)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sNode() *v1.Node { @@ -106,6 +95,25 @@ func k8sNode() *v1.Node { } } +func makeMxNode(name, cpu, mem string) mv1beta1.NodeMetrics { + return v1beta1.NodeMetrics{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Usage: makeRes(cpu, mem), + } +} + +func makeRes(c, m string) v1.ResourceList { + cpu, _ := res.ParseQuantity(c) + mem, _ := res.ParseQuantity(m) + + return v1.ResourceList{ + v1.ResourceCPU: cpu, + v1.ResourceMemory: mem, + } +} + func newNode() resource.Columnar { return resource.NewNode().NewInstance(k8sNode()) } diff --git a/internal/resource/ns.go b/internal/resource/ns.go index 820367cd..4e1d306d 100644 --- a/internal/resource/ns.go +++ b/internal/resource/ns.go @@ -13,51 +13,43 @@ type Namespace struct { } // NewNamespaceList returns a new resource list. -func NewNamespaceList(ns string) List { - return NewNamespaceListWithArgs(ns, NewNamespace()) +func NewNamespaceList(c k8s.Connection, ns string) List { + return newList( + NotNamespaced, + "ns", + NewNamespace(c), + CRUDAccess|DescribeAccess, + ) } -// NewNamespaceListWithArgs returns a new resource list. -func NewNamespaceListWithArgs(ns string, res Resource) List { - return newList(NotNamespaced, "ns", res, CRUDAccess|DescribeAccess) +// NewNamespace instantiates a new Namespace. +func NewNamespace(c k8s.Connection) *Namespace { + n := &Namespace{&Base{connection: c, resource: k8s.NewNamespace(c)}, nil} + n.Factory = n + + return n } -// NewNamespace instantiates a new Endpoint. -func NewNamespace() *Namespace { - return NewNamespaceWithArgs(k8s.NewNamespace()) -} - -// NewNamespaceWithArgs instantiates a new Endpoint. -func NewNamespaceWithArgs(r k8s.Res) *Namespace { - ep := &Namespace{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*Namespace) NewInstance(i interface{}) Columnar { - cm := NewNamespace() - switch i.(type) { +// New builds a new Namespace instance from a k8s resource. +func (r *Namespace) New(i interface{}) Columnar { + c := NewNamespace(r.connection) + switch instance := i.(type) { case *v1.Namespace: - cm.instance = i.(*v1.Namespace) + c.instance = instance case v1.Namespace: - ii := i.(v1.Namespace) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Namespace type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal a resource to yaml. func (r *Namespace) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { log.Error().Err(err) return "", err @@ -66,6 +58,7 @@ func (r *Namespace) Marshal(path string) (string, error) { nss := i.(*v1.Namespace) nss.TypeMeta.APIVersion = "v1" nss.TypeMeta.Kind = "Namespace" + return r.marshalObject(nss) } @@ -78,14 +71,10 @@ func (*Namespace) Header(ns string) Row { func (r *Namespace) Fields(ns string) Row { ff := make(Row, 0, len(r.Header(ns))) i := r.instance + return append(ff, i.Name, string(i.Status.Phase), toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*Namespace) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/ns_test.go b/internal/resource/ns_test.go index 3050197e..03018866 100644 --- a/internal/resource/ns_test.go +++ b/internal/resource/ns_test.go @@ -62,7 +62,6 @@ func TestNamespaceListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 3, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestNamespaceListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestNamespaceListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sNamespace(), nil) - l := resource.NewNamespaceListWithArgs("blee", resource.NewNamespaceWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sNamespace() *v1.Namespace { diff --git a/internal/resource/pdb.go b/internal/resource/pdb.go index 1b2fe94c..3a965493 100644 --- a/internal/resource/pdb.go +++ b/internal/resource/pdb.go @@ -11,59 +11,52 @@ import ( // PodDisruptionBudget that can be displayed in a table and interacted with. type PodDisruptionBudget struct { *Base + instance *v1beta1.PodDisruptionBudget } // NewPDBList returns a new resource list. -func NewPDBList(ns string) List { - return NewPDBListWithArgs(ns, NewPDB()) +func NewPDBList(c k8s.Connection, ns string) List { + return newList( + ns, + "pdb", + NewPDB(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewPDBListWithArgs returns a new resource list. -func NewPDBListWithArgs(ns string, res Resource) List { - return newList(ns, "pdb", res, AllVerbsAccess|DescribeAccess) -} +// NewPDB instantiates a new PDB. +func NewPDB(c k8s.Connection) *PodDisruptionBudget { + p := &PodDisruptionBudget{&Base{connection: c, resource: k8s.NewPodDisruptionBudget(c)}, nil} + p.Factory = p -// NewPDB returns a new PodDisruptionBudget instance. -func NewPDB() *PodDisruptionBudget { - return NewPDBWithArgs(k8s.NewPodDisruptionBudget()) -} - -// NewPDBWithArgs returns a new Pod instance. -func NewPDBWithArgs(r k8s.Res) *PodDisruptionBudget { - p := &PodDisruptionBudget{ - Base: &Base{ - caller: r, - }, - } - p.creator = p return p } -// NewInstance builds a new PodDisruptionBudget instance from a k8s resource. -func (r *PodDisruptionBudget) NewInstance(i interface{}) Columnar { - pdb := NewPDB() - switch i.(type) { +// New builds a new PDB instance from a k8s resource. +func (r *PodDisruptionBudget) New(i interface{}) Columnar { + c := NewPDB(r.connection) + switch instance := i.(type) { case *v1beta1.PodDisruptionBudget: - pdb.instance = i.(*v1beta1.PodDisruptionBudget) + c.instance = instance case v1beta1.PodDisruptionBudget: - ii := i.(v1beta1.PodDisruptionBudget) - pdb.instance = &ii + c.instance = &instance case *interface{}: ptr := *i.(*interface{}) pdbi := ptr.(v1beta1.PodDisruptionBudget) - pdb.instance = &pdbi + c.instance = &pdbi default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown PDB type %#v", i) } - pdb.path = r.namespacedName(pdb.instance.ObjectMeta) - return pdb + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *PodDisruptionBudget) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -71,6 +64,7 @@ func (r *PodDisruptionBudget) Marshal(path string) (string, error) { pdb := i.(*v1beta1.PodDisruptionBudget) pdb.TypeMeta.APIVersion = "v1beta1" pdb.TypeMeta.Kind = "PodDisruptionBudget" + return r.marshalObject(pdb) } @@ -80,6 +74,7 @@ func (*PodDisruptionBudget) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "MIN AVAILABLE", @@ -122,8 +117,3 @@ func (r *PodDisruptionBudget) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extra info about the resource. -func (r *PodDisruptionBudget) ExtFields() Properties { - return nil -} diff --git a/internal/resource/pdb_test.go b/internal/resource/pdb_test.go index e7610619..73173ffb 100644 --- a/internal/resource/pdb_test.go +++ b/internal/resource/pdb_test.go @@ -72,7 +72,6 @@ func TestPDBListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 8, len(row.Deltas)) for _, d := range row.Deltas { @@ -81,19 +80,6 @@ func TestPDBListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestPDBListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sPDB(), nil) - l := resource.NewPDBListWithArgs("blee", resource.NewPDBWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sPDB() *v1beta1.PodDisruptionBudget { diff --git a/internal/resource/pod.go b/internal/resource/pod.go index 43055831..65d69de7 100644 --- a/internal/resource/pod.go +++ b/internal/resource/pod.go @@ -37,75 +37,64 @@ type ( // Pod that can be displayed in a table and interacted with. Pod struct { *Base - instance *v1.Pod - metricSvc MetricsIfc - metrics k8s.Metric + instance *v1.Pod + metricServer MetricsServer + metrics k8s.PodMetrics } ) // NewPodList returns a new resource list. -func NewPodList(ns string) List { - return NewPodListWithArgs(ns, NewPod()) +func NewPodList(c k8s.Connection, ns string) List { + return newList( + ns, + "po", + NewPod(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewPodListWithArgs returns a new resource list. -func NewPodListWithArgs(ns string, res Resource) List { - l := newList(ns, "po", res, AllVerbsAccess|DescribeAccess) - l.xray = true - return l -} +// NewPod instantiates a new Pod. +func NewPod(c k8s.Connection) *Pod { + p := &Pod{&Base{connection: c, resource: k8s.NewPod(c)}, nil, k8s.NewMetricsServer(c), k8s.PodMetrics{}} + p.Factory = p -// NewPod returns a new Pod instance. -func NewPod() *Pod { - return NewPodWithArgs(k8s.NewPod(), k8s.NewMetricsServer()) -} - -// NewPodWithArgs returns a new Pod instance. -func NewPodWithArgs(r k8s.Res, mx MetricsIfc) *Pod { - p := &Pod{ - metricSvc: mx, - Base: &Base{ - caller: r, - }, - } - p.creator = p return p } -// NewInstance builds a new Pod instance from a k8s resource. -func (r *Pod) NewInstance(i interface{}) Columnar { - pod := NewPod() - switch i.(type) { +// New builds a new Pod instance from a k8s resource. +func (r *Pod) New(i interface{}) Columnar { + c := NewPod(r.connection) + switch instance := i.(type) { case *v1.Pod: - pod.instance = i.(*v1.Pod) + c.instance = instance case v1.Pod: - ii := i.(v1.Pod) - pod.instance = &ii + c.instance = &instance case *interface{}: - ptr := *i.(*interface{}) + ptr := *instance po := ptr.(v1.Pod) - pod.instance = &po + c.instance = &po default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Pod type %#v", i) } - pod.path = r.namespacedName(pod.instance.ObjectMeta) - return pod + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Metrics retrieves cpu/mem resource consumption on a pod. -func (r *Pod) Metrics() k8s.Metric { +func (r *Pod) Metrics() k8s.PodMetrics { return r.metrics } // SetMetrics set the current k8s resource metrics on a given pod. -func (r *Pod) SetMetrics(m k8s.Metric) { +func (r *Pod) SetMetrics(m k8s.PodMetrics) { r.metrics = m } // Marshal resource to yaml. func (r *Pod) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -113,18 +102,20 @@ func (r *Pod) Marshal(path string) (string, error) { po := i.(*v1.Pod) po.TypeMeta.APIVersion = "v1" po.TypeMeta.Kind = "Pod" + return r.marshalObject(po) } // Containers lists out all the docker contrainers name contained in a pod. func (r *Pod) Containers(path string, includeInit bool) ([]string, error) { ns, po := namespaced(path) - return r.caller.(k8s.Loggable).Containers(ns, po, includeInit) + + return r.resource.(k8s.Loggable).Containers(ns, po, includeInit) } // Logs tails a given container logs func (r *Pod) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (context.CancelFunc, error) { - req := r.caller.(k8s.Loggable).Logs(ns, n, co, lines, prev) + req := r.resource.(k8s.Loggable).Logs(ns, n, co, lines, prev) ctx, cancel := context.WithCancel(context.TODO()) req.Context(ctx) @@ -158,29 +149,32 @@ func (r *Pod) Logs(c chan<- string, ns, n, co string, lines int64, prev bool) (c c <- scanner.Text() } }() + return cancel, nil } // List resources for a given namespace. func (r *Pod) List(ns string) (Columnars, error) { - ii, err := r.caller.List(ns) + pods, err := r.resource.List(ns) if err != nil { return nil, err } - metrics, err := r.metricSvc.PodMetrics() - if err != nil { - log.Error().Err(err) + mx := make(k8s.PodsMetrics, len(pods)) + if r.metricServer.HasMetrics() { + pmx, _ := r.metricServer.FetchPodsMetrics(ns) + r.metricServer.PodsMetrics(pmx, mx) } - cc := make(Columnars, 0, len(ii)) - for i := 0; i < len(ii); i++ { - po := r.NewInstance(&ii[i]).(MxColumnar) + cc := make(Columnars, 0, len(pods)) + for i := range pods { + po := r.New(&pods[i]).(*Pod) if err == nil { - po.SetMetrics(metrics[po.Name()]) + po.metrics = mx[po.Name()] } cc = append(cc, po) } + return cc, nil } @@ -214,13 +208,14 @@ func (r *Pod) Fields(ns string) Row { } cr, _, rc, cc := r.statuses() + return append(ff, Pad(i.ObjectMeta.Name, podNameSize), strconv.Itoa(cr)+"/"+strconv.Itoa(len(cc)), r.phase(i.Status), strconv.Itoa(rc), - r.metrics.CPU, - r.metrics.Mem, + ToMillicore(r.metrics.CurrentCPU), + ToMi(r.metrics.CurrentMEM), i.Status.PodIP, i.Spec.NodeName, string(i.Status.QOSClass), @@ -228,27 +223,15 @@ func (r *Pod) Fields(ns string) Row { ) } -// ExtFields returns extra info about the resource. -func (r *Pod) ExtFields() Properties { - i := r.instance - - return Properties{ - "Priority": strconv.Itoa(int(*i.Spec.Priority)), - "Priority Class": missing(i.Spec.PriorityClassName), - "Labels": mapToStr(i.Labels), - "Annotations": mapToStr(i.ObjectMeta.Annotations), - "Containers": r.toContainers(i.Spec.Containers), - "Init Containers": r.toContainers(i.Spec.InitContainers), - "Node Selectors": mapToStr(i.Spec.NodeSelector), - "Volumes": r.toVolumes(i.Spec.Volumes), - } -} +// ---------------------------------------------------------------------------- +// Helpers... func (r *Pod) toVolumes(vv []v1.Volume) map[string]interface{} { m := make(map[string]interface{}, len(vv)) for _, v := range vv { m[v.Name] = r.toVolume(v) } + return m } @@ -280,6 +263,7 @@ func (r *Pod) toContainers(cc []v1.Container) map[string]interface{} { "Environment": r.toEnv(c.Env), } } + return m } @@ -297,6 +281,7 @@ func (r *Pod) toEnv(ee []v1.EnvVar) []string { ss[i] = e.Name + "=" + e.Value + "(" + s + ")" } } + return ss } @@ -317,6 +302,7 @@ func (r *Pod) toEnvFrom(e *v1.EnvVarSource) string { f := e.SecretKeyRef s += f.Name + ":" + f.Key + "(" + r.boolPtrToStr(f.Optional) + ")" } + return s } @@ -339,6 +325,7 @@ func (r *Pod) statuses() (cr, ct, rc int, cc []v1.ContainerStatus) { } rc += int(c.RestartCount) } + return } @@ -357,5 +344,6 @@ func (*Pod) phase(s v1.PodStatus) string { } } } + return status } diff --git a/internal/resource/pod_test.go b/internal/resource/pod_test.go index 28f850e8..e4b6e875 100644 --- a/internal/resource/pod_test.go +++ b/internal/resource/pod_test.go @@ -35,8 +35,9 @@ func TestPodFields(t *testing.T) { func TestPodMarshal(t *testing.T) { setup(t) - mx := NewMockMetricsIfc() - m.When(mx.PodMetrics()).ThenReturn(map[string]k8s.Metric{"fred": {}}, nil) + mx := NewMockMetricsServer() + // metrics := make(k8s.PodsMetrics, 1) + // m.When(mx.PodsMetrics([]mv1beta1.PodMetrics{}, metrics)).thenReturn() ca := NewMockCaller() m.When(ca.Get("blee", "fred")).ThenReturn(k8sPod(), nil) @@ -50,8 +51,8 @@ func TestPodMarshal(t *testing.T) { func TestPodListData(t *testing.T) { setup(t) - mx := NewMockMetricsIfc() - m.When(mx.PodMetrics()).ThenReturn(map[string]k8s.Metric{"fred": {}}, nil) + mx := NewMockMetricsServer() + // m.When(mx.PodsMetrics("")).ThenReturn(map[string]k8s.Metric{"fred": {}}, nil) ca := NewMockCaller() m.When(ca.List("")).ThenReturn(k8s.Collection{*k8sPod()}, nil) @@ -66,7 +67,6 @@ func TestPodListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.AllNamespaces, l.GetNamespace()) - assert.True(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 11, len(row.Deltas)) for _, d := range row.Deltas { @@ -75,21 +75,6 @@ func TestPodListData(t *testing.T) { assert.Equal(t, resource.Row{"blee"}, row.Fields[:1]) } -func TestPodListDescribe(t *testing.T) { - setup(t) - - mx := NewMockMetricsIfc() - m.When(mx.PodMetrics()).ThenReturn(map[string]k8s.Metric{"fred": {}}, nil) - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sPod(), nil) - l := resource.NewPodListWithArgs("blee", resource.NewPodWithArgs(ca, mx)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 8, len(props)) -} - // Helpers... func k8sPod() *v1.Pod { diff --git a/internal/resource/pv.go b/internal/resource/pv.go index 2d32f3c3..b2a615cc 100644 --- a/internal/resource/pv.go +++ b/internal/resource/pv.go @@ -16,51 +16,43 @@ type PV struct { } // NewPVList returns a new resource list. -func NewPVList(ns string) List { - return NewPVListWithArgs(ns, NewPV()) +func NewPVList(c k8s.Connection, ns string) List { + return newList( + NotNamespaced, + "pv", + NewPV(c), + CRUDAccess|DescribeAccess, + ) } -// NewPVListWithArgs returns a new resource list. -func NewPVListWithArgs(ns string, res Resource) List { - return newList(NotNamespaced, "pv", res, CRUDAccess|DescribeAccess) +// NewPV instantiates a new PV. +func NewPV(c k8s.Connection) *PV { + p := &PV{&Base{connection: c, resource: k8s.NewPV(c)}, nil} + p.Factory = p + + return p } -// NewPV instantiates a new Endpoint. -func NewPV() *PV { - return NewPVWithArgs(k8s.NewPV()) -} - -// NewPVWithArgs instantiates a new Endpoint. -func NewPVWithArgs(r k8s.Res) *PV { - ep := &PV{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*PV) NewInstance(i interface{}) Columnar { - cm := NewPV() - switch i.(type) { +// New builds a new PV instance from a k8s resource. +func (r *PV) New(i interface{}) Columnar { + c := NewPV(r.connection) + switch instance := i.(type) { case *v1.PersistentVolume: - cm.instance = i.(*v1.PersistentVolume) + c.instance = instance case v1.PersistentVolume: - ii := i.(v1.PersistentVolume) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown PV type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *PV) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -68,6 +60,7 @@ func (r *PV) Marshal(path string) (string, error) { pv := i.(*v1.PersistentVolume) pv.TypeMeta.APIVersion = "v1" pv.TypeMeta.Kind = "PeristentVolume" + return r.marshalObject(pv) } @@ -77,6 +70,7 @@ func (*PV) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "CAPACITY", "ACCESS MODES", "RECLAIM POLICY", "STATUS", "CLAIM", "STORAGECLASS", "REASON", "AGE") } @@ -118,11 +112,7 @@ func (r *PV) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (*PV) ExtFields() Properties { - return Properties{} -} - +// ---------------------------------------------------------------------------- // Helpers... func (r *PV) accessMode(aa []v1.PersistentVolumeAccessMode) string { @@ -138,6 +128,7 @@ func (r *PV) accessMode(aa []v1.PersistentVolumeAccessMode) string { s = append(s, "RWX") } } + return strings.Join(s, ",") } @@ -147,6 +138,7 @@ func (r *PV) accessContains(cc []v1.PersistentVolumeAccessMode, a v1.PersistentV return true } } + return false } @@ -157,5 +149,6 @@ func (r *PV) accessDedup(cc []v1.PersistentVolumeAccessMode) []v1.PersistentVolu set = append(set, c) } } + return set } diff --git a/internal/resource/pv_test.go b/internal/resource/pv_test.go index b56f07bd..36ae988b 100644 --- a/internal/resource/pv_test.go +++ b/internal/resource/pv_test.go @@ -62,7 +62,6 @@ func TestPVListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 9, len(row.Deltas)) for _, d := range row.Deltas { @@ -71,19 +70,6 @@ func TestPVListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestPVListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sPV(), nil) - l := resource.NewPVListWithArgs("blee", resource.NewPVWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sPV() *v1.PersistentVolume { diff --git a/internal/resource/pvc.go b/internal/resource/pvc.go index 947a09c1..26fd6557 100644 --- a/internal/resource/pvc.go +++ b/internal/resource/pvc.go @@ -13,51 +13,43 @@ type PVC struct { } // NewPVCList returns a new resource list. -func NewPVCList(ns string) List { - return NewPVCListWithArgs(ns, NewPVC()) +func NewPVCList(c k8s.Connection, ns string) List { + return newList( + ns, + "pvc", + NewPVC(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewPVCListWithArgs returns a new resource list. -func NewPVCListWithArgs(ns string, res Resource) List { - return newList(ns, "pvc", res, AllVerbsAccess|DescribeAccess) +// NewPVC instantiates a new PVC. +func NewPVC(c k8s.Connection) *PVC { + p := &PVC{&Base{connection: c, resource: k8s.NewPVC(c)}, nil} + p.Factory = p + + return p } -// NewPVC instantiates a new Endpoint. -func NewPVC() *PVC { - return NewPVCWithArgs(k8s.NewPVC()) -} - -// NewPVCWithArgs instantiates a new Endpoint. -func NewPVCWithArgs(r k8s.Res) *PVC { - ep := &PVC{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*PVC) NewInstance(i interface{}) Columnar { - cm := NewPVC() - switch i.(type) { +// New builds a new PVC instance from a k8s resource. +func (r *PVC) New(i interface{}) Columnar { + c := NewPVC(r.connection) + switch instance := i.(type) { case *v1.PersistentVolumeClaim: - cm.instance = i.(*v1.PersistentVolumeClaim) + c.instance = instance case v1.PersistentVolumeClaim: - ii := i.(v1.PersistentVolumeClaim) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown PVC type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *PVC) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -65,6 +57,7 @@ func (r *PVC) Marshal(path string) (string, error) { pvc := i.(*v1.PersistentVolumeClaim) pvc.TypeMeta.APIVersion = "v1" pvc.TypeMeta.Kind = "PersistentVolumeClaim" + return r.marshalObject(pvc) } @@ -74,6 +67,7 @@ func (*PVC) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "STATUS", "VOLUME", "CAPACITY", "ACCESS MODES", "STORAGECLASS", "AGE") } @@ -116,8 +110,3 @@ func (r *PVC) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*PVC) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/pvc_test.go b/internal/resource/pvc_test.go index 65298f14..f5d6ff57 100644 --- a/internal/resource/pvc_test.go +++ b/internal/resource/pvc_test.go @@ -63,7 +63,6 @@ func TestPVCListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 7, len(row.Deltas)) for _, d := range row.Deltas { @@ -72,19 +71,6 @@ func TestPVCListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestPVCListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sPVC(), nil) - l := resource.NewPVCListWithArgs("blee", resource.NewPVCWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sPVC() *v1.PersistentVolumeClaim { diff --git a/internal/resource/rc.go b/internal/resource/rc.go index 0076ac8e..216ed767 100644 --- a/internal/resource/rc.go +++ b/internal/resource/rc.go @@ -15,51 +15,43 @@ type ReplicationController struct { } // NewReplicationControllerList returns a new resource list. -func NewReplicationControllerList(ns string) List { - return NewReplicationControllerListWithArgs(ns, NewReplicationController()) +func NewReplicationControllerList(c k8s.Connection, ns string) List { + return newList( + ns, + "rc", + NewReplicationController(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewReplicationControllerListWithArgs returns a new resource list. -func NewReplicationControllerListWithArgs(ns string, res Resource) List { - return newList(ns, "rc", res, AllVerbsAccess|DescribeAccess) +// NewReplicationController instantiates a new ReplicationController. +func NewReplicationController(c k8s.Connection) *ReplicationController { + r := &ReplicationController{&Base{connection: c, resource: k8s.NewReplicationController(c)}, nil} + r.Factory = r + + return r } -// NewReplicationController instantiates a new Endpoint. -func NewReplicationController() *ReplicationController { - return NewReplicationControllerWithArgs(k8s.NewReplicationController()) -} - -// NewReplicationControllerWithArgs instantiates a new Endpoint. -func NewReplicationControllerWithArgs(r k8s.Res) *ReplicationController { - ep := &ReplicationController{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*ReplicationController) NewInstance(i interface{}) Columnar { - cm := NewReplicationController() - switch i.(type) { +// New builds a new ReplicationController instance from a k8s resource. +func (r *ReplicationController) New(i interface{}) Columnar { + c := NewReplicationController(r.connection) + switch instance := i.(type) { case *v1.ReplicationController: - cm.instance = i.(*v1.ReplicationController) + c.instance = instance case v1.ReplicationController: - ii := i.(v1.ReplicationController) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown ReplicationController type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal a deployment given a namespaced name. func (r *ReplicationController) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *ReplicationController) Marshal(path string) (string, error) { rc := i.(*v1.ReplicationController) rc.TypeMeta.APIVersion = "v1" rc.TypeMeta.Kind = "ReplicationController" + return r.marshalObject(rc) } @@ -76,6 +69,7 @@ func (*ReplicationController) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "DESIRED", "CURRENT", "READY", "AGE") } @@ -85,8 +79,8 @@ func (r *ReplicationController) Fields(ns string) Row { if ns == AllNamespaces { ff = append(ff, r.instance.Namespace) } - i := r.instance + return append(ff, i.Name, strconv.Itoa(int(*i.Spec.Replicas)), @@ -95,8 +89,3 @@ func (r *ReplicationController) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*ReplicationController) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/ro.go b/internal/resource/ro.go index 3a67a20e..218a1ded 100644 --- a/internal/resource/ro.go +++ b/internal/resource/ro.go @@ -15,53 +15,43 @@ type Role struct { } // NewRoleList returns a new resource list. -func NewRoleList(ns string) List { - return NewRoleListWithArgs(ns, NewRole()) +func NewRoleList(c k8s.Connection, ns string) List { + return newList( + ns, + "role", + NewRole(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewRoleListWithArgs returns a new resource list. -func NewRoleListWithArgs(ns string, res Resource) List { - l := newList(ns, "role", res, AllVerbsAccess|DescribeAccess) - l.xray = true - return l +// NewRole instantiates a new Role. +func NewRole(c k8s.Connection) *Role { + r := &Role{&Base{connection: c, resource: k8s.NewRole(c)}, nil} + r.Factory = r + + return r } -// NewRole instantiates a new Endpoint. -func NewRole() *Role { - return NewRoleWithArgs(k8s.NewRole()) -} - -// NewRoleWithArgs instantiates a new Endpoint. -func NewRoleWithArgs(r k8s.Res) *Role { - ep := &Role{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*Role) NewInstance(i interface{}) Columnar { - cm := NewRole() - switch i.(type) { +// New builds a new Role instance from a k8s resource. +func (r *Role) New(i interface{}) Columnar { + c := NewRole(r.connection) + switch instance := i.(type) { case *v1.Role: - cm.instance = i.(*v1.Role) + c.instance = instance case v1.Role: - ii := i.(v1.Role) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Role type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *Role) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -69,6 +59,7 @@ func (r *Role) Marshal(path string) (string, error) { role := i.(*v1.Role) role.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" role.TypeMeta.Kind = "Role" + return r.marshalObject(role) } @@ -78,6 +69,7 @@ func (*Role) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "AGE") } @@ -95,16 +87,7 @@ func (r *Role) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (r *Role) ExtFields() Properties { - i := r.instance - - return Properties{ - "Headers": Row{"RESOURCES", "NON-RESOURCE URLS", "RESOURCE NAMES", "VERBS"}, - "Rows": r.parseRules(i.Rules), - } -} - +// ---------------------------------------------------------------------------- // Helpers... func (r *Role) parseRules(pp []v1.PolicyRule) []Row { @@ -116,5 +99,6 @@ func (r *Role) parseRules(pp []v1.PolicyRule) []Row { acc[i][2] = strings.Join(p.ResourceNames, ", ") acc[i][3] = strings.Join(p.Verbs, ", ") } + return acc } diff --git a/internal/resource/ro_binding.go b/internal/resource/ro_binding.go index 3ce528a5..6829ed0e 100644 --- a/internal/resource/ro_binding.go +++ b/internal/resource/ro_binding.go @@ -15,51 +15,43 @@ type RoleBinding struct { } // NewRoleBindingList returns a new resource list. -func NewRoleBindingList(ns string) List { - return NewRoleBindingListWithArgs(ns, NewRoleBinding()) +func NewRoleBindingList(c k8s.Connection, ns string) List { + return newList( + ns, + "rolebinding", + NewRoleBinding(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewRoleBindingListWithArgs returns a new resource list. -func NewRoleBindingListWithArgs(ns string, res Resource) List { - return newList(ns, "rolebinding", res, AllVerbsAccess|DescribeAccess) +// NewRoleBinding instantiates a new RoleBinding. +func NewRoleBinding(c k8s.Connection) *RoleBinding { + r := &RoleBinding{&Base{connection: c, resource: k8s.NewRoleBinding(c)}, nil} + r.Factory = r + + return r } -// NewRoleBinding instantiates a new Endpoint. -func NewRoleBinding() *RoleBinding { - return NewRoleBindingWithArgs(k8s.NewRoleBinding()) -} - -// NewRoleBindingWithArgs instantiates a new Endpoint. -func NewRoleBindingWithArgs(r k8s.Res) *RoleBinding { - ep := &RoleBinding{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*RoleBinding) NewInstance(i interface{}) Columnar { - cm := NewRoleBinding() - switch i.(type) { +// New builds a new RoleBinding instance from a k8s resource. +func (r *RoleBinding) New(i interface{}) Columnar { + c := NewRoleBinding(r.connection) + switch instance := i.(type) { case *v1.RoleBinding: - cm.instance = i.(*v1.RoleBinding) + c.instance = instance case v1.RoleBinding: - ii := i.(v1.RoleBinding) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown RoleBinding type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *RoleBinding) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *RoleBinding) Marshal(path string) (string, error) { rb := i.(*v1.RoleBinding) rb.TypeMeta.APIVersion = "rbac.authorization.k8s.io/v1" rb.TypeMeta.Kind = "RoleBinding" + return r.marshalObject(rb) } @@ -76,6 +69,7 @@ func (*RoleBinding) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "ROLE", "SUBJECTS", "AGE") } @@ -95,11 +89,7 @@ func (r *RoleBinding) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (*RoleBinding) ExtFields() Properties { - return Properties{} -} - +// ---------------------------------------------------------------------------- // Helpers... func (r *RoleBinding) toSubjects(ss []v1.Subject) string { @@ -110,6 +100,7 @@ func (r *RoleBinding) toSubjects(ss []v1.Subject) string { acc += "," } } + return acc } diff --git a/internal/resource/ro_binding_test.go b/internal/resource/ro_binding_test.go index 43a3044c..90bff2ff 100644 --- a/internal/resource/ro_binding_test.go +++ b/internal/resource/ro_binding_test.go @@ -41,7 +41,6 @@ func TestRBListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, "blee", l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 4, len(row.Deltas)) for _, d := range row.Deltas { @@ -50,19 +49,6 @@ func TestRBListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestRBListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sRB(), nil) - l := resource.NewRoleBindingListWithArgs("blee", resource.NewRoleBindingWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sRB() *v1.RoleBinding { diff --git a/internal/resource/ro_test.go b/internal/resource/ro_test.go index 88f34025..932f751d 100644 --- a/internal/resource/ro_test.go +++ b/internal/resource/ro_test.go @@ -41,7 +41,6 @@ func TestRoleListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, "blee", l.GetNamespace()) - assert.True(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 2, len(row.Deltas)) for _, d := range row.Deltas { @@ -50,19 +49,6 @@ func TestRoleListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestRoleListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sRole(), nil) - l := resource.NewRoleListWithArgs("blee", resource.NewRoleWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 2, len(props)) -} - // Helpers... func k8sRole() *v1.Role { diff --git a/internal/resource/rs.go b/internal/resource/rs.go index a531d958..e1969bf3 100644 --- a/internal/resource/rs.go +++ b/internal/resource/rs.go @@ -15,51 +15,43 @@ type ReplicaSet struct { } // NewReplicaSetList returns a new resource list. -func NewReplicaSetList(ns string) List { - return NewReplicaSetListWithArgs(ns, NewReplicaSet()) +func NewReplicaSetList(c k8s.Connection, ns string) List { + return newList( + ns, + "rs", + NewReplicaSet(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewReplicaSetListWithArgs returns a new resource list. -func NewReplicaSetListWithArgs(ns string, res Resource) List { - return newList(ns, "rs", res, AllVerbsAccess|DescribeAccess) +// NewReplicaSet instantiates a new ReplicaSet. +func NewReplicaSet(c k8s.Connection) *ReplicaSet { + r := &ReplicaSet{&Base{connection: c, resource: k8s.NewReplicaSet(c)}, nil} + r.Factory = r + + return r } -// NewReplicaSet instantiates a new Endpoint. -func NewReplicaSet() *ReplicaSet { - return NewReplicaSetWithArgs(k8s.NewReplicaSet()) -} - -// NewReplicaSetWithArgs instantiates a new Endpoint. -func NewReplicaSetWithArgs(r k8s.Res) *ReplicaSet { - ep := &ReplicaSet{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*ReplicaSet) NewInstance(i interface{}) Columnar { - cm := NewReplicaSet() - switch i.(type) { +// New builds a new ReplicaSet instance from a k8s resource. +func (r *ReplicaSet) New(i interface{}) Columnar { + c := NewReplicaSet(r.connection) + switch instance := i.(type) { case *v1.ReplicaSet: - cm.instance = i.(*v1.ReplicaSet) + c.instance = instance case v1.ReplicaSet: - ii := i.(v1.ReplicaSet) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown ReplicaSet type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal a deployment given a namespaced name. func (r *ReplicaSet) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *ReplicaSet) Marshal(path string) (string, error) { rs := i.(*v1.ReplicaSet) rs.TypeMeta.APIVersion = "extensions/v1beta" rs.TypeMeta.Kind = "ReplicaSet" + return r.marshalObject(rs) } @@ -76,6 +69,7 @@ func (*ReplicaSet) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "DESIRED", "CURRENT", "READY", "AGE") } @@ -87,6 +81,7 @@ func (r *ReplicaSet) Fields(ns string) Row { } i := r.instance + return append(ff, i.Name, strconv.Itoa(int(*i.Spec.Replicas)), @@ -95,8 +90,3 @@ func (r *ReplicaSet) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*ReplicaSet) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/rs_test.go b/internal/resource/rs_test.go index a2070d32..aaaf3352 100644 --- a/internal/resource/rs_test.go +++ b/internal/resource/rs_test.go @@ -41,7 +41,6 @@ func TestReplicaSetListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, "blee", l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 5, len(row.Deltas)) for _, d := range row.Deltas { @@ -50,19 +49,6 @@ func TestReplicaSetListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestReplicaSetListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sReplicaSet(), nil) - l := resource.NewReplicaSetListWithArgs("blee", resource.NewReplicaSetWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sReplicaSet() *v1.ReplicaSet { diff --git a/internal/resource/sa.go b/internal/resource/sa.go index ea2ed4dc..de03d868 100644 --- a/internal/resource/sa.go +++ b/internal/resource/sa.go @@ -15,51 +15,43 @@ type ServiceAccount struct { } // NewServiceAccountList returns a new resource list. -func NewServiceAccountList(ns string) List { - return NewServiceAccountListWithArgs(ns, NewServiceAccount()) +func NewServiceAccountList(c k8s.Connection, ns string) List { + return newList( + ns, + "sa", + NewServiceAccount(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewServiceAccountListWithArgs returns a new resource list. -func NewServiceAccountListWithArgs(ns string, res Resource) List { - return newList(ns, "sa", res, AllVerbsAccess|DescribeAccess) +// NewServiceAccount instantiates a new ServiceAccount. +func NewServiceAccount(c k8s.Connection) *ServiceAccount { + s := &ServiceAccount{&Base{connection: c, resource: k8s.NewServiceAccount(c)}, nil} + s.Factory = s + + return s } -// NewServiceAccount instantiates a new Endpoint. -func NewServiceAccount() *ServiceAccount { - return NewServiceAccountWithArgs(k8s.NewServiceAccount()) -} - -// NewServiceAccountWithArgs instantiates a new Endpoint. -func NewServiceAccountWithArgs(r k8s.Res) *ServiceAccount { - ep := &ServiceAccount{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*ServiceAccount) NewInstance(i interface{}) Columnar { - cm := NewServiceAccount() - switch i.(type) { +// New builds a new ServiceAccount instance from a k8s resource. +func (r *ServiceAccount) New(i interface{}) Columnar { + c := NewServiceAccount(r.connection) + switch instance := i.(type) { case *v1.ServiceAccount: - cm.instance = i.(*v1.ServiceAccount) + c.instance = instance case v1.ServiceAccount: - ii := i.(v1.ServiceAccount) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown ServiceAccount type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *ServiceAccount) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *ServiceAccount) Marshal(path string) (string, error) { sa := i.(*v1.ServiceAccount) sa.TypeMeta.APIVersion = "v1" sa.TypeMeta.Kind = "ServiceAccount" + return r.marshalObject(sa) } @@ -76,6 +69,7 @@ func (*ServiceAccount) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "SECRET", "AGE") } @@ -93,8 +87,3 @@ func (r *ServiceAccount) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*ServiceAccount) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/sa_test.go b/internal/resource/sa_test.go index f42a2920..bd3e0dc1 100644 --- a/internal/resource/sa_test.go +++ b/internal/resource/sa_test.go @@ -75,7 +75,6 @@ func TestSAListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, resource.NotNamespaced, l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 3, len(row.Deltas)) for _, d := range row.Deltas { @@ -84,19 +83,6 @@ func TestSAListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestSAListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sSA(), nil) - l := resource.NewServiceAccountListWithArgs("blee", resource.NewServiceAccountWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sSA() *v1.ServiceAccount { diff --git a/internal/resource/secret.go b/internal/resource/secret.go index dc1105da..59d2f212 100644 --- a/internal/resource/secret.go +++ b/internal/resource/secret.go @@ -1,10 +1,10 @@ package resource import ( - "log" "strconv" "github.com/derailed/k9s/internal/k8s" + "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" ) @@ -15,51 +15,43 @@ type Secret struct { } // NewSecretList returns a new resource list. -func NewSecretList(ns string) List { - return NewSecretListWithArgs(ns, NewSecret()) -} - -// NewSecretListWithArgs returns a new resource list. -func NewSecretListWithArgs(ns string, res Resource) List { - return newList(ns, "secret", res, AllVerbsAccess|DescribeAccess) +func NewSecretList(c k8s.Connection, ns string) List { + return newList( + ns, + "secret", + NewSecret(c), + AllVerbsAccess|DescribeAccess, + ) } // NewSecret instantiates a new Secret. -func NewSecret() *Secret { - return NewSecretWithArgs(k8s.NewSecret()) +func NewSecret(c k8s.Connection) *Secret { + s := &Secret{&Base{connection: c, resource: k8s.NewSecret(c)}, nil} + s.Factory = s + + return s } -// NewSecretWithArgs instantiates a new Secret. -func NewSecretWithArgs(r k8s.Res) *Secret { - cm := &Secret{ - Base: &Base{ - caller: r, - }, - } - cm.creator = cm - return cm -} - -// NewInstance builds a new Secret instance from a k8s resource. -func (*Secret) NewInstance(i interface{}) Columnar { - cm := NewSecret() - switch i.(type) { +// New builds a new Secret instance from a k8s resource. +func (r *Secret) New(i interface{}) Columnar { + c := NewSecret(r.connection) + switch instance := i.(type) { case *v1.Secret: - cm.instance = i.(*v1.Secret) + c.instance = instance case v1.Secret: - ii := i.(v1.Secret) - cm.instance = &ii + c.instance = &instance default: - log.Fatalf("Unknown %#v", i) + log.Fatal().Msgf("unknown Secret type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *Secret) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *Secret) Marshal(path string) (string, error) { sec := i.(*v1.Secret) sec.TypeMeta.APIVersion = "v1" sec.TypeMeta.Kind = "Secret" + return r.marshalObject(sec) } @@ -76,6 +69,7 @@ func (*Secret) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "TYPE", "DATA", "AGE") } @@ -94,8 +88,3 @@ func (r *Secret) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*Secret) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/secret_test.go b/internal/resource/secret_test.go index 63fd920c..fcf0d8b6 100644 --- a/internal/resource/secret_test.go +++ b/internal/resource/secret_test.go @@ -114,20 +114,6 @@ func TestSecretListHasResource(t *testing.T) { assert.NotNil(t, l.Resource()) } -func TestSecretListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sSecret(), nil) - - l := resource.NewSecretListWithArgs("blee", resource.NewSecretWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - func TestSecretListData(t *testing.T) { setup(t) @@ -147,7 +133,6 @@ func TestSecretListData(t *testing.T) { assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, "blee", l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 4, len(row.Deltas)) for _, d := range row.Deltas { diff --git a/internal/resource/sts.go b/internal/resource/sts.go index c5e16c66..5627896b 100644 --- a/internal/resource/sts.go +++ b/internal/resource/sts.go @@ -15,51 +15,43 @@ type StatefulSet struct { } // NewStatefulSetList returns a new resource list. -func NewStatefulSetList(ns string) List { - return NewStatefulSetListWithArgs(ns, NewStatefulSet()) +func NewStatefulSetList(c k8s.Connection, ns string) List { + return newList( + ns, + "sts", + NewStatefulSet(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewStatefulSetListWithArgs returns a new resource list. -func NewStatefulSetListWithArgs(ns string, res Resource) List { - return newList(ns, "sts", res, AllVerbsAccess|DescribeAccess) +// NewStatefulSet instantiates a new StatefulSet. +func NewStatefulSet(c k8s.Connection) *StatefulSet { + s := &StatefulSet{&Base{connection: c, resource: k8s.NewStatefulSet(c)}, nil} + s.Factory = s + + return s } -// NewStatefulSet instantiates a new Endpoint. -func NewStatefulSet() *StatefulSet { - return NewStatefulSetWithArgs(k8s.NewStatefulSet()) -} - -// NewStatefulSetWithArgs instantiates a new Endpoint. -func NewStatefulSetWithArgs(r k8s.Res) *StatefulSet { - ep := &StatefulSet{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*StatefulSet) NewInstance(i interface{}) Columnar { - cm := NewStatefulSet() - switch i.(type) { +// New builds a new StatefulSet instance from a k8s resource. +func (r *StatefulSet) New(i interface{}) Columnar { + c := NewStatefulSet(r.connection) + switch instance := i.(type) { case *v1.StatefulSet: - cm.instance = i.(*v1.StatefulSet) + c.instance = instance case v1.StatefulSet: - ii := i.(v1.StatefulSet) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown StatefulSet type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. func (r *StatefulSet) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -67,6 +59,7 @@ func (r *StatefulSet) Marshal(path string) (string, error) { sts := i.(*v1.StatefulSet) sts.TypeMeta.APIVersion = "v1" sts.TypeMeta.Kind = "StatefulSet" + return r.marshalObject(sts) } @@ -76,6 +69,7 @@ func (*StatefulSet) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "DESIRED", "CURRENT", "AGE") } @@ -94,8 +88,3 @@ func (r *StatefulSet) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } - -// ExtFields returns extended fields in relation to headers. -func (*StatefulSet) ExtFields() Properties { - return Properties{} -} diff --git a/internal/resource/sts_test.go b/internal/resource/sts_test.go index a6ebb845..9ff8ba4f 100644 --- a/internal/resource/sts_test.go +++ b/internal/resource/sts_test.go @@ -75,7 +75,6 @@ func TestSTSListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, "blee", l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 4, len(row.Deltas)) for _, d := range row.Deltas { @@ -84,19 +83,6 @@ func TestSTSListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestSTSListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sSTS(), nil) - l := resource.NewStatefulSetListWithArgs("blee", resource.NewStatefulSetWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sSTS() *v1.StatefulSet { diff --git a/internal/resource/svc.go b/internal/resource/svc.go index fd68b9a7..4f38af92 100644 --- a/internal/resource/svc.go +++ b/internal/resource/svc.go @@ -19,52 +19,44 @@ type Service struct { } // NewServiceList returns a new resource list. -func NewServiceList(ns string) List { - return NewServiceListWithArgs(ns, NewService()) +func NewServiceList(c k8s.Connection, ns string) List { + return newList( + ns, + "svc", + NewService(c), + AllVerbsAccess|DescribeAccess, + ) } -// NewServiceListWithArgs returns a new resource list. -func NewServiceListWithArgs(ns string, res Resource) List { - return newList(ns, "svc", res, AllVerbsAccess|DescribeAccess) +// NewService instantiates a new Service. +func NewService(c k8s.Connection) *Service { + s := &Service{&Base{connection: c, resource: k8s.NewService(c)}, nil} + s.Factory = s + + return s } -// NewService instantiates a new Endpoint. -func NewService() *Service { - return NewServiceWithArgs(k8s.NewService()) -} - -// NewServiceWithArgs instantiates a new Endpoint. -func NewServiceWithArgs(r k8s.Res) *Service { - ep := &Service{ - Base: &Base{ - caller: r, - }, - } - ep.creator = ep - return ep -} - -// NewInstance builds a new Endpoint instance from a k8s resource. -func (*Service) NewInstance(i interface{}) Columnar { - cm := NewService() - switch i.(type) { +// New builds a new Service instance from a k8s resource. +func (r *Service) New(i interface{}) Columnar { + c := NewService(r.connection) + switch instance := i.(type) { case *v1.Service: - cm.instance = i.(*v1.Service) + c.instance = instance case v1.Service: - ii := i.(v1.Service) - cm.instance = &ii + c.instance = &instance default: - log.Fatal().Msgf("Unknown %#v", i) + log.Fatal().Msgf("unknown Service type %#v", i) } - cm.path = cm.namespacedName(cm.instance.ObjectMeta) - return cm + c.path = c.namespacedName(c.instance.ObjectMeta) + + return c } // Marshal resource to yaml. // BOZO!! Why you need to fill type info?? func (r *Service) Marshal(path string) (string, error) { ns, n := namespaced(path) - i, err := r.caller.Get(ns, n) + i, err := r.resource.Get(ns, n) if err != nil { return "", err } @@ -72,6 +64,7 @@ func (r *Service) Marshal(path string) (string, error) { svc := i.(*v1.Service) svc.TypeMeta.APIVersion = "v1" svc.TypeMeta.Kind = "Service" + return r.marshalObject(svc) } @@ -81,6 +74,7 @@ func (*Service) Header(ns string) Row { if ns == AllNamespaces { hh = append(hh, "NAMESPACE") } + return append(hh, "NAME", "TYPE", @@ -110,11 +104,7 @@ func (r *Service) Fields(ns string) Row { ) } -// ExtFields returns extended fields in relation to headers. -func (r *Service) ExtFields() Properties { - return Properties{} -} - +// ---------------------------------------------------------------------------- // Helpers... func getSvcExtIPS(svc *v1.Service) []string { @@ -139,6 +129,7 @@ func getSvcExtIPS(svc *v1.Service) []string { case v1.ServiceTypeExternalName: results = append(results, svc.Spec.ExternalName) } + return results } @@ -152,6 +143,7 @@ func lbIngressIP(s v1.LoadBalancerStatus) string { result = append(result, ingress[i].Hostname) } } + return strings.Join(result, ",") } @@ -163,6 +155,7 @@ func (*Service) toIPs(svcType v1.ServiceType, ips []string) string { return MissingValue } sort.Strings(ips) + return strings.Join(ips, ",") } @@ -179,5 +172,6 @@ func (*Service) toPorts(pp []v1.ServicePort) string { ports[i] += "╱" + string(p.Protocol) } } + return strings.Join(ports, " ") } diff --git a/internal/resource/svc_test.go b/internal/resource/svc_test.go index 676b6678..bfb514a8 100644 --- a/internal/resource/svc_test.go +++ b/internal/resource/svc_test.go @@ -86,7 +86,6 @@ func TestSVCListData(t *testing.T) { td := l.Data() assert.Equal(t, 1, len(td.Rows)) assert.Equal(t, "blee", l.GetNamespace()) - assert.False(t, l.HasXRay()) row := td.Rows["blee/fred"] assert.Equal(t, 6, len(row.Deltas)) for _, d := range row.Deltas { @@ -95,19 +94,6 @@ func TestSVCListData(t *testing.T) { assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) } -func TestSVCListDescribe(t *testing.T) { - setup(t) - - ca := NewMockCaller() - m.When(ca.Get("blee", "fred")).ThenReturn(k8sSVC(), nil) - l := resource.NewServiceListWithArgs("blee", resource.NewServiceWithArgs(ca)) - props, err := l.Describe("blee/fred") - - ca.VerifyWasCalledOnce().Get("blee", "fred") - assert.Nil(t, err) - assert.Equal(t, 0, len(props)) -} - // Helpers... func k8sSVC() *v1.Service { diff --git a/internal/views/alias.go b/internal/views/alias.go index a80428c2..036b4d67 100644 --- a/internal/views/alias.go +++ b/internal/views/alias.go @@ -35,7 +35,7 @@ func newAliasView(app *appView) *aliasView { v.actions[tcell.KeyEscape] = newKeyAction("Reset", v.resetCmd, false) v.actions[KeySlash] = newKeyAction("Filter", v.activateCmd, false) v.actions[KeyShiftR] = newKeyAction("Sort Resources", v.sortResourceCmd, true) - v.actions[KeyShiftG] = newKeyAction("Sort Groups", v.sortGroupCmd, true) + v.actions[KeyShiftO] = newKeyAction("Sort Groups", v.sortGroupCmd, true) ctx, cancel := context.WithCancel(context.TODO()) v.cancel = cancel @@ -128,7 +128,7 @@ func (v *aliasView) hints() hints { } func (v *aliasView) hydrate() resource.TableData { - cmds := helpCmds() + cmds := helpCmds(v.app.conn()) data := resource.TableData{ Header: resource.Row{"NAME", "RESOURCE", "APIGROUP"}, diff --git a/internal/views/app.go b/internal/views/app.go index 516192d6..31d88426 100644 --- a/internal/views/app.go +++ b/internal/views/app.go @@ -6,6 +6,7 @@ import ( "time" "github.com/derailed/k9s/internal/config" + "github.com/derailed/k9s/internal/k8s" "github.com/derailed/tview" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" @@ -34,7 +35,9 @@ type ( appView struct { *tview.Application + config *config.Config version string + flags *genericclioptions.ConfigFlags pages *tview.Pages content *tview.Pages flashView *flashView @@ -58,8 +61,8 @@ func init() { } // NewApp returns a K9s app instance. -func NewApp() *appView { - v := appView{Application: tview.NewApplication()} +func NewApp(cfg *config.Config) *appView { + v := appView{Application: tview.NewApplication(), config: cfg} { v.pages = tview.NewPages() v.actions = make(keyActions) @@ -92,6 +95,7 @@ func NewApp() *appView { func (a *appView) Init(v string, rate int, flags *genericclioptions.ConfigFlags) { a.version = v + a.flags = flags a.clusterInfoView.init() a.cmdBuff.addListener(a.cmdView) @@ -118,8 +122,18 @@ func (a *appView) Init(v string, rate int, flags *genericclioptions.ConfigFlags) a.SetRoot(a.pages, true) } +func (a *appView) conn() k8s.Connection { + return a.config.GetConnection() +} + // Run starts the application loop func (a *appView) Run() { + defer func() { + if err := recover(); err != nil { + log.Error().Msgf("%#v", err) + } + }() + go func() { <-time.After(splashTime * time.Second) a.showPage("main") @@ -256,7 +270,7 @@ func (a *appView) inject(p igniter) { var ctx context.Context { ctx, a.cancel = context.WithCancel(context.TODO()) - p.init(ctx, config.Root.ActiveNamespace()) + p.init(ctx, a.config.ActiveNamespace()) } a.content.AddPage("main", p, true, true) diff --git a/internal/views/cluster_info.go b/internal/views/cluster_info.go index daf9adc0..8c662e07 100644 --- a/internal/views/cluster_info.go +++ b/internal/views/cluster_info.go @@ -10,34 +10,37 @@ import ( type clusterInfoView struct { *tview.Table - app *appView + app *appView + cluster *resource.Cluster } func newInfoView(app *appView) *clusterInfoView { - return &clusterInfoView{app: app, Table: tview.NewTable()} + return &clusterInfoView{ + app: app, + Table: tview.NewTable(), + cluster: resource.NewCluster(app.conn(), &log.Logger), + } } func (v *clusterInfoView) init() { - cluster := resource.NewCluster() - var row int v.SetCell(row, 0, v.sectionCell("Context")) - v.SetCell(row, 1, v.infoCell(cluster.ContextName())) + v.SetCell(row, 1, v.infoCell(v.cluster.ContextName())) row++ v.SetCell(row, 0, v.sectionCell("Cluster")) - v.SetCell(row, 1, v.infoCell(cluster.ClusterName())) + v.SetCell(row, 1, v.infoCell(v.cluster.ClusterName())) row++ v.SetCell(row, 0, v.sectionCell("User")) - v.SetCell(row, 1, v.infoCell(cluster.UserName())) + v.SetCell(row, 1, v.infoCell(v.cluster.UserName())) row++ v.SetCell(row, 0, v.sectionCell("K9s Rev")) v.SetCell(row, 1, v.infoCell(v.app.version)) row++ - rev := cluster.Version() + rev := v.cluster.Version() v.SetCell(row, 0, v.sectionCell("K8s Rev")) v.SetCell(row, 1, v.infoCell(rev)) row++ @@ -67,24 +70,30 @@ func (*clusterInfoView) infoCell(t string) *tview.TableCell { func (v *clusterInfoView) refresh() { var row int - cluster := resource.NewCluster() - v.GetCell(row, 1).SetText(cluster.ContextName()) + v.GetCell(row, 1).SetText(v.cluster.ContextName()) row++ - v.GetCell(row, 1).SetText(cluster.ClusterName()) + v.GetCell(row, 1).SetText(v.cluster.ClusterName()) row++ - v.GetCell(row, 1).SetText(cluster.UserName()) + v.GetCell(row, 1).SetText(v.cluster.UserName()) row += 2 - v.GetCell(row, 1).SetText(cluster.Version()) + v.GetCell(row, 1).SetText(v.cluster.Version()) row++ - mx, err := cluster.Metrics() + nodes, err := v.cluster.GetNodes() if err != nil { - log.Warn().Msgf("%s", err) + log.Warn().Msgf("ClusterInfo %s", err) return } + mxNodes, err := v.cluster.GetNodesMetrics() + if err != nil { + log.Warn().Msgf("ClusterInfo %s", err) + return + } + + mx := v.cluster.Metrics(nodes, mxNodes) c := v.GetCell(row, 1) - c.SetText(deltas(c.Text, mx.CPU)) + c.SetText(deltas(c.Text, toPerc(mx.PercCPU))) row++ c = v.GetCell(row, 1) - c.SetText(deltas(c.Text, mx.Mem)) + c.SetText(deltas(c.Text, toPerc(mx.PercMEM))) } diff --git a/internal/views/command.go b/internal/views/command.go index eb62f568..110be059 100644 --- a/internal/views/command.go +++ b/internal/views/command.go @@ -3,8 +3,8 @@ package views import ( "fmt" - "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/resource" + "github.com/rs/zerolog/log" ) type command struct { @@ -29,14 +29,20 @@ func (c *command) previousCmd() (string, bool) { // DefaultCmd reset default command ie show pods. func (c *command) defaultCmd() { - c.pushCmd(config.Root.ActiveView()) - c.run(config.Root.ActiveView()) + c.pushCmd(c.app.config.ActiveView()) + c.run(c.app.config.ActiveView()) } // Helpers... // Exec the command by showing associated display. func (c *command) run(cmd string) bool { + defer func() { + if err := recover(); err != nil { + log.Debug().Msgf("Command failed %v", err) + } + }() + var v igniter switch cmd { case "q", "quit": @@ -47,14 +53,14 @@ func (c *command) run(cmd string) bool { return true default: if res, ok := resourceViews()[cmd]; ok { - v = res.viewFn(res.title, c.app, res.listFn(resource.DefaultNamespace), res.colorerFn) - c.app.flash(flashInfo, fmt.Sprintf("Viewing %s in namespace %s...", res.title, config.Root.ActiveNamespace())) + v = res.viewFn(res.title, c.app, res.listFn(c.app.conn(), resource.DefaultNamespace), res.colorerFn) + c.app.flash(flashInfo, fmt.Sprintf("Viewing %s in namespace %s...", res.title, c.app.config.ActiveNamespace())) c.exec(cmd, v) return true } } - res, ok := allCRDs()[cmd] + res, ok := allCRDs(c.app.conn())[cmd] if !ok { c.app.flash(flashWarn, fmt.Sprintf("Huh? `%s` command not found", cmd)) return false @@ -67,7 +73,7 @@ func (c *command) run(cmd string) bool { v = newResourceView( res.Kind, c.app, - resource.NewCustomList("", res.Group, res.Version, n), + resource.NewCustomList(c.app.conn(), "", res.Group, res.Version, n), defaultColorer, ) c.exec(cmd, v) @@ -76,8 +82,8 @@ func (c *command) run(cmd string) bool { func (c *command) exec(cmd string, v igniter) { if v != nil { - config.Root.SetActiveView(cmd) - config.Root.Save() + c.app.config.SetActiveView(cmd) + c.app.config.Save() c.app.inject(v) } } diff --git a/internal/views/context.go b/internal/views/context.go index 22b27a4c..6183a110 100644 --- a/internal/views/context.go +++ b/internal/views/context.go @@ -3,7 +3,6 @@ package views import ( "strings" - "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/resource" "github.com/gdamore/tcell" ) @@ -57,8 +56,8 @@ func (v *contextView) useContext(name string) error { return err } - config.Root.Reset() - config.Root.Save() + v.app.config.Reset() + v.app.config.Save() v.app.flash(flashInfo, "Switching context to", ctx) v.refresh() if tv, ok := v.GetPrimitive("ctx").(*tableView); ok { diff --git a/internal/views/flash.go b/internal/views/flash.go index 753143c8..1cd21462 100644 --- a/internal/views/flash.go +++ b/internal/views/flash.go @@ -40,7 +40,7 @@ func newFlashView(app *tview.Application, m string) *flashView { f.SetTextColor(tcell.ColorAqua) f.SetTextAlign(tview.AlignLeft) f.SetBorderPadding(0, 0, 1, 1) - f.SetText(m) + f.SetText("") } return &f } diff --git a/internal/views/helpers.go b/internal/views/helpers.go index 183ab523..a5b381ba 100644 --- a/internal/views/helpers.go +++ b/internal/views/helpers.go @@ -1,10 +1,15 @@ package views import ( + "fmt" "strconv" "strings" ) +func toPerc(f float64) string { + return fmt.Sprintf("%.0f%%", f) +} + func deltas(c, n string) string { if c == "n/a" { return n diff --git a/internal/views/log.go b/internal/views/log.go index 6a284a58..c526acc0 100644 --- a/internal/views/log.go +++ b/internal/views/log.go @@ -4,7 +4,6 @@ import ( "fmt" "io" - "github.com/derailed/k9s/internal/config" "github.com/derailed/tview" ) @@ -21,7 +20,7 @@ func newLogView(title string, parent loggable) *logView { v.SetDynamicColors(true) v.SetWrap(true) v.setTitle(parent.getSelection()) - v.SetMaxBuffer(config.Root.K9s.LogBufferSize) + v.SetMaxBuffer(parent.appView().config.K9s.LogBufferSize) } v.ansiWriter = tview.ANSIWriter(v) return &v diff --git a/internal/views/logs.go b/internal/views/logs.go index 72a335fe..cd82c216 100644 --- a/internal/views/logs.go +++ b/internal/views/logs.go @@ -6,7 +6,6 @@ import ( "strconv" "time" - "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/resource" "github.com/derailed/tview" "github.com/gdamore/tcell" @@ -163,7 +162,7 @@ func (v *logsView) doLoad(path, co string) error { if !ok { return fmt.Errorf("Resource %T is not tailable", v.parent.getList().Resource) } - maxBuff := int64(config.Root.K9s.LogRequestSize) + maxBuff := int64(v.parent.appView().config.K9s.LogRequestSize) cancelFn, err := res.Logs(c, ns, po, co, maxBuff, false) if err != nil { cancelFn() diff --git a/internal/views/namespace.go b/internal/views/namespace.go index 14058851..e4429319 100644 --- a/internal/views/namespace.go +++ b/internal/views/namespace.go @@ -8,6 +8,7 @@ import ( "github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/resource" "github.com/gdamore/tcell" + "github.com/rs/zerolog/log" ) const ( @@ -55,12 +56,12 @@ func (v *namespaceView) useNsCmd(evt *tcell.EventKey) *tcell.EventKey { } func (v *namespaceView) useNamespace(name string) { - if err := config.Root.SetActiveNamespace(name); err != nil { + if err := v.app.config.SetActiveNamespace(name); err != nil { v.app.flash(flashErr, err.Error()) } else { v.app.flash(flashInfo, fmt.Sprintf("Namespace %s is now active!", name)) } - config.Root.Save() + v.app.config.Save() } func (v *namespaceView) getSelectedItem() string { @@ -73,7 +74,7 @@ func (*namespaceView) cleanser(s string) string { func (v *namespaceView) decorate(data resource.TableData) resource.TableData { if _, ok := data.Rows[resource.AllNamespaces]; !ok { - if k8s.CanIAccess("", "list", "namespaces", "namespace.v1") { + if k8s.CanIAccess(v.app.conn().Config(), log.Logger, "", "list", "namespaces", "namespace.v1") { data.Rows[resource.AllNamespace] = &resource.RowEvent{ Action: resource.Unchanged, Fields: resource.Row{resource.AllNamespace, "Active", "0"}, @@ -81,14 +82,14 @@ func (v *namespaceView) decorate(data resource.TableData) resource.TableData { } } } - for k, v := range data.Rows { - if config.InList(config.Root.FavNamespaces(), k) { - v.Fields[0] += "+" - v.Action = resource.Unchanged + for k, r := range data.Rows { + if config.InList(v.app.config.FavNamespaces(), k) { + r.Fields[0] += "+" + r.Action = resource.Unchanged } - if config.Root.ActiveNamespace() == k { - v.Fields[0] += "(*)" - v.Action = resource.Unchanged + if v.app.config.ActiveNamespace() == k { + r.Fields[0] += "(*)" + r.Action = resource.Unchanged } } return data diff --git a/internal/views/no.go b/internal/views/no.go new file mode 100644 index 00000000..b339057d --- /dev/null +++ b/internal/views/no.go @@ -0,0 +1,35 @@ +package views + +import ( + "github.com/derailed/k9s/internal/resource" + "github.com/gdamore/tcell" +) + +type nodeView struct { + *resourceView +} + +func newNodeView(t string, app *appView, list resource.List, c colorerFn) resourceViewer { + v := nodeView{newResourceView(t, app, list, c).(*resourceView)} + { + v.extraActionsFn = v.extraActions + v.switchPage("no") + } + + return &v +} + +func (v *nodeView) extraActions(aa keyActions) { + aa[KeyShiftC] = newKeyAction("Sort CPU", v.sortColCmd(7, false), true) + aa[KeyShiftM] = newKeyAction("Sort MEM", v.sortColCmd(8, false), true) +} + +func (v *nodeView) sortColCmd(col int, asc bool) func(evt *tcell.EventKey) *tcell.EventKey { + return func(evt *tcell.EventKey) *tcell.EventKey { + t := v.getTV() + t.sortCol.index, t.sortCol.asc = t.nameColIndex()+col, asc + t.refresh() + + return nil + } +} diff --git a/internal/views/pod.go b/internal/views/pod.go index c9569c61..b3b9aeb5 100644 --- a/internal/views/pod.go +++ b/internal/views/pod.go @@ -3,7 +3,6 @@ package views import ( "fmt" - "github.com/derailed/k9s/internal/config" "github.com/derailed/k9s/internal/resource" "github.com/gdamore/tcell" "github.com/rs/zerolog/log" @@ -113,7 +112,7 @@ func (v *podView) shellIn(path, co string) { ns, po := namespaced(path) args := make([]string, 0, 12) args = append(args, "exec", "-it") - args = append(args, "--context", config.Root.K9s.CurrentContext) + args = append(args, "--context", v.app.config.K9s.CurrentContext) args = append(args, "-n", ns) args = append(args, po) if len(co) != 0 { @@ -129,7 +128,7 @@ func (v *podView) showLogs(path, co string, previous bool) { args := make([]string, 0, 10) args = append(args, "logs", "-f") args = append(args, "-n", ns) - args = append(args, "--context", config.Root.K9s.CurrentContext) + args = append(args, "--context", v.app.config.K9s.CurrentContext) if len(co) != 0 { args = append(args, "-c", co) v.app.flash(flashInfo, fmt.Sprintf("Viewing logs from container %s on pod %s", co, po)) diff --git a/internal/views/registrar.go b/internal/views/registrar.go index d1b1c661..4dc79524 100644 --- a/internal/views/registrar.go +++ b/internal/views/registrar.go @@ -7,9 +7,9 @@ import ( ) type ( - viewFn func(string, *appView, resource.List, colorerFn) resourceViewer - listFn func(string) resource.List - colorerFn func(string, *resource.RowEvent) tcell.Color + viewFn func(ns string, app *appView, list resource.List, colorer colorerFn) resourceViewer + listFn func(c k8s.Connection, ns string) resource.List + colorerFn func(ns string, evt *resource.RowEvent) tcell.Color resCmd struct { title string @@ -20,49 +20,50 @@ type ( } ) -func helpCmds() map[string]resCmd { +func helpCmds(c k8s.Connection) map[string]resCmd { cmdMap := resourceViews() cmds := make(map[string]resCmd, len(cmdMap)) for k, v := range cmdMap { cmds[k] = v } - for k, v := range allCRDs() { + for k, v := range allCRDs(c) { cmds[k] = resCmd{title: v.Kind, api: v.Group} } return cmds } -func allCRDs() map[string]k8s.APIGroup { +func allCRDs(c k8s.Connection) map[string]k8s.APIGroup { m := map[string]k8s.APIGroup{} - list := resource.NewCRDList(resource.AllNamespaces) - ll, _ := list.Resource().List(resource.AllNamespaces) - for _, l := range ll { - ff := l.ExtFields() + + crds, _ := resource. + NewCRDList(c, resource.AllNamespaces). + Resource(). + List(resource.AllNamespaces) + + for _, crd := range crds { + ff := crd.ExtFields() + grp := k8s.APIGroup{ - Version: ff["version"].(string), Group: ff["group"].(string), Kind: ff["kind"].(string), + Version: ff["version"].(string), } + + if p, ok := ff["plural"].(string); ok { + grp.Plural = p + m[p] = grp + } + + if s, ok := ff["singular"].(string); ok { + grp.Singular = s + m[s] = grp + } + if aa, ok := ff["aliases"].([]interface{}); ok { - if n, ok := ff["plural"].(string); ok { - grp.Plural = n - } - if n, ok := ff["singular"].(string); ok { - grp.Singular = n - } for _, a := range aa { m[a.(string)] = grp } - } else if s, ok := ff["singular"].(string); ok { - grp.Singular = s - if p, ok := ff["plural"].(string); ok { - grp.Plural = p - } - m[s] = grp - } else if s, ok := ff["plural"].(string); ok { - grp.Plural = s - m[s] = grp } } @@ -165,7 +166,7 @@ func resourceViews() map[string]resCmd { "no": { title: "Nodes", api: "", - viewFn: newResourceView, + viewFn: newNodeView, listFn: resource.NewNodeList, colorerFn: nsColorer, }, @@ -177,7 +178,7 @@ func resourceViews() map[string]resCmd { colorerFn: nsColorer, }, "pdb": { - title: "PodDiscruptionBudgets", + title: "PodDisruptionBudgets", api: "v1.beta1", viewFn: newResourceView, listFn: resource.NewPDBList, diff --git a/internal/views/resource.go b/internal/views/resource.go index d5a5fc53..5a682de5 100644 --- a/internal/views/resource.go +++ b/internal/views/resource.go @@ -88,7 +88,7 @@ func (v *resourceView) init(ctx context.Context, ns string) { return case <-time.After(time.Duration(initTick) * time.Second): v.refresh() - initTick = float64(config.Root.K9s.RefreshRate) + initTick = float64(v.app.config.K9s.RefreshRate) } } }(ctx) @@ -170,10 +170,10 @@ func (v *resourceView) describeCmd(evt *tcell.EventKey) *tcell.EventKey { return evt } sel := v.getSelectedItem() - raw, err := v.list.Resource().Describe(v.title, sel) + raw, err := v.list.Resource().Describe(v.title, sel, v.app.flags) if err != nil { v.app.flash(flashErr, err.Error()) - log.Warn().Msg(err.Error()) + log.Warn().Msgf("Describe %v", err.Error()) return evt } details := v.GetPrimitive("details").(*detailsView) @@ -220,7 +220,7 @@ func (v *resourceView) editCmd(evt *tcell.EventKey) *tcell.EventKey { args = append(args, "edit") args = append(args, v.list.GetName()) args = append(args, "-n", ns) - args = append(args, "--context", config.Root.K9s.CurrentContext) + args = append(args, "--context", v.app.config.K9s.CurrentContext) args = append(args, po) runK(v.app, args...) return evt @@ -249,8 +249,8 @@ func (v *resourceView) doSwitchNamespace(ns string) { v.getTV().resetTitle() v.getTV().Select(0, 0) v.app.cmdBuff.reset() - config.Root.SetActiveNamespace(v.selectedNS) - config.Root.Save() + v.app.config.SetActiveNamespace(v.selectedNS) + v.app.config.Save() } func (v *resourceView) refresh() { @@ -264,7 +264,7 @@ func (v *resourceView) refresh() { v.list.SetNamespace(v.selectedNS) } if err := v.list.Reconcile(); err != nil { - log.Warn().Msgf("%s", err) + log.Warn().Msgf("Reconcile %v", err) v.app.flash(flashErr, err.Error()) } data := v.list.Data() @@ -338,11 +338,11 @@ func (v *resourceView) refreshActions() { var nn []interface{} aa := make(keyActions) - if k8s.CanIAccess("", "list", "namespaces", "namespace.v1") { + if k8s.CanIAccess(v.app.conn().Config(), log.Logger, "", "list", "namespaces", "namespace.v1") { var err error - nn, err = k8s.NewNamespace().List(resource.AllNamespaces) + nn, err = k8s.NewNamespace(v.app.conn()).List(resource.AllNamespaces) if err != nil { - log.Warn().Msgf("%s", err) + log.Warn().Msgf("Access %v", err) v.app.flash(flashErr, err.Error()) } @@ -354,7 +354,7 @@ func (v *resourceView) refreshActions() { if v.list.Access(resource.NamespaceAccess) { v.namespaces = make(map[int]string, config.MaxFavoritesNS) - for i, n := range config.Root.FavNamespaces() { + for i, n := range v.app.config.FavNamespaces() { aa[tcell.Key(numKeys[i])] = newKeyAction(n, v.switchNamespaceCmd, true) v.namespaces[i] = n } diff --git a/internal/views/table.go b/internal/views/table.go index 56ddc310..c586d7c4 100644 --- a/internal/views/table.go +++ b/internal/views/table.go @@ -283,7 +283,7 @@ func (v *tableView) displayCol(index int, name string) string { func (v *tableView) doUpdate(data resource.TableData) { v.currentNS = data.Namespace if v.currentNS == resource.AllNamespaces { - v.actions[KeyShiftG] = newKeyAction("Sort Namespace", v.sortNamespaceCmd, true) + v.actions[KeyShiftS] = newKeyAction("Sort Namespace", v.sortNamespaceCmd, true) } else { delete(v.actions, KeyShiftS) } @@ -308,9 +308,14 @@ func (v *tableView) doUpdate(data resource.TableData) { } row++ + // for k := range data.Rows { + // log.Debug().Msgf("Keys: %s", k) + // } + keys := v.sortRows(data) groupKeys := map[string][]string{} for _, k := range keys { + // log.Debug().Msgf("RKEY: %s", k) grp := data.Rows[k].Fields[v.sortCol.index] if s, ok := groupKeys[grp]; ok { s = append(s, k)