diff --git a/internal/k8s/access.go b/internal/k8s/access.go index 4702ae85..b01ac196 100644 --- a/internal/k8s/access.go +++ b/internal/k8s/access.go @@ -3,14 +3,14 @@ package k8s import ( "strings" - "github.com/rs/zerolog/log" + "github.com/rs/zerolog" authorizationv1 "k8s.io/api/authorization/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/kubernetes" ) // CanIAccess checks if user has access to a certain resource. -func CanIAccess(ns, verb, name, resURL string) bool { +func CanIAccess(cfg *Config, log zerolog.Logger, ns, verb, name, resURL string) bool { _, gr := schema.ParseResourceArg(strings.ToLower(resURL)) sar := &authorizationv1.SelfSubjectAccessReview{ Spec: authorizationv1.SelfSubjectAccessReviewSpec{ @@ -25,15 +25,21 @@ func CanIAccess(ns, verb, name, resURL string) bool { }, } - auth, err := kubernetes.NewForConfig(conn.restConfigOrDie()) + rest, err := cfg.RESTConfig() if err != nil { - log.Warn().Msgf("%s", err) + log.Warn().Msgf("Access %s", err) + return false + } + + auth, err := kubernetes.NewForConfig(rest) + if err != nil { + log.Warn().Msgf("Access %s", err) return false } response, err := auth.AuthorizationV1().SelfSubjectAccessReviews().Create(sar) if err != nil { - log.Warn().Msgf("%s", err) + log.Warn().Msgf("Access %s", err) return false } diff --git a/internal/k8s/api.go b/internal/k8s/api.go index c660c808..84b9f353 100644 --- a/internal/k8s/api.go +++ b/internal/k8s/api.go @@ -1,13 +1,12 @@ package k8s import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" - clientcmd "k8s.io/client-go/tools/clientcmd" - clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - "k8s.io/kubernetes/pkg/kubectl/metricsutil" metricsapi "k8s.io/metrics/pkg/apis/metrics" versioned "k8s.io/metrics/pkg/client/clientset/versioned" ) @@ -15,10 +14,7 @@ import ( // NA Not available const NA = "n/a" -var ( - conn = &apiServer{} - supportedMetricsAPIVersions = []string{"v1beta1"} -) +var supportedMetricsAPIVersions = []string{"v1beta1"} type ( // APIGroup represents a K8s resource descriptor. @@ -32,81 +28,125 @@ type ( // Collection of empty interfaces. Collection []interface{} - // Res K8s api server calls. - Res interface { + // Cruder represent a crudable Kubernetes resource. + Cruder interface { Get(ns string, name string) (interface{}, error) List(ns string) (Collection, error) Delete(ns string, name string) error } - // Connection represents a k8s api server connection. - connection interface { - configAccess() clientcmd.ConfigAccess - restConfigOrDie() *restclient.Config - apiConfigOrDie() clientcmdapi.Config - dialOrDie() kubernetes.Interface - dynDialOrDie() dynamic.Interface - nsDialOrDie() dynamic.NamespaceableResourceInterface - mxsDial() (*versioned.Clientset, error) - heapsterDial() (*metricsutil.HeapsterMetricsClient, error) - hasMetricsServer() bool + // Connection represents a Kubenetes apiserver connection. + Connection interface { + Config() *Config + DialOrDie() kubernetes.Interface + SwitchContextOrDie(ctx string) + NSDialOrDie() dynamic.NamespaceableResourceInterface + RestConfigOrDie() *restclient.Config + MXDial() (*versioned.Clientset, error) + DynDialOrDie() dynamic.Interface + HasMetrics() bool + IsNamespaced(n string) bool + SupportsResource(group string) bool } - apiServer struct { + // APIClient represents a Kubernetes api client. + APIClient struct { config *Config client kubernetes.Interface dClient dynamic.Interface nsClient dynamic.NamespaceableResourceInterface - heapsterClient *metricsutil.HeapsterMetricsClient mxsClient *versioned.Clientset useMetricServer bool + log zerolog.Logger } ) // InitConnectionOrDie initialize connection from command line args. // Checks for connectivity with the api server. -func InitConnectionOrDie(config *Config) { - conn = &apiServer{config: config} +func InitConnectionOrDie(config *Config, logger zerolog.Logger) *APIClient { + conn := APIClient{config: config, log: logger} conn.useMetricServer = conn.supportsMxServer() + + return &conn } -func (a *apiServer) hasMetricsServer() bool { +// IsNamespaced check on server if given resource is namespaced +func (a *APIClient) IsNamespaced(res string) bool { + list, _ := a.DialOrDie().Discovery().ServerPreferredResources() + for _, l := range list { + log.Debug().Msgf("GV %s", l.GroupVersion) + for _, r := range l.APIResources { + if r.Name == res { + return r.Namespaced + } + } + } + return false +} + +// SupportsResource checks for resource supported version against the server. +func (a *APIClient) SupportsResource(group string) bool { + list, _ := a.DialOrDie().Discovery().ServerPreferredResources() + for _, l := range list { + if l.GroupVersion == group { + return true + } + } + return false +} + +// Config return a kubernetes configuration. +func (a *APIClient) Config() *Config { + return a.config +} + +// HasMetrics returns true if the cluster supports metrics. +func (a *APIClient) HasMetrics() bool { return a.useMetricServer } // DialOrDie returns a handle to api server or die. -func (a *apiServer) dialOrDie() kubernetes.Interface { +func (a *APIClient) DialOrDie() kubernetes.Interface { if a.client != nil { return a.client } var err error - if a.client, err = kubernetes.NewForConfig(a.restConfigOrDie()); err != nil { - panic(err) + if a.client, err = kubernetes.NewForConfig(a.RestConfigOrDie()); err != nil { + a.log.Panic().Err(err) } return a.client } -// DynDial returns a handle to the api server. -func (a *apiServer) dynDialOrDie() dynamic.Interface { +// RestConfigOrDie returns a rest api client. +func (a *APIClient) RestConfigOrDie() *restclient.Config { + cfg, err := a.config.RESTConfig() + if err != nil { + a.log.Panic().Err(err) + } + return cfg +} + +// DynDialOrDie returns a handle to a dynamic interface. +func (a *APIClient) DynDialOrDie() dynamic.Interface { if a.dClient != nil { return a.dClient } var err error - if a.dClient, err = dynamic.NewForConfig(a.restConfigOrDie()); err != nil { - panic(err) + if a.dClient, err = dynamic.NewForConfig(a.RestConfigOrDie()); err != nil { + a.log.Panic().Err(err) } - return a.dClient } -func (a *apiServer) nsDialOrDie() dynamic.NamespaceableResourceInterface { +// NSDialOrDie returns a handle to a namespaced resource. +func (a *APIClient) NSDialOrDie() dynamic.NamespaceableResourceInterface { if a.nsClient != nil { return a.nsClient } - a.nsClient = a.dynDialOrDie().Resource(schema.GroupVersionResource{ + a.nsClient = a.DynDialOrDie().Resource(schema.GroupVersionResource{ Group: "apiextensions.k8s.io", Version: "v1beta1", Resource: "customresourcedefinitions", @@ -114,40 +154,21 @@ func (a *apiServer) nsDialOrDie() dynamic.NamespaceableResourceInterface { return a.nsClient } -func (a *apiServer) heapsterDial() (*metricsutil.HeapsterMetricsClient, error) { - if a.heapsterClient != nil { - return a.heapsterClient, nil - } - - a.heapsterClient = metricsutil.NewHeapsterMetricsClient( - a.dialOrDie().CoreV1(), - metricsutil.DefaultHeapsterNamespace, - metricsutil.DefaultHeapsterScheme, - metricsutil.DefaultHeapsterService, - metricsutil.DefaultHeapsterPort, - ) - return a.heapsterClient, nil -} - -func (a *apiServer) mxsDial() (*versioned.Clientset, error) { +// MXDial returns a handle to the metrics server. +func (a *APIClient) MXDial() (*versioned.Clientset, error) { if a.mxsClient != nil { return a.mxsClient, nil } var err error - a.mxsClient, err = versioned.NewForConfig(a.restConfigOrDie()) + if a.mxsClient, err = versioned.NewForConfig(a.RestConfigOrDie()); err != nil { + a.log.Debug().Err(err) + } return a.mxsClient, err } -func (a *apiServer) restConfigOrDie() *restclient.Config { - cfg, err := a.config.RESTConfig() - if err != nil { - panic(err) - } - return cfg -} - -func (a *apiServer) switchContextOrDie(ctx string) { +// SwitchContextOrDie handles kubeconfig context switches. +func (a *APIClient) SwitchContextOrDie(ctx string) { currentCtx, err := a.config.CurrentContextName() if err != nil { panic(err) @@ -162,13 +183,12 @@ func (a *apiServer) switchContextOrDie(ctx string) { } } -func (a *apiServer) reset() { - a.client, a.dClient, a.nsClient = nil, nil, nil - a.heapsterClient, a.mxsClient = nil, nil +func (a *APIClient) reset() { + a.client, a.dClient, a.nsClient, a.mxsClient = nil, nil, nil, nil } -func (a *apiServer) supportsMxServer() bool { - apiGroups, err := a.dialOrDie().Discovery().ServerGroups() +func (a *APIClient) supportsMxServer() bool { + apiGroups, err := a.DialOrDie().Discovery().ServerGroups() if err != nil { return false } @@ -177,6 +197,7 @@ func (a *apiServer) supportsMxServer() bool { if discoveredAPIGroup.Name != metricsapi.GroupName { continue } + for _, version := range discoveredAPIGroup.Versions { for _, supportedVersion := range supportedMetricsAPIVersions { if version.Version == supportedVersion { @@ -185,5 +206,6 @@ func (a *apiServer) supportsMxServer() bool { } } } + return false } diff --git a/internal/k8s/cluster.go b/internal/k8s/cluster.go index 97440774..9a9d65ee 100644 --- a/internal/k8s/cluster.go +++ b/internal/k8s/cluster.go @@ -1,20 +1,26 @@ package k8s -import "github.com/rs/zerolog/log" +import ( + "github.com/rs/zerolog" +) // Cluster represents a Kubernetes cluster. -type Cluster struct{} +type Cluster struct { + Connection + + logger *zerolog.Logger +} // NewCluster instantiates a new cluster. -func NewCluster() *Cluster { - return &Cluster{} +func NewCluster(c Connection, l *zerolog.Logger) *Cluster { + return &Cluster{c, l} } // Version returns the current cluster git version. func (c *Cluster) Version() (string, error) { - rev, err := conn.dialOrDie().Discovery().ServerVersion() + rev, err := c.DialOrDie().Discovery().ServerVersion() if err != nil { - log.Warn().Msgf("%s", err) + c.logger.Warn().Msgf("%s", err) return "", err } return rev.GitVersion, nil @@ -22,9 +28,9 @@ func (c *Cluster) Version() (string, error) { // ContextName returns the currently active context. func (c *Cluster) ContextName() string { - ctx, err := conn.config.CurrentContextName() + ctx, err := c.Config().CurrentContextName() if err != nil { - log.Warn().Msgf("%s", err) + c.logger.Warn().Msgf("%s", err) return "N/A" } return ctx @@ -32,9 +38,9 @@ func (c *Cluster) ContextName() string { // ClusterName return the currently active cluster name. func (c *Cluster) ClusterName() string { - ctx, err := conn.config.CurrentClusterName() + ctx, err := c.Config().CurrentClusterName() if err != nil { - log.Warn().Msgf("%s", err) + c.logger.Warn().Msgf("%s", err) return "N/A" } return ctx @@ -42,9 +48,9 @@ func (c *Cluster) ClusterName() string { // UserName returns the currently active user. func (c *Cluster) UserName() string { - usr, err := conn.config.CurrentUserName() + usr, err := c.Config().CurrentUserName() if err != nil { - log.Warn().Msgf("%s", err) + c.logger.Warn().Msgf("%s", err) return "N/A" } return usr diff --git a/internal/k8s/cluster_role.go b/internal/k8s/cluster_role.go index 5f09917f..ec3eec91 100644 --- a/internal/k8s/cluster_role.go +++ b/internal/k8s/cluster_role.go @@ -5,28 +5,26 @@ import ( ) // ClusterRole represents a Kubernetes ClusterRole -type ClusterRole struct{} +type ClusterRole struct { + Connection +} // NewClusterRole returns a new ClusterRole. -func NewClusterRole() Res { - return &ClusterRole{} +func NewClusterRole(c Connection) Cruder { + return &ClusterRole{c} } // Get a cluster role. -func (*ClusterRole) Get(_, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().RbacV1().ClusterRoles().Get(n, opts) +func (c *ClusterRole) Get(_, n string) (interface{}, error) { + return c.DialOrDie().RbacV1().ClusterRoles().Get(n, metav1.GetOptions{}) } // List all ClusterRoles on a cluster. -func (*ClusterRole) List(_ string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().RbacV1().ClusterRoles().List(opts) +func (c *ClusterRole) List(_ string) (Collection, error) { + rr, err := c.DialOrDie().RbacV1().ClusterRoles().List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*ClusterRole) List(_ string) (Collection, error) { } // Delete a ClusterRole. -func (*ClusterRole) Delete(_, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().RbacV1().ClusterRoles().Delete(n, &opts) +func (c *ClusterRole) Delete(_, n string) error { + return c.DialOrDie().RbacV1().ClusterRoles().Delete(n, nil) } diff --git a/internal/k8s/cluster_roleb.go b/internal/k8s/cluster_roleb.go index db795a4e..153e1ac3 100644 --- a/internal/k8s/cluster_roleb.go +++ b/internal/k8s/cluster_roleb.go @@ -5,28 +5,26 @@ import ( ) // ClusterRoleBinding represents a Kubernetes ClusterRoleBinding -type ClusterRoleBinding struct{} +type ClusterRoleBinding struct { + Connection +} // NewClusterRoleBinding returns a new ClusterRoleBinding. -func NewClusterRoleBinding() Res { - return &ClusterRoleBinding{} +func NewClusterRoleBinding(c Connection) Cruder { + return &ClusterRoleBinding{c} } // Get a service. -func (*ClusterRoleBinding) Get(_, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().RbacV1().ClusterRoleBindings().Get(n, opts) +func (c *ClusterRoleBinding) Get(_, n string) (interface{}, error) { + return c.DialOrDie().RbacV1().ClusterRoleBindings().Get(n, metav1.GetOptions{}) } // List all ClusterRoleBindings on a cluster. -func (*ClusterRoleBinding) List(_ string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().RbacV1().ClusterRoleBindings().List(opts) +func (c *ClusterRoleBinding) List(_ string) (Collection, error) { + rr, err := c.DialOrDie().RbacV1().ClusterRoleBindings().List(metav1.ListOptions{}) if err != nil { return Collection{}, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*ClusterRoleBinding) List(_ string) (Collection, error) { } // Delete a ClusterRoleBinding. -func (*ClusterRoleBinding) Delete(_, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().RbacV1().ClusterRoleBindings().Delete(n, &opts) +func (c *ClusterRoleBinding) Delete(_, n string) error { + return c.DialOrDie().RbacV1().ClusterRoleBindings().Delete(n, nil) } diff --git a/internal/k8s/cm.go b/internal/k8s/cm.go index 6fd6a0bb..82166d94 100644 --- a/internal/k8s/cm.go +++ b/internal/k8s/cm.go @@ -5,28 +5,26 @@ import ( ) // ConfigMap represents a Kubernetes ConfigMap -type ConfigMap struct{} +type ConfigMap struct { + Connection +} // NewConfigMap returns a new ConfigMap. -func NewConfigMap() Res { - return &ConfigMap{} +func NewConfigMap(c Connection) Cruder { + return &ConfigMap{c} } // Get a ConfigMap. func (c *ConfigMap) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().ConfigMaps(ns).Get(n, opts) + return c.DialOrDie().CoreV1().ConfigMaps(ns).Get(n, metav1.GetOptions{}) } // List all ConfigMaps in a given namespace. func (c *ConfigMap) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().ConfigMaps(ns).List(opts) + rr, err := c.DialOrDie().CoreV1().ConfigMaps(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -37,6 +35,5 @@ func (c *ConfigMap) List(ns string) (Collection, error) { // Delete a ConfigMap. func (c *ConfigMap) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().ConfigMaps(ns).Delete(n, &opts) + return c.DialOrDie().CoreV1().ConfigMaps(ns).Delete(n, nil) } diff --git a/internal/k8s/config.go b/internal/k8s/config.go index 35c49816..c91405da 100644 --- a/internal/k8s/config.go +++ b/internal/k8s/config.go @@ -14,9 +14,6 @@ import ( const defaultNamespace = "default" -// KubeConfig represents kubeconfig settings. -var KubeConfig *Config - // Config tracks a kubernetes configuration. type Config struct { flags *genericclioptions.ConfigFlags @@ -28,8 +25,7 @@ type Config struct { // NewConfig returns a new k8s config or an error if the flags are invalid. func NewConfig(f *genericclioptions.ConfigFlags) *Config { - KubeConfig = &Config{flags: f} - return KubeConfig + return &Config{flags: f} } // Flags returns configuration flags. @@ -60,6 +56,7 @@ func (c *Config) CurrentContextName() (string, error) { if isSet(c.flags.Context) { return *c.flags.Context, nil } + cfg, err := c.RawConfig() if err != nil { return "", err @@ -82,11 +79,11 @@ func (c *Config) GetContext(n string) (*clientcmdapi.Context, error) { // Contexts fetch all available contexts. func (c *Config) Contexts() (map[string]*clientcmdapi.Context, error) { - var cc map[string]*clientcmdapi.Context cfg, err := c.RawConfig() if err != nil { - return cc, err + return nil, err } + return cfg.Contexts, nil } @@ -97,18 +94,18 @@ func (c *Config) DelContext(n string) error { return err } delete(cfg.Contexts, n) + return clientcmd.ModifyConfig(c.clientConfig.ConfigAccess(), cfg, true) } // ContextNames fetch all available contexts. func (c *Config) ContextNames() ([]string, error) { - var cc []string cfg, err := c.RawConfig() if err != nil { - return cc, err + return nil, err } - cc = make([]string, 0, len(cfg.Contexts)) + cc := make([]string, 0, len(cfg.Contexts)) for n := range cfg.Contexts { cc = append(cc, n) } @@ -121,6 +118,7 @@ func (c *Config) ClusterNameFromContext(ctx string) (string, error) { if err != nil { return "", err } + if ctx, ok := cfg.Contexts[ctx]; ok { return ctx.Cluster, nil } @@ -137,32 +135,31 @@ func (c *Config) CurrentClusterName() (string, error) { if err != nil { return "", err } + current := cfg.CurrentContext if isSet(c.flags.Context) { current = *c.flags.Context } + if ctx, ok := cfg.Contexts[current]; ok { return ctx.Cluster, nil } + return "", errors.New("unable to locate current cluster") } // ClusterNames fetch all kubeconfig defined clusters. func (c *Config) ClusterNames() ([]string, error) { - var cc []string - if err := c.configFromFlags(); err != nil { - return cc, err - } - cfg, err := c.RawConfig() if err != nil { - return cc, err + return nil, err } - cc = make([]string, 0, len(cfg.Clusters)) + cc := make([]string, 0, len(cfg.Clusters)) for name := range cfg.Clusters { cc = append(cc, name) } + return cc, nil } @@ -171,6 +168,7 @@ func (c *Config) CurrentUserName() (string, error) { if isSet(c.flags.Impersonate) { return *c.flags.Impersonate, nil } + if isSet(c.flags.AuthInfoName) { return *c.flags.AuthInfoName, nil } @@ -187,6 +185,7 @@ func (c *Config) CurrentUserName() (string, error) { if ctx, ok := cfg.Contexts[current]; ok { return ctx.AuthInfo, nil } + return "", errors.New("unable to locate current cluster") } @@ -211,48 +210,39 @@ func (c *Config) CurrentNamespaceName() (string, error) { return ctx.Namespace, nil } } + return "", fmt.Errorf("No active namespace specified") } // NamespaceNames fetch all available namespaces on current cluster. -func (c *Config) NamespaceNames() ([]string, error) { - ll, err := NewNamespace().List("") - if err != nil { - return []string{}, err +func (c *Config) NamespaceNames(nns []v1.Namespace) []string { + nn := make([]string, 0, len(nns)) + for _, ns := range nns { + nn = append(nn, ns.Name) } - nn := make([]string, 0, len(ll)) - for _, n := range ll { - if ns, ok := n.(v1.Namespace); ok { - nn = append(nn, ns.Name) - } - } - return nn, nil + + return nn } // ConfigAccess return the current kubeconfig api server access configuration. func (c *Config) ConfigAccess() (clientcmd.ConfigAccess, error) { - var acc clientcmd.ConfigAccess - if err := c.configFromFlags(); err != nil { - return acc, err - } + c.ensureConfig() return c.clientConfig.ConfigAccess(), nil } // RawConfig fetch the current kubeconfig with no overrides. func (c *Config) RawConfig() (clientcmdapi.Config, error) { if c.rawConfig != nil { - if c.rawConfig.CurrentContext != c.currentContext { - log.Debug().Msgf("Context switch detected... %s vs %s", c.rawConfig.CurrentContext, c.currentContext) - c.currentContext = c.rawConfig.CurrentContext - c.reset() + if c.rawConfig.CurrentContext == c.currentContext { + return *c.rawConfig, nil } + log.Debug().Msgf("Context switch detected... %s vs %s", c.rawConfig.CurrentContext, c.currentContext) + c.currentContext = c.rawConfig.CurrentContext + c.reset() } if c.rawConfig == nil { - if err := c.configFromFlags(); err != nil { - return clientcmdapi.Config{}, err - } - log.Debug().Msg("Loading RawConfig...") + c.ensureConfig() cfg, err := c.clientConfig.RawConfig() if err != nil { return cfg, err @@ -260,36 +250,38 @@ func (c *Config) RawConfig() (clientcmdapi.Config, error) { c.rawConfig = &cfg c.currentContext = cfg.CurrentContext } + return *c.rawConfig, nil } // RESTConfig fetch the current REST api service connection. func (c *Config) RESTConfig() (*restclient.Config, error) { - var err error - if c.restConfig == nil { - if err = c.configFromFlags(); err != nil { - return nil, err - } - c.restConfig, err = c.flags.ToRESTConfig() - if err != nil { - return c.restConfig, err - } - log.Debug().Msgf("Connecting to API Server %s", c.restConfig.Host) + if c.restConfig != nil { + return c.restConfig, nil } + + var err error + if c.restConfig, err = c.flags.ToRESTConfig(); err != nil { + return nil, err + } + log.Debug().Msgf("Connecting to API Server %s", c.restConfig.Host) + return c.restConfig, nil } +func (c *Config) ensureConfig() { + if c.clientConfig != nil { + return + } + + log.Debug().Msg("Loading raw config from flags...") + c.clientConfig = c.flags.ToRawKubeConfigLoader() + return +} + // ---------------------------------------------------------------------------- // Helpers... -func (c *Config) configFromFlags() error { - if c.clientConfig == nil { - log.Debug().Msg("Loading raw config from flags...") - c.clientConfig = c.flags.ToRawKubeConfigLoader() - } - return nil -} - func isSet(s *string) bool { return s != nil && len(*s) != 0 } diff --git a/internal/k8s/config_test.go b/internal/k8s/config_test.go index d1911238..5e568c16 100644 --- a/internal/k8s/config_test.go +++ b/internal/k8s/config_test.go @@ -8,6 +8,8 @@ import ( "github.com/derailed/k9s/internal/k8s" "github.com/rs/zerolog" "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" ) @@ -228,3 +230,22 @@ func TestConfigBadConfig(t *testing.T) { _, err := cfg.RESTConfig() assert.NotNil(t, err) } + +func TestNamespaceNames(t *testing.T) { + kubeConfig := "./assets/config" + + flags := genericclioptions.ConfigFlags{ + KubeConfig: &kubeConfig, + } + + cfg := k8s.NewConfig(&flags) + + nn := []v1.Namespace{ + {ObjectMeta: metav1.ObjectMeta{Name: "ns1"}}, + {ObjectMeta: metav1.ObjectMeta{Name: "ns2"}}, + } + + nns := cfg.NamespaceNames(nn) + assert.Equal(t, 2, len(nns)) + assert.Equal(t, []string{"ns1", "ns2"}, nns) +} diff --git a/internal/k8s/context.go b/internal/k8s/context.go index a6c22685..92abb496 100644 --- a/internal/k8s/context.go +++ b/internal/k8s/context.go @@ -9,7 +9,7 @@ import ( // ContextRes represents a Kubernetes clusters configurations. type ContextRes interface { - Res + Cruder Switch(n string) error } @@ -17,11 +17,17 @@ type ContextRes interface { type NamedContext struct { Name string Context *api.Context + config *Config +} + +// NewNamedContext returns a new named context. +func NewNamedContext(c *Config, n string, ctx *api.Context) *NamedContext { + return &NamedContext{Name: n, Context: ctx, config: c} } // MustCurrentContextName return the active context name. func (c *NamedContext) MustCurrentContextName() string { - cl, err := conn.config.CurrentContextName() + cl, err := c.config.CurrentContextName() if err != nil { panic(err) } @@ -29,16 +35,18 @@ func (c *NamedContext) MustCurrentContextName() string { } // Context represents a Kubernetes Context. -type Context struct{} +type Context struct { + Connection +} // NewContext returns a new Context. -func NewContext() Res { - return &Context{} +func NewContext(c Connection) Cruder { + return &Context{c} } // Get a Context. -func (*Context) Get(_, n string) (interface{}, error) { - ctx, err := conn.config.GetContext(n) +func (c *Context) Get(_, n string) (interface{}, error) { + ctx, err := c.Config().GetContext(n) if err != nil { return nil, err } @@ -46,43 +54,45 @@ func (*Context) Get(_, n string) (interface{}, error) { } // List all Contexts on the current cluster. -func (*Context) List(string) (Collection, error) { - ctxs, err := conn.config.Contexts() +func (c *Context) List(string) (Collection, error) { + ctxs, err := c.Config().Contexts() if err != nil { - return Collection{}, err + return nil, err } cc := make([]interface{}, 0, len(ctxs)) for k, v := range ctxs { - cc = append(cc, &NamedContext{k, v}) + cc = append(cc, NewNamedContext(c.Config(), k, v)) } + return cc, nil } // Delete a Context. -func (*Context) Delete(_, n string) error { - ctx, err := conn.config.CurrentContextName() +func (c *Context) Delete(_, n string) error { + ctx, err := c.Config().CurrentContextName() if err != nil { return err } if ctx == n { return fmt.Errorf("trying to delete your current context %s", n) } - return conn.config.DelContext(n) + return c.Config().DelContext(n) } // Switch to another context. -func (*Context) Switch(n string) error { - conn.switchContextOrDie(n) +func (c *Context) Switch(ctx string) error { + c.SwitchContextOrDie(ctx) return nil } // KubeUpdate modifies kubeconfig default context. func (c *Context) KubeUpdate(n string) error { - c.Switch(n) - acc := clientcmd.NewDefaultPathOptions() - config, err := conn.config.RawConfig() + config, err := c.Config().RawConfig() if err != nil { return err } - return clientcmd.ModifyConfig(acc, config, true) + c.Switch(n) + return clientcmd.ModifyConfig( + clientcmd.NewDefaultPathOptions(), config, true, + ) } diff --git a/internal/k8s/crd.go b/internal/k8s/crd.go index a35000a0..91b61b80 100644 --- a/internal/k8s/crd.go +++ b/internal/k8s/crd.go @@ -5,28 +5,26 @@ import ( ) // CRD represents a Kubernetes CRD -type CRD struct{} +type CRD struct { + Connection +} // NewCRD returns a new CRD. -func NewCRD() Res { - return &CRD{} +func NewCRD(c Connection) Cruder { + return &CRD{c} } // Get a CRD. -func (*CRD) Get(_, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.nsDialOrDie().Get(n, opts) +func (c *CRD) Get(_, n string) (interface{}, error) { + return c.NSDialOrDie().Get(n, metav1.GetOptions{}) } // List all CRDs in a given namespace. -func (*CRD) List(string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.nsDialOrDie().List(opts) +func (c *CRD) List(string) (Collection, error) { + rr, err := c.NSDialOrDie().List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*CRD) List(string) (Collection, error) { } // Delete a CRD. -func (*CRD) Delete(_, n string) error { - opts := metav1.DeleteOptions{} - return conn.nsDialOrDie().Delete(n, &opts) +func (c *CRD) Delete(_, n string) error { + return c.NSDialOrDie().Delete(n, nil) } diff --git a/internal/k8s/cronjob.go b/internal/k8s/cronjob.go index 26daad21..c0a38880 100644 --- a/internal/k8s/cronjob.go +++ b/internal/k8s/cronjob.go @@ -10,28 +10,26 @@ import ( const maxJobNameSize = 42 // CronJob represents a Kubernetes CronJob. -type CronJob struct{} +type CronJob struct { + Connection +} // NewCronJob returns a new CronJob. -func NewCronJob() Res { - return &CronJob{} +func NewCronJob(c Connection) Cruder { + return &CronJob{c} } // Get a CronJob. func (c *CronJob) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().BatchV1beta1().CronJobs(ns).Get(n, opts) + return c.DialOrDie().BatchV1beta1().CronJobs(ns).Get(n, metav1.GetOptions{}) } // List all CronJobs in a given namespace. func (c *CronJob) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().BatchV1beta1().CronJobs(ns).List(opts) + rr, err := c.DialOrDie().BatchV1beta1().CronJobs(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -42,22 +40,20 @@ func (c *CronJob) List(ns string) (Collection, error) { // Delete a CronJob. func (c *CronJob) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().BatchV1beta1().CronJobs(ns).Delete(n, &opts) + return c.DialOrDie().BatchV1beta1().CronJobs(ns).Delete(n, nil) } // Run the job associated with this cronjob. func (c *CronJob) Run(ns, n string) error { - i, err := c.Get(ns, n) + cj, err := c.Get(ns, n) if err != nil { return err } - - cronJob := i.(*batchv1beta1.CronJob) + cronJob := cj.(*batchv1beta1.CronJob) var jobName = cronJob.Name if len(cronJob.Name) >= maxJobNameSize { - jobName = cronJob.Name[0:41] + jobName = cronJob.Name[0:maxJobNameSize] } job := &batchv1.Job{ @@ -68,6 +64,7 @@ func (c *CronJob) Run(ns, n string) error { }, Spec: cronJob.Spec.JobTemplate.Spec, } - _, err = conn.dialOrDie().BatchV1().Jobs(ns).Create(job) + + _, err = c.DialOrDie().BatchV1().Jobs(ns).Create(job) return err } diff --git a/internal/k8s/dp.go b/internal/k8s/dp.go index c5d8ad8c..00ea93c8 100644 --- a/internal/k8s/dp.go +++ b/internal/k8s/dp.go @@ -5,28 +5,26 @@ import ( ) // Deployment represents a Kubernetes Deployment. -type Deployment struct{} +type Deployment struct { + Connection +} // NewDeployment returns a new Deployment. -func NewDeployment() Res { - return &Deployment{} +func NewDeployment(c Connection) Cruder { + return &Deployment{c} } // Get a deployment. -func (*Deployment) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().Apps().Deployments(ns).Get(n, opts) +func (d *Deployment) Get(ns, n string) (interface{}, error) { + return d.DialOrDie().Apps().Deployments(ns).Get(n, metav1.GetOptions{}) } // List all Deployments in a given namespace. -func (*Deployment) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().Apps().Deployments(ns).List(opts) +func (d *Deployment) List(ns string) (Collection, error) { + rr, err := d.DialOrDie().Apps().Deployments(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*Deployment) List(ns string) (Collection, error) { } // Delete a Deployment. -func (*Deployment) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().Apps().Deployments(ns).Delete(n, &opts) +func (d *Deployment) Delete(ns, n string) error { + return d.DialOrDie().Apps().Deployments(ns).Delete(n, nil) } diff --git a/internal/k8s/ds.go b/internal/k8s/ds.go index 3c73a9bc..a8cafb65 100644 --- a/internal/k8s/ds.go +++ b/internal/k8s/ds.go @@ -5,28 +5,26 @@ import ( ) // DaemonSet represents a Kubernetes DaemonSet -type DaemonSet struct{} +type DaemonSet struct { + Connection +} // NewDaemonSet returns a new DaemonSet. -func NewDaemonSet() Res { - return &DaemonSet{} +func NewDaemonSet(c Connection) Cruder { + return &DaemonSet{c} } // Get a DaemonSet. -func (*DaemonSet) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().ExtensionsV1beta1().DaemonSets(ns).Get(n, opts) +func (d *DaemonSet) Get(ns, n string) (interface{}, error) { + return d.DialOrDie().ExtensionsV1beta1().DaemonSets(ns).Get(n, metav1.GetOptions{}) } // List all DaemonSets in a given namespace. -func (*DaemonSet) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().ExtensionsV1beta1().DaemonSets(ns).List(opts) +func (d *DaemonSet) List(ns string) (Collection, error) { + rr, err := d.DialOrDie().ExtensionsV1beta1().DaemonSets(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*DaemonSet) List(ns string) (Collection, error) { } // Delete a DaemonSet. -func (*DaemonSet) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().ExtensionsV1beta1().DaemonSets(ns).Delete(n, &opts) +func (d *DaemonSet) Delete(ns, n string) error { + return d.DialOrDie().ExtensionsV1beta1().DaemonSets(ns).Delete(n, nil) } diff --git a/internal/k8s/ep.go b/internal/k8s/ep.go index e8bdcc33..d974ce11 100644 --- a/internal/k8s/ep.go +++ b/internal/k8s/ep.go @@ -5,28 +5,26 @@ import ( ) // Endpoints represents a Kubernetes Endpoints. -type Endpoints struct{} +type Endpoints struct { + Connection +} // NewEndpoints returns a new Endpoints. -func NewEndpoints() Res { - return &Endpoints{} +func NewEndpoints(c Connection) Cruder { + return &Endpoints{c} } // Get a Endpoint. -func (*Endpoints) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().Endpoints(ns).Get(n, opts) +func (e *Endpoints) Get(ns, n string) (interface{}, error) { + return e.DialOrDie().CoreV1().Endpoints(ns).Get(n, metav1.GetOptions{}) } // List all Endpoints in a given namespace. -func (*Endpoints) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().Endpoints(ns).List(opts) +func (e *Endpoints) List(ns string) (Collection, error) { + rr, err := e.DialOrDie().CoreV1().Endpoints(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*Endpoints) List(ns string) (Collection, error) { } // Delete a Endpoint. -func (*Endpoints) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().Endpoints(ns).Delete(n, &opts) +func (e *Endpoints) Delete(ns, n string) error { + return e.DialOrDie().CoreV1().Endpoints(ns).Delete(n, nil) } diff --git a/internal/k8s/evt.go b/internal/k8s/evt.go index 6d1ca35e..ba51c7f3 100644 --- a/internal/k8s/evt.go +++ b/internal/k8s/evt.go @@ -5,28 +5,26 @@ import ( ) // Event represents a Kubernetes Event. -type Event struct{} +type Event struct { + Connection +} // NewEvent returns a new Event. -func NewEvent() Res { - return &Event{} +func NewEvent(c Connection) Cruder { + return &Event{c} } // Get a Event. -func (*Event) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().Events(ns).Get(n, opts) +func (e *Event) Get(ns, n string) (interface{}, error) { + return e.DialOrDie().CoreV1().Events(ns).Get(n, metav1.GetOptions{}) } // List all Events in a given namespace. -func (*Event) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().Events(ns).List(opts) +func (e *Event) List(ns string) (Collection, error) { + rr, err := e.DialOrDie().CoreV1().Events(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*Event) List(ns string) (Collection, error) { } // Delete an Event. -func (*Event) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().Events(ns).Delete(n, &opts) +func (e *Event) Delete(ns, n string) error { + return e.DialOrDie().CoreV1().Events(ns).Delete(n, nil) } diff --git a/internal/k8s/helpers.go b/internal/k8s/helpers.go new file mode 100644 index 00000000..c6ed23a4 --- /dev/null +++ b/internal/k8s/helpers.go @@ -0,0 +1,18 @@ +package k8s + +import ( + "math" +) + +const megaByte = 1024 * 1024 + +func asMi(v int64) float64 { + return float64(v) / megaByte +} + +func toPerc(v1, v2 float64) float64 { + if v2 == 0 { + return 0 + } + return math.Round((v1 / v2) * 100) +} diff --git a/internal/k8s/helpers_test.go b/internal/k8s/helpers_test.go new file mode 100644 index 00000000..3c20f1c9 --- /dev/null +++ b/internal/k8s/helpers_test.go @@ -0,0 +1,36 @@ +package k8s + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestToPerc(t *testing.T) { + uu := []struct { + v1, v2, e float64 + }{ + {0, 0, 0}, + {100, 200, 50}, + {200, 100, 200}, + } + + for _, u := range uu { + assert.Equal(t, u.e, toPerc(u.v1, u.v2)) + } +} + +func TestAsMi(t *testing.T) { + uu := []struct { + v int64 + e float64 + }{ + {0, 0}, + {2 * megaByte, 2}, + {10 * megaByte, 10}, + } + + for _, u := range uu { + assert.Equal(t, u.e, asMi(u.v)) + } +} diff --git a/internal/k8s/hpa.go b/internal/k8s/hpa.go index 31a227b4..51933d15 100644 --- a/internal/k8s/hpa.go +++ b/internal/k8s/hpa.go @@ -5,38 +5,37 @@ import ( ) // HPA represents am HorizontalPodAutoscaler. -type HPA struct{} +type HPA struct { + Connection +} // NewHPA returns a new HPA. -func NewHPA() Res { - return &HPA{} +func NewHPA(c Connection) Cruder { + return &HPA{c} } // Get a HPA. -func (*HPA) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Get(n, opts) +func (h *HPA) Get(ns, n string) (interface{}, error) { + return h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Get(n, metav1.GetOptions{}) } // List all HPAs in a given namespace. -func (*HPA) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).List(opts) +func (h *HPA) List(ns string) (Collection, error) { + rr, err := h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r } - return cc, nil } // Delete a HPA. -func (*HPA) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Delete(n, &opts) +func (h *HPA) Delete(ns, n string) error { + if h.SupportsResource("autoscaling/v2beta1") { + return h.DialOrDie().AutoscalingV2beta1().HorizontalPodAutoscalers(ns).Delete(n, nil) + } + return h.DialOrDie().AutoscalingV2beta2().HorizontalPodAutoscalers(ns).Delete(n, nil) } diff --git a/internal/k8s/ing.go b/internal/k8s/ing.go index 7f01b145..32b11f5f 100644 --- a/internal/k8s/ing.go +++ b/internal/k8s/ing.go @@ -5,28 +5,26 @@ import ( ) // Ingress represents a Kubernetes Ingress. -type Ingress struct{} +type Ingress struct { + Connection +} // NewIngress returns a new Ingress. -func NewIngress() Res { - return &Ingress{} +func NewIngress(c Connection) Cruder { + return &Ingress{c} } // Get a Ingress. -func (*Ingress) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().ExtensionsV1beta1().Ingresses(ns).Get(n, opts) +func (i *Ingress) Get(ns, n string) (interface{}, error) { + return i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).Get(n, metav1.GetOptions{}) } // List all Ingresss in a given namespace. -func (*Ingress) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().ExtensionsV1beta1().Ingresses(ns).List(opts) +func (i *Ingress) List(ns string) (Collection, error) { + rr, err := i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*Ingress) List(ns string) (Collection, error) { } // Delete a Ingress. -func (*Ingress) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().ExtensionsV1beta1().Ingresses(ns).Delete(n, &opts) +func (i *Ingress) Delete(ns, n string) error { + return i.DialOrDie().ExtensionsV1beta1().Ingresses(ns).Delete(n, nil) } diff --git a/internal/k8s/job.go b/internal/k8s/job.go index 2792dd13..5916807e 100644 --- a/internal/k8s/job.go +++ b/internal/k8s/job.go @@ -4,35 +4,40 @@ import ( "fmt" "strings" - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" restclient "k8s.io/client-go/rest" ) -// Job represents a Kubernetes Job. -type Job struct{} +type ( + // Job represents a Kubernetes Job. + Job struct { + Connection + } + + // Loggable represents a K8s resource that has containers and can be logged. + Loggable interface { + Containers(ns, n string, includeInit bool) ([]string, error) + Logs(ns, n, co string, lines int64, previous bool) *restclient.Request + } +) // NewJob returns a new Job. -func NewJob() Res { - return &Job{} +func NewJob(c Connection) Cruder { + return &Job{c} } // Get a Job. -func (*Job) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().BatchV1().Jobs(ns).Get(n, opts) +func (j *Job) Get(ns, n string) (interface{}, error) { + return j.DialOrDie().BatchV1().Jobs(ns).Get(n, metav1.GetOptions{}) } // List all Jobs in a given namespace. -func (*Job) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().BatchV1().Jobs(ns).List(opts) +func (j *Job) List(ns string) (Collection, error) { + rr, err := j.DialOrDie().BatchV1().Jobs(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -42,9 +47,8 @@ func (*Job) List(ns string) (Collection, error) { } // Delete a Job. -func (*Job) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().BatchV1().Jobs(ns).Delete(n, &opts) +func (j *Job) Delete(ns, n string) error { + return j.DialOrDie().BatchV1().Jobs(ns).Delete(n, nil) } // Containers returns all container names on job. @@ -53,8 +57,7 @@ func (j *Job) Containers(ns, n string, includeInit bool) ([]string, error) { if err != nil { return nil, err } - log.Debug().Msgf("Containers found assoc pod %v", pod) - return NewPod().(Loggable).Containers(ns, pod, includeInit) + return NewPod(j).(Loggable).Containers(ns, pod, includeInit) } // Logs fetch container logs for a given job and container. @@ -63,16 +66,15 @@ func (j *Job) Logs(ns, n, co string, lines int64, prev bool) *restclient.Request if err != nil { return nil } - return NewPod().(Loggable).Logs(ns, pod, co, lines, prev) + return NewPod(j).(Loggable).Logs(ns, pod, co, lines, prev) } // Events retrieved jobs events. -func (*Job) Events(ns, n string) (*v1.EventList, error) { - e := conn.dialOrDie().Core().Events(ns) - sel := e.GetFieldSelector(&n, &ns, nil, nil) - opts := metav1.ListOptions{FieldSelector: sel.String()} - ee, err := e.List(opts) - return ee, err +func (j *Job) Events(ns, n string) (*v1.EventList, error) { + e := j.DialOrDie().Core().Events(ns) + return e.List(metav1.ListOptions{ + FieldSelector: e.GetFieldSelector(&n, &ns, nil, nil).String(), + }) } func (j *Job) assocPod(ns, n string) (string, error) { diff --git a/internal/k8s/mapper.go b/internal/k8s/mapper.go index 0a146f07..9d13f4b2 100644 --- a/internal/k8s/mapper.go +++ b/internal/k8s/mapper.go @@ -14,12 +14,17 @@ import ( "k8s.io/client-go/restmapper" ) -// RestMapping holds k8s resource mapping -// BOZO!! Has to be a better way... -var RestMapping = &RestMapper{} +var ( + // RestMapping holds k8s resource mapping + // BOZO!! Has to be a better way... + RestMapping = &RestMapper{} + toFileName = regexp.MustCompile(`[^(\w/\.)]`) +) // RestMapper map resource to REST mapping ie kind, group, version. -type RestMapper struct{} +type RestMapper struct { + Connection +} // Find a mapping given a resource name. func (*RestMapper) Find(res string) (*meta.RESTMapping, error) { @@ -30,8 +35,8 @@ func (*RestMapper) Find(res string) (*meta.RESTMapping, error) { } // ToRESTMapper map resources to kind, and map kind and version to interfaces for manipulating K8s objects. -func (*RestMapper) ToRESTMapper() (meta.RESTMapper, error) { - rc := conn.restConfigOrDie() +func (r *RestMapper) ToRESTMapper() (meta.RESTMapper, error) { + rc := r.RestConfigOrDie() httpCacheDir := filepath.Join(mustHomeDir(), ".kube", "http-cache") discCacheDir := filepath.Join(mustHomeDir(), ".kube", "cache", "discovery", toHostDir(rc.Host)) @@ -45,11 +50,8 @@ func (*RestMapper) ToRESTMapper() (meta.RESTMapper, error) { return expander, nil } -var toFileName = regexp.MustCompile(`[^(\w/\.)]`) - func toHostDir(host string) string { h := strings.Replace(strings.Replace(host, "https://", "", 1), "http://", "", 1) - // now do a simple collapse of non-AZ09 characters. Collisions are possible but unlikely. Even if we do collide the problem is short lived return toFileName.ReplaceAllString(h, "_") } diff --git a/internal/k8s/metrics.go b/internal/k8s/metrics.go index 088d712a..38fe90d5 100644 --- a/internal/k8s/metrics.go +++ b/internal/k8s/metrics.go @@ -1,218 +1,145 @@ package k8s import ( - "fmt" - "math" - "path" - - "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - metricsapi "k8s.io/metrics/pkg/apis/metrics" - metricsV1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1" + mv1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" ) type ( // MetricsServer serves cluster metrics for nodes and pods. - MetricsServer struct{} - - // Metric tracks resource metrics. - Metric struct { - CPU string - Mem string - AvailCPU string - AvailMem string + MetricsServer struct { + Connection } + + // NodeMetrics describes raw node metrics. + NodeMetrics struct { + CurrentCPU int64 + CurrentMEM float64 + AvailCPU int64 + AvailMEM float64 + TotalCPU int64 + TotalMEM float64 + } + + // PodMetrics represent an aggregation of all pod containers metrics. + PodMetrics struct { + CurrentCPU int64 + CurrentMEM float64 + } + + // ClusterMetrics summarizes total node metrics as percentages. + ClusterMetrics struct { + PercCPU float64 + PercMEM float64 + } + + // NodesMetrics tracks usage metrics per nodes. + NodesMetrics map[string]NodeMetrics + + // PodsMetrics tracks usage metrics per pods. + PodsMetrics map[string]PodMetrics ) // NewMetricsServer return a metric server instance. -func NewMetricsServer() *MetricsServer { - return &MetricsServer{} +func NewMetricsServer(c Connection) *MetricsServer { + return &MetricsServer{c} } -// NodeMetrics retrieves all nodes metrics -func (m *MetricsServer) NodeMetrics() (Metric, error) { - var mx Metric - - opts := metav1.ListOptions{} - nn, err := conn.dialOrDie().CoreV1().Nodes().List(opts) - if err != nil { - log.Warn().Msgf("%s", err) - return mx, err - } - - nods := make([]string, len(nn.Items)) - var maxCPU, maxMem float64 - for i, n := range nn.Items { - nods[i] = n.Name - c := n.Status.Allocatable["cpu"] - maxCPU += float64(c.MilliValue()) - m := n.Status.Allocatable["memory"] - maxMem += float64(m.Value() / (1024 * 1024)) - } - - mm, err := m.getNodeMetrics() - if err != nil { - log.Warn().Msgf("%s", err) - return mx, err - } - - var cpu, mem float64 - for _, n := range nods { - for _, m := range mm.Items { - if m.Name == n { - cpu += float64(m.Usage.Cpu().MilliValue()) - mem += float64(m.Usage.Memory().Value() / (1024 * 1024)) - } - } - } - mx = Metric{ - CPU: fmt.Sprintf("%0.f%%", math.Round((cpu/maxCPU)*100)), - Mem: fmt.Sprintf("%0.f%%", math.Round((mem/maxMem)*100)), - } - - return mx, nil -} - -// PodMetrics retrieves all pods metrics -func (m *MetricsServer) PodMetrics() (map[string]Metric, error) { - mx := map[string]Metric{} - - mm, err := m.getPodMetrics() - if err != nil { - log.Warn().Msgf("%s", err) - return mx, err - } - - for _, m := range mm.Items { - var cpu, mem int64 - for _, c := range m.Containers { - cpu += c.Usage.Cpu().MilliValue() - mem += c.Usage.Memory().Value() / (1024 * 1024) - } - pa := path.Join(m.Namespace, m.Name) - mx[pa] = Metric{CPU: fmt.Sprintf("%dm", cpu), Mem: fmt.Sprintf("%dMi", mem)} - } - - return mx, nil -} - -// PerNodeMetrics retrieves all nodes metrics -func (m *MetricsServer) PerNodeMetrics(nn []v1.Node) (map[string]Metric, error) { - mx := map[string]Metric{} - - mm, err := m.getNodeMetrics() - if err != nil { - log.Warn().Msgf("%s", err) - return mx, err - } - - for _, n := range nn { - acpu := n.Status.Allocatable["cpu"] - amem := n.Status.Allocatable["memory"] - var cpu, mem int64 - for _, m := range mm.Items { - if m.Name == n.Name { - cpu += m.Usage.Cpu().MilliValue() - mem += m.Usage.Memory().Value() / (1024 * 1024) - } - } - mx[n.Name] = Metric{ - CPU: fmt.Sprintf("%dm", cpu), - Mem: fmt.Sprintf("%dMi", mem), - AvailCPU: fmt.Sprintf("%dm", acpu.MilliValue()), - AvailMem: fmt.Sprintf("%dMi", amem.Value()/(1024*1024)), +// NodesMetrics retrieves metrics for a given set of nodes. +func (m *MetricsServer) NodesMetrics(nodes []v1.Node, metrics []mv1beta1.NodeMetrics, mmx NodesMetrics) { + for _, n := range nodes { + mmx[n.Name] = NodeMetrics{ + AvailCPU: n.Status.Allocatable.Cpu().MilliValue(), + AvailMEM: asMi(n.Status.Allocatable.Memory().Value()), + TotalCPU: n.Status.Capacity.Cpu().MilliValue(), + TotalMEM: asMi(n.Status.Capacity.Memory().Value()), } } - return mx, nil + for _, c := range metrics { + if mx, ok := mmx[c.Name]; ok { + mx.CurrentCPU = c.Usage.Cpu().MilliValue() + mx.CurrentMEM = asMi(c.Usage.Memory().Value()) + mmx[c.Name] = mx + } + } } -func (m *MetricsServer) getPodMetrics() (*metricsapi.PodMetricsList, error) { - if conn.hasMetricsServer() { - return m.podMetricsViaService() +// ClusterLoad retrieves all cluster nodes metrics. +func (m *MetricsServer) ClusterLoad(nodes []v1.Node, metrics []mv1beta1.NodeMetrics) ClusterMetrics { + nodeMetrics := make(NodesMetrics, len(nodes)) + + for _, n := range nodes { + nodeMetrics[n.Name] = NodeMetrics{ + AvailCPU: n.Status.Allocatable.Cpu().MilliValue(), + AvailMEM: asMi(n.Status.Allocatable.Memory().Value()), + TotalCPU: n.Status.Capacity.Cpu().MilliValue(), + TotalMEM: asMi(n.Status.Capacity.Memory().Value()), + } } - var mx *metricsapi.PodMetricsList - conn, err := conn.heapsterDial() - if err != nil { - log.Warn().Msgf("%s", err) - return mx, err + for _, mx := range metrics { + if m, ok := nodeMetrics[mx.Name]; ok { + m.CurrentCPU = mx.Usage.Cpu().MilliValue() + m.CurrentMEM = asMi(mx.Usage.Memory().Value()) + nodeMetrics[mx.Name] = m + } } - return conn.GetPodMetrics("", "", true, labels.Everything()) + var cpu, tcpu, mem, tmem float64 + for _, mx := range nodeMetrics { + cpu += float64(mx.CurrentCPU) + tcpu += float64(mx.TotalCPU) + mem += mx.CurrentMEM + tmem += mx.TotalMEM + } + + return ClusterMetrics{PercCPU: toPerc(cpu, tcpu), PercMEM: toPerc(mem, tmem)} } -func (m *MetricsServer) getNodeMetrics() (*metricsapi.NodeMetricsList, error) { - if conn.hasMetricsServer() { - return m.nodeMetricsViaService() - } +// // HasMetrics check if cluster has a metrics server. +// func (m *MetricsServer) HasMetrics() bool { +// return m.HasMetrics() +// } - var mx *metricsapi.NodeMetricsList - conn, err := conn.heapsterDial() +// FetchNodesMetrics return all metrics for pods in a given namespace. +func (m *MetricsServer) FetchNodesMetrics() ([]mv1beta1.NodeMetrics, error) { + client, err := m.MXDial() if err != nil { - log.Warn().Msgf("%s", err) - return mx, err - } - - return conn.GetNodeMetrics("", labels.Everything().String()) -} - -func (*MetricsServer) nodeMetricsViaService() (*metricsapi.NodeMetricsList, error) { - var mx *metricsapi.NodeMetricsList - - clt, err := conn.mxsDial() - if err != nil { - log.Warn().Msgf("%s", err) - return mx, err - } - - selector := labels.Everything() - var versionedMetrics *metricsV1beta1api.NodeMetricsList - mc := clt.Metrics() - nm := mc.NodeMetricses() - versionedMetrics, err = nm.List(metav1.ListOptions{LabelSelector: selector.String()}) - if err != nil { - log.Warn().Msgf("%s", err) return nil, err } - metrics := &metricsapi.NodeMetricsList{} - err = metricsV1beta1api.Convert_v1beta1_NodeMetricsList_To_metrics_NodeMetricsList(versionedMetrics, metrics, nil) + list, err := client.Metrics().NodeMetricses().List(metav1.ListOptions{}) if err != nil { - log.Warn().Msgf("%s", err) return nil, err } - - return metrics, nil + return list.Items, nil } -func (*MetricsServer) podMetricsViaService() (*metricsapi.PodMetricsList, error) { - var mx *metricsapi.PodMetricsList - - clt, err := conn.mxsDial() +// FetchPodsMetrics return all metrics for pods in a given namespace. +func (m *MetricsServer) FetchPodsMetrics(ns string) ([]mv1beta1.PodMetrics, error) { + client, err := m.MXDial() if err != nil { - log.Warn().Msgf("%s", err) - return mx, err - } - - selector := labels.Everything() - var versionedMetrics *metricsV1beta1api.PodMetricsList - mc := clt.Metrics() - nm := mc.PodMetricses("") - versionedMetrics, err = nm.List(metav1.ListOptions{LabelSelector: selector.String()}) - if err != nil { - log.Warn().Msgf("%s", err) return nil, err } - metrics := &metricsapi.PodMetricsList{} - err = metricsV1beta1api.Convert_v1beta1_PodMetricsList_To_metrics_PodMetricsList(versionedMetrics, metrics, nil) + list, err := client.Metrics().PodMetricses(ns).List(metav1.ListOptions{}) if err != nil { - log.Warn().Msgf("%s", err) return nil, err } - - return metrics, nil + return list.Items, nil +} + +// PodsMetrics retrieves metrics for all pods in a given namespace. +func (m *MetricsServer) PodsMetrics(pods []mv1beta1.PodMetrics, mmx PodsMetrics) { + // Compute all pod's containers metrics. + for _, p := range pods { + var mx PodMetrics + for _, c := range p.Containers { + mx.CurrentCPU += c.Usage.Cpu().MilliValue() + mx.CurrentMEM += asMi(c.Usage.Memory().Value()) + } + mmx[p.Namespace+"/"+p.Name] = mx + } } diff --git a/internal/k8s/metrics_test.go b/internal/k8s/metrics_test.go new file mode 100644 index 00000000..1900cd5a --- /dev/null +++ b/internal/k8s/metrics_test.go @@ -0,0 +1,196 @@ +package k8s + +import ( + "testing" + + "github.com/stretchr/testify/assert" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1beta1 "k8s.io/metrics/pkg/apis/metrics/v1beta1" +) + +func TestPodsMetrics(t *testing.T) { + m := NewMetricsServer(nil) + + metrics := v1beta1.PodMetricsList{ + Items: []v1beta1.PodMetrics{ + makeMxPod("p1", "1", "4Gi"), + makeMxPod("p2", "50m", "1Mi"), + }, + } + + mmx := make(PodsMetrics) + m.PodsMetrics(metrics.Items, mmx) + assert.Equal(t, 2, len(mmx)) + + mx, ok := mmx["default/p1"] + assert.True(t, ok) + assert.Equal(t, int64(3000), mx.CurrentCPU) + assert.Equal(t, float64(12288), mx.CurrentMEM) +} + +func BenchmarkPodsMetrics(b *testing.B) { + m := NewMetricsServer(nil) + + metrics := v1beta1.PodMetricsList{ + Items: []v1beta1.PodMetrics{ + makeMxPod("p1", "1", "4Gi"), + makeMxPod("p2", "50m", "1Mi"), + makeMxPod("p3", "50m", "1Mi"), + }, + } + mmx := make(PodsMetrics, 3) + + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + m.PodsMetrics(metrics.Items, mmx) + } +} + +func TestNodesMetrics(t *testing.T) { + m := NewMetricsServer(nil) + + nodes := v1.NodeList{ + Items: []v1.Node{ + makeNode("n1", "32", "128Gi", "50m", "2Mi"), + makeNode("n2", "8", "4Gi", "50m", "2Mi"), + }, + } + + metrics := v1beta1.NodeMetricsList{ + Items: []v1beta1.NodeMetrics{ + makeMxNode("n1", "10", "8Gi"), + makeMxNode("n2", "50m", "1Mi"), + }, + } + + mmx := make(NodesMetrics) + m.NodesMetrics(nodes.Items, metrics.Items, mmx) + assert.Equal(t, 2, len(mmx)) + mx, ok := mmx["n1"] + assert.True(t, ok) + assert.Equal(t, int64(32000), mx.TotalCPU) + assert.Equal(t, float64(131072), mx.TotalMEM) + assert.Equal(t, int64(50), mx.AvailCPU) + assert.Equal(t, float64(2), mx.AvailMEM) + assert.Equal(t, int64(10000), mx.CurrentCPU) + assert.Equal(t, float64(8192), mx.CurrentMEM) +} + +func BenchmarkNodesMetrics(b *testing.B) { + nodes := v1.NodeList{ + Items: []v1.Node{ + makeNode("n1", "100m", "4Mi", "50m", "2Mi"), + makeNode("n2", "100m", "4Mi", "50m", "2Mi"), + }, + } + + metrics := v1beta1.NodeMetricsList{ + Items: []v1beta1.NodeMetrics{ + makeMxNode("n1", "50m", "1Mi"), + makeMxNode("n2", "50m", "1Mi"), + }, + } + + m := NewMetricsServer(nil) + mmx := make(NodesMetrics) + + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + m.NodesMetrics(nodes.Items, metrics.Items, mmx) + } +} + +func TestClusterLoad(t *testing.T) { + m := NewMetricsServer(nil) + + nodes := v1.NodeList{ + Items: []v1.Node{ + makeNode("n1", "100m", "4Mi", "50m", "2Mi"), + makeNode("n2", "100m", "4Mi", "50m", "2Mi"), + }, + } + + metrics := v1beta1.NodeMetricsList{ + Items: []v1beta1.NodeMetrics{ + makeMxNode("n1", "50m", "1Mi"), + makeMxNode("n2", "50m", "1Mi"), + }, + } + + mx := m.ClusterLoad(nodes.Items, metrics.Items) + assert.Equal(t, 50.0, mx.PercCPU) + assert.Equal(t, 25.0, mx.PercMEM) +} + +func BenchmarkClusterLoad(b *testing.B) { + nodes := v1.NodeList{ + Items: []v1.Node{ + makeNode("n1", "100m", "4Mi", "50m", "2Mi"), + makeNode("n2", "100m", "4Mi", "50m", "2Mi"), + }, + } + + metrics := v1beta1.NodeMetricsList{ + Items: []v1beta1.NodeMetrics{ + makeMxNode("n1", "50m", "1Mi"), + makeMxNode("n2", "50m", "1Mi"), + }, + } + + m := NewMetricsServer(nil) + + b.ResetTimer() + b.ReportAllocs() + for n := 0; n < b.N; n++ { + m.ClusterLoad(nodes.Items, metrics.Items) + } +} + +func makeMxPod(name, cpu, mem string) v1beta1.PodMetrics { + return v1beta1.PodMetrics{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: "default", + }, + Containers: []v1beta1.ContainerMetrics{ + {Usage: makeRes(cpu, mem)}, + {Usage: makeRes(cpu, mem)}, + {Usage: makeRes(cpu, mem)}, + }, + } +} + +func makeNode(name, tcpu, tmem, acpu, amem string) v1.Node { + return v1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Status: v1.NodeStatus{ + Capacity: makeRes(tcpu, tmem), + Allocatable: makeRes(acpu, amem), + }, + } +} + +func makeMxNode(name, cpu, mem string) v1beta1.NodeMetrics { + return v1beta1.NodeMetrics{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Usage: makeRes(cpu, mem), + } +} + +func makeRes(c, m string) v1.ResourceList { + cpu, _ := resource.ParseQuantity(c) + mem, _ := resource.ParseQuantity(m) + + return v1.ResourceList{ + v1.ResourceCPU: cpu, + v1.ResourceMemory: mem, + } +} diff --git a/internal/k8s/no.go b/internal/k8s/no.go index 23a92b24..dc5c48ee 100644 --- a/internal/k8s/no.go +++ b/internal/k8s/no.go @@ -5,28 +5,26 @@ import ( ) // Node represents a Kubernetes node. -type Node struct{} +type Node struct { + Connection +} // NewNode returns a new Node. -func NewNode() Res { - return &Node{} +func NewNode(c Connection) Cruder { + return &Node{c} } // Get a node. -func (*Node) Get(_, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().Nodes().Get(n, opts) +func (n *Node) Get(_, name string) (interface{}, error) { + return n.DialOrDie().CoreV1().Nodes().Get(name, metav1.GetOptions{}) } // List all nodes on the cluster. -func (*Node) List(_ string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().Nodes().List(opts) +func (n *Node) List(_ string) (Collection, error) { + rr, err := n.DialOrDie().CoreV1().Nodes().List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*Node) List(_ string) (Collection, error) { } // Delete a node. -func (*Node) Delete(_, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().Nodes().Delete(n, &opts) +func (n *Node) Delete(_, name string) error { + return n.DialOrDie().CoreV1().Nodes().Delete(name, nil) } diff --git a/internal/k8s/ns.go b/internal/k8s/ns.go index 86a82c19..340496aa 100644 --- a/internal/k8s/ns.go +++ b/internal/k8s/ns.go @@ -5,29 +5,26 @@ import ( ) // Namespace represents a Kubernetes namespace. -type Namespace struct{} +type Namespace struct { + Connection +} // NewNamespace returns a new Namespace. -func NewNamespace() Res { - return &Namespace{} +func NewNamespace(c Connection) Cruder { + return &Namespace{c} } // Get a active namespace. -func (*Namespace) Get(_, n string) (interface{}, error) { - opts := metav1.GetOptions{} - - return conn.dialOrDie().CoreV1().Namespaces().Get(n, opts) +func (n *Namespace) Get(_, name string) (interface{}, error) { + return n.DialOrDie().CoreV1().Namespaces().Get(name, metav1.GetOptions{}) } // List all active namespaces on the cluster. -func (*Namespace) List(_ string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().Namespaces().List(opts) +func (n *Namespace) List(_ string) (Collection, error) { + rr, err := n.DialOrDie().CoreV1().Namespaces().List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -37,8 +34,6 @@ func (*Namespace) List(_ string) (Collection, error) { } // Delete a namespace. -func (*Namespace) Delete(_, n string) error { - opts := metav1.DeleteOptions{} - - return conn.dialOrDie().CoreV1().Namespaces().Delete(n, &opts) +func (n *Namespace) Delete(_, name string) error { + return n.DialOrDie().CoreV1().Namespaces().Delete(name, nil) } diff --git a/internal/k8s/pdb.go b/internal/k8s/pdb.go index 6507e273..20a0474f 100644 --- a/internal/k8s/pdb.go +++ b/internal/k8s/pdb.go @@ -5,28 +5,26 @@ import ( ) // PodDisruptionBudget represents a PodDisruptionBudget Kubernetes resource. -type PodDisruptionBudget struct{} +type PodDisruptionBudget struct { + Connection +} // NewPodDisruptionBudget returns a new PodDisruptionBudget. -func NewPodDisruptionBudget() Res { - return &PodDisruptionBudget{} +func NewPodDisruptionBudget(c Connection) Cruder { + return &PodDisruptionBudget{c} } // Get a pdb. -func (*PodDisruptionBudget) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).Get(n, opts) +func (p *PodDisruptionBudget) Get(ns, n string) (interface{}, error) { + return p.DialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).Get(n, metav1.GetOptions{}) } // List all pdbs in a given namespace. -func (*PodDisruptionBudget) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).List(opts) +func (p *PodDisruptionBudget) List(ns string) (Collection, error) { + rr, err := p.DialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*PodDisruptionBudget) List(ns string) (Collection, error) { } // Delete a pdb. -func (*PodDisruptionBudget) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).Delete(n, &opts) +func (p *PodDisruptionBudget) Delete(ns, n string) error { + return p.DialOrDie().PolicyV1beta1().PodDisruptionBudgets(ns).Delete(n, nil) } diff --git a/internal/k8s/pod.go b/internal/k8s/pod.go index 507355cf..ad104296 100644 --- a/internal/k8s/pod.go +++ b/internal/k8s/pod.go @@ -8,38 +8,27 @@ import ( const defaultKillGrace int64 = 5 -type ( - // Loggable represents a K8s resource that has containers and can be logged. - Loggable interface { - Res - Containers(ns, n string, includeInit bool) ([]string, error) - Logs(ns, n, co string, lines int64, previous bool) *restclient.Request - } - - // Pod represents a Kubernetes resource. - Pod struct{} -) +// Pod represents a Kubernetes resource. +type Pod struct { + Connection +} // NewPod returns a new Pod. -func NewPod() Res { - return &Pod{} +func NewPod(c Connection) Cruder { + return &Pod{c} } // Get a pod. -func (*Pod) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().Pods(ns).Get(n, opts) +func (p *Pod) Get(ns, name string) (interface{}, error) { + return p.DialOrDie().CoreV1().Pods(ns).Get(name, metav1.GetOptions{}) } // List all pods in a given namespace. -func (*Pod) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().Pods(ns).List(opts) +func (p *Pod) List(ns string) (Collection, error) { + rr, err := p.DialOrDie().CoreV1().Pods(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -49,51 +38,40 @@ func (*Pod) List(ns string) (Collection, error) { } // Delete a pod. -func (*Pod) Delete(ns, n string) error { - var grace = defaultKillGrace - opts := metav1.DeleteOptions{ +func (p *Pod) Delete(ns, n string) error { + // Make pods die faster? + grace := defaultKillGrace + return p.DialOrDie().CoreV1().Pods(ns).Delete(n, &metav1.DeleteOptions{ GracePeriodSeconds: &grace, - } - return conn.dialOrDie().CoreV1().Pods(ns).Delete(n, &opts) + }) } // Containers returns all container names on pod -func (*Pod) Containers(ns, n string, includeInit bool) ([]string, error) { - opts := metav1.GetOptions{} - cc := []string{} - po, err := conn.dialOrDie().CoreV1().Pods(ns).Get(n, opts) +func (p *Pod) Containers(ns, n string, includeInit bool) ([]string, error) { + po, err := p.DialOrDie().CoreV1().Pods(ns).Get(n, metav1.GetOptions{}) if err != nil { - return cc, err + return nil, err } - for _, c := range po.Spec.Containers { - cc = append(cc, c.Name) - } + cc := []string{} if includeInit { for _, c := range po.Spec.InitContainers { cc = append(cc, c.Name) } } + for _, c := range po.Spec.Containers { + cc = append(cc, c.Name) + } + return cc, nil } // Logs fetch container logs for a given pod and container. -func (*Pod) Logs(ns, n, co string, lines int64, prev bool) *restclient.Request { - opts := &v1.PodLogOptions{ +func (p *Pod) Logs(ns, n, co string, lines int64, prev bool) *restclient.Request { + return p.DialOrDie().CoreV1().Pods(ns).GetLogs(n, &v1.PodLogOptions{ Container: co, Follow: true, TailLines: &lines, Previous: prev, - } - - return conn.dialOrDie().CoreV1().Pods(ns).GetLogs(n, opts) -} - -// Events retrieved pod's events. -func (*Pod) Events(ns, n string) (*v1.EventList, error) { - e := conn.dialOrDie().Core().Events(ns) - sel := e.GetFieldSelector(&n, &ns, nil, nil) - opts := metav1.ListOptions{FieldSelector: sel.String()} - ee, err := e.List(opts) - return ee, err + }) } diff --git a/internal/k8s/pv.go b/internal/k8s/pv.go index 0bd0a60f..d28ae1ee 100644 --- a/internal/k8s/pv.go +++ b/internal/k8s/pv.go @@ -5,26 +5,25 @@ import ( ) // PV represents a Kubernetes PersistentVolume. -type PV struct{} +type PV struct { + Connection +} // NewPV returns a new PV. -func NewPV() Res { - return &PV{} +func NewPV(c Connection) Cruder { + return &PV{c} } // Get a PV. -func (*PV) Get(_, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().PersistentVolumes().Get(n, opts) +func (p *PV) Get(_, n string) (interface{}, error) { + return p.DialOrDie().CoreV1().PersistentVolumes().Get(n, metav1.GetOptions{}) } // List all PVs in a given namespace. -func (*PV) List(_ string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().PersistentVolumes().List(opts) +func (p *PV) List(_ string) (Collection, error) { + rr, err := p.DialOrDie().CoreV1().PersistentVolumes().List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } cc := make(Collection, len(rr.Items)) @@ -36,7 +35,6 @@ func (*PV) List(_ string) (Collection, error) { } // Delete a PV. -func (*PV) Delete(_, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().PersistentVolumes().Delete(n, &opts) +func (p *PV) Delete(_, n string) error { + return p.DialOrDie().CoreV1().PersistentVolumes().Delete(n, nil) } diff --git a/internal/k8s/pvc.go b/internal/k8s/pvc.go index a28db85d..a4506fd5 100644 --- a/internal/k8s/pvc.go +++ b/internal/k8s/pvc.go @@ -5,28 +5,26 @@ import ( ) // PVC represents a Kubernetes service. -type PVC struct{} +type PVC struct { + Connection +} // NewPVC returns a new PVC. -func NewPVC() Res { - return &PVC{} +func NewPVC(c Connection) Cruder { + return &PVC{c} } // Get a PVC. -func (*PVC) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, opts) +func (p *PVC) Get(ns, n string) (interface{}, error) { + return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Get(n, metav1.GetOptions{}) } // List all PVCs in a given namespace. -func (*PVC) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().PersistentVolumeClaims(ns).List(opts) +func (p *PVC) List(ns string) (Collection, error) { + rr, err := p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*PVC) List(ns string) (Collection, error) { } // Delete a PVC. -func (*PVC) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, &opts) +func (p *PVC) Delete(ns, n string) error { + return p.DialOrDie().CoreV1().PersistentVolumeClaims(ns).Delete(n, nil) } diff --git a/internal/k8s/rc.go b/internal/k8s/rc.go index cc2dda70..96b7d285 100644 --- a/internal/k8s/rc.go +++ b/internal/k8s/rc.go @@ -5,28 +5,26 @@ import ( ) // ReplicationController represents a Kubernetes service. -type ReplicationController struct{} +type ReplicationController struct { + Connection +} // NewReplicationController returns a new ReplicationController. -func NewReplicationController() Res { - return &ReplicationController{} +func NewReplicationController(c Connection) Cruder { + return &ReplicationController{c} } // Get a RC. -func (*ReplicationController) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().Core().ReplicationControllers(ns).Get(n, opts) +func (r *ReplicationController) Get(ns, n string) (interface{}, error) { + return r.DialOrDie().Core().ReplicationControllers(ns).Get(n, metav1.GetOptions{}) } // List all RCs in a given namespace. -func (*ReplicationController) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().Core().ReplicationControllers(ns).List(opts) +func (r *ReplicationController) List(ns string) (Collection, error) { + rr, err := r.DialOrDie().Core().ReplicationControllers(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*ReplicationController) List(ns string) (Collection, error) { } // Delete a RC. -func (*ReplicationController) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().Core().ReplicationControllers(ns).Delete(n, &opts) +func (r *ReplicationController) Delete(ns, n string) error { + return r.DialOrDie().Core().ReplicationControllers(ns).Delete(n, nil) } diff --git a/internal/k8s/resource.go b/internal/k8s/resource.go index af88ac0c..2650a163 100644 --- a/internal/k8s/resource.go +++ b/internal/k8s/resource.go @@ -3,7 +3,6 @@ package k8s import ( "fmt" - "github.com/rs/zerolog/log" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" "k8s.io/apimachinery/pkg/runtime" @@ -15,12 +14,14 @@ import ( // Resource represents a Kubernetes Resource type Resource struct { + Connection + group, version, name string } // NewResource returns a new Resource. -func NewResource(group, version, name string) Res { - return &Resource{group: group, version: version, name: name} +func NewResource(c Connection, group, version, name string) Cruder { + return &Resource{Connection: c, group: group, version: version, name: name} } // GetInfo returns info about apigroup. @@ -34,48 +35,82 @@ func (r *Resource) base() dynamic.NamespaceableResourceInterface { Version: r.version, Resource: r.name, } - return conn.dynDialOrDie().Resource(g) + return r.DynDialOrDie().Resource(g) } // Get a Resource. func (r *Resource) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return r.base().Namespace(ns).Get(n, opts) + return r.base().Namespace(ns).Get(n, metav1.GetOptions{}) } // List all Resources in a given namespace. func (r *Resource) List(ns string) (Collection, error) { obj, err := r.listAll(ns, r.name) if err != nil { - return Collection{}, err + return nil, err } - return Collection{obj.(*metav1beta1.Table)}, nil } // Delete a Resource. func (r *Resource) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return r.base().Namespace(ns).Delete(n, &opts) + return r.base().Namespace(ns).Delete(n, nil) } +// ---------------------------------------------------------------------------- // Helpers... -func (r *Resource) getClient() *rest.RESTClient { - gv := schema.GroupVersion{Group: r.group, Version: r.version} - codecs, _ := r.codecs() - crConfig := *conn.restConfigOrDie() - crConfig.GroupVersion = &gv +const gvFmt = "application/json;as=Table;v=%s;g=%s, application/json" + +func (r *Resource) listAll(ns, n string) (runtime.Object, error) { + a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName) + _, codec := r.codecs() + + c, err := r.getClient() + if err != nil { + return nil, err + } + + return c.Get(). + SetHeader("Accept", a). + Namespace(ns). + Resource(n). + VersionedParams(&metav1beta1.TableOptions{}, codec). + Do().Get() +} + +func (r *Resource) getOne(ns, n string) (runtime.Object, error) { + a := fmt.Sprintf(gvFmt, metav1beta1.SchemeGroupVersion.Version, metav1beta1.GroupName) + _, codec := r.codecs() + + c, err := r.getClient() + if err != nil { + return nil, err + } + + return c.Get(). + SetHeader("Accept", a). + Namespace(ns). + Resource(n). + VersionedParams(&metav1beta1.TableOptions{}, codec). + Do().Get() +} + +func (r *Resource) getClient() (*rest.RESTClient, error) { + crConfig := r.RestConfigOrDie() + crConfig.GroupVersion = &schema.GroupVersion{Group: r.group, Version: r.version} crConfig.APIPath = "/apis" if len(r.group) == 0 { crConfig.APIPath = "/api" } + codecs, _ := r.codecs() crConfig.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: codecs} - crRestClient, err := rest.RESTClientFor(&crConfig) + + crRestClient, err := rest.RESTClientFor(crConfig) if err != nil { - log.Fatal().Err(err) + return nil, err } - return crRestClient + return crRestClient, nil } func (r *Resource) codecs() (serializer.CodecFactory, runtime.ParameterCodec) { @@ -84,36 +119,6 @@ func (r *Resource) codecs() (serializer.CodecFactory, runtime.ParameterCodec) { metav1.AddToGroupVersion(scheme, gv) scheme.AddKnownTypes(gv, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) scheme.AddKnownTypes(metav1beta1.SchemeGroupVersion, &metav1beta1.Table{}, &metav1beta1.TableOptions{}) - codecs := serializer.NewCodecFactory(scheme) - return codecs, runtime.NewParameterCodec(scheme) -} - -func (r *Resource) listAll(ns, n string) (runtime.Object, error) { - group := metav1beta1.GroupName - version := metav1beta1.SchemeGroupVersion.Version - a := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group) - - _, codec := r.codecs() - return r.getClient().Get(). - SetHeader("Accept", a). - Namespace(ns). - Resource(n). - VersionedParams(&metav1beta1.TableOptions{}, codec). - Do(). - Get() -} - -func (r *Resource) getOne(ns, n string) (runtime.Object, error) { - group := metav1beta1.GroupName - version := metav1beta1.SchemeGroupVersion.Version - a := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group) - - _, codec := r.codecs() - return r.getClient().Get(). - SetHeader("Accept", a). - Namespace(ns). - Resource(n). - VersionedParams(&metav1beta1.TableOptions{}, codec). - Do(). - Get() + + return serializer.NewCodecFactory(scheme), runtime.NewParameterCodec(scheme) } diff --git a/internal/k8s/role.go b/internal/k8s/role.go index 93e0aa77..d2448b7d 100644 --- a/internal/k8s/role.go +++ b/internal/k8s/role.go @@ -6,28 +6,26 @@ import ( ) // Role represents a Kubernetes Role. -type Role struct{} +type Role struct { + Connection +} // NewRole returns a new Role. -func NewRole() Res { - return &Role{} +func NewRole(c Connection) Cruder { + return &Role{c} } // Get a Role. -func (*Role) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().RbacV1().Roles(ns).Get(n, opts) +func (r *Role) Get(ns, n string) (interface{}, error) { + return r.DialOrDie().RbacV1().Roles(ns).Get(n, metav1.GetOptions{}) } // List all Roles in a given namespace. -func (*Role) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().RbacV1().Roles(ns).List(opts) +func (r *Role) List(ns string) (Collection, error) { + rr, err := r.DialOrDie().RbacV1().Roles(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -37,7 +35,6 @@ func (*Role) List(ns string) (Collection, error) { } // Delete a Role. -func (*Role) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().RbacV1().Roles(ns).Delete(n, &opts) +func (r *Role) Delete(ns, n string) error { + return r.DialOrDie().RbacV1().Roles(ns).Delete(n, nil) } diff --git a/internal/k8s/role_binding.go b/internal/k8s/role_binding.go index 25683828..eaf46987 100644 --- a/internal/k8s/role_binding.go +++ b/internal/k8s/role_binding.go @@ -3,28 +3,26 @@ package k8s import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // RoleBinding represents a Kubernetes RoleBinding. -type RoleBinding struct{} +type RoleBinding struct { + Connection +} // NewRoleBinding returns a new RoleBinding. -func NewRoleBinding() Res { - return &RoleBinding{} +func NewRoleBinding(c Connection) Cruder { + return &RoleBinding{c} } // Get a RoleBinding. -func (*RoleBinding) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().RbacV1().RoleBindings(ns).Get(n, opts) +func (r *RoleBinding) Get(ns, n string) (interface{}, error) { + return r.DialOrDie().RbacV1().RoleBindings(ns).Get(n, metav1.GetOptions{}) } // List all RoleBindings in a given namespace. -func (*RoleBinding) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().RbacV1().RoleBindings(ns).List(opts) +func (r *RoleBinding) List(ns string) (Collection, error) { + rr, err := r.DialOrDie().RbacV1().RoleBindings(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -34,7 +32,6 @@ func (*RoleBinding) List(ns string) (Collection, error) { } // Delete a RoleBinding. -func (*RoleBinding) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().RbacV1().RoleBindings(ns).Delete(n, &opts) +func (r *RoleBinding) Delete(ns, n string) error { + return r.DialOrDie().RbacV1().RoleBindings(ns).Delete(n, nil) } diff --git a/internal/k8s/rs.go b/internal/k8s/rs.go index ca7d64e0..c82e0b0d 100644 --- a/internal/k8s/rs.go +++ b/internal/k8s/rs.go @@ -5,28 +5,26 @@ import ( ) // ReplicaSet represents a Kubernetes ReplicaSet. -type ReplicaSet struct{} +type ReplicaSet struct { + Connection +} // NewReplicaSet returns a new ReplicaSet. -func NewReplicaSet() Res { - return &ReplicaSet{} +func NewReplicaSet(c Connection) Cruder { + return &ReplicaSet{c} } // Get a ReplicaSet. -func (*ReplicaSet) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().Apps().ReplicaSets(ns).Get(n, opts) +func (r *ReplicaSet) Get(ns, n string) (interface{}, error) { + return r.DialOrDie().Apps().ReplicaSets(ns).Get(n, metav1.GetOptions{}) } // List all ReplicaSets in a given namespace. -func (*ReplicaSet) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().Apps().ReplicaSets(ns).List(opts) +func (r *ReplicaSet) List(ns string) (Collection, error) { + rr, err := r.DialOrDie().Apps().ReplicaSets(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*ReplicaSet) List(ns string) (Collection, error) { } // Delete a ReplicaSet. -func (*ReplicaSet) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().Apps().ReplicaSets(ns).Delete(n, &opts) +func (r *ReplicaSet) Delete(ns, n string) error { + return r.DialOrDie().Apps().ReplicaSets(ns).Delete(n, nil) } diff --git a/internal/k8s/sa.go b/internal/k8s/sa.go index 354d9300..f3d02ffd 100644 --- a/internal/k8s/sa.go +++ b/internal/k8s/sa.go @@ -5,32 +5,26 @@ import ( ) // ServiceAccount manages a Kubernetes ServiceAccount. -type ServiceAccount struct{} +type ServiceAccount struct { + Connection +} // NewServiceAccount instantiates a new ServiceAccount. -func NewServiceAccount() Res { - return &ServiceAccount{} +func NewServiceAccount(c Connection) Cruder { + return &ServiceAccount{c} } // Get a ServiceAccount. -func (*ServiceAccount) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - o, err := conn.dialOrDie().CoreV1().ServiceAccounts(ns).Get(n, opts) - if err != nil { - return o, err - } - return o, nil +func (s *ServiceAccount) Get(ns, n string) (interface{}, error) { + return s.DialOrDie().CoreV1().ServiceAccounts(ns).Get(n, metav1.GetOptions{}) } // List all ServiceAccounts in a given namespace. -func (*ServiceAccount) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().ServiceAccounts(ns).List(opts) +func (s *ServiceAccount) List(ns string) (Collection, error) { + rr, err := s.DialOrDie().CoreV1().ServiceAccounts(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -40,7 +34,6 @@ func (*ServiceAccount) List(ns string) (Collection, error) { } // Delete a ServiceAccount. -func (*ServiceAccount) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().ServiceAccounts(ns).Delete(n, &opts) +func (s *ServiceAccount) Delete(ns, n string) error { + return s.DialOrDie().CoreV1().ServiceAccounts(ns).Delete(n, nil) } diff --git a/internal/k8s/secret.go b/internal/k8s/secret.go index 6e5ab4ab..f5d48637 100644 --- a/internal/k8s/secret.go +++ b/internal/k8s/secret.go @@ -5,28 +5,26 @@ import ( ) // Secret represents a Kubernetes Secret. -type Secret struct{} +type Secret struct { + Connection +} // NewSecret returns a new Secret. -func NewSecret() Res { - return &Secret{} +func NewSecret(c Connection) Cruder { + return &Secret{c} } // Get a Secret. -func (c *Secret) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().Secrets(ns).Get(n, opts) +func (s *Secret) Get(ns, n string) (interface{}, error) { + return s.DialOrDie().CoreV1().Secrets(ns).Get(n, metav1.GetOptions{}) } // List all Secrets in a given namespace. -func (c *Secret) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().Secrets(ns).List(opts) +func (s *Secret) List(ns string) (Collection, error) { + rr, err := s.DialOrDie().CoreV1().Secrets(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (c *Secret) List(ns string) (Collection, error) { } // Delete a Secret. -func (c *Secret) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().Secrets(ns).Delete(n, &opts) +func (s *Secret) Delete(ns, n string) error { + return s.DialOrDie().CoreV1().Secrets(ns).Delete(n, nil) } diff --git a/internal/k8s/sts.go b/internal/k8s/sts.go index b9fdaf48..612e0722 100644 --- a/internal/k8s/sts.go +++ b/internal/k8s/sts.go @@ -5,41 +5,35 @@ import ( ) // StatefulSet manages a Kubernetes StatefulSet. -type StatefulSet struct{} +type StatefulSet struct { + Connection +} // NewStatefulSet instantiates a new StatefulSet. -func NewStatefulSet() Res { - return &StatefulSet{} +func NewStatefulSet(c Connection) Cruder { + return &StatefulSet{c} } // Get a StatefulSet. -func (*StatefulSet) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - o, err := conn.dialOrDie().AppsV1().StatefulSets(ns).Get(n, opts) - if err != nil { - return o, err - } - return o, nil +func (s *StatefulSet) Get(ns, n string) (interface{}, error) { + return s.DialOrDie().AppsV1().StatefulSets(ns).Get(n, metav1.GetOptions{}) } // List all StatefulSets in a given namespace. -func (*StatefulSet) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().AppsV1().StatefulSets(ns).List(opts) +func (s *StatefulSet) List(ns string) (Collection, error) { + rr, err := s.DialOrDie().AppsV1().StatefulSets(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r } + return cc, nil } // Delete a StatefulSet. -func (*StatefulSet) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().AppsV1().StatefulSets(ns).Delete(n, &opts) +func (s *StatefulSet) Delete(ns, n string) error { + return s.DialOrDie().AppsV1().StatefulSets(ns).Delete(n, nil) } diff --git a/internal/k8s/svc.go b/internal/k8s/svc.go index e61d6d8e..0d54c703 100644 --- a/internal/k8s/svc.go +++ b/internal/k8s/svc.go @@ -5,28 +5,26 @@ import ( ) // Service represents a Kubernetes Service. -type Service struct{} +type Service struct { + Connection +} // NewService returns a new Service. -func NewService() Res { - return &Service{} +func NewService(c Connection) Cruder { + return &Service{c} } // Get a service. -func (*Service) Get(ns, n string) (interface{}, error) { - opts := metav1.GetOptions{} - return conn.dialOrDie().CoreV1().Services(ns).Get(n, opts) +func (s *Service) Get(ns, n string) (interface{}, error) { + return s.DialOrDie().CoreV1().Services(ns).Get(n, metav1.GetOptions{}) } // List all Services in a given namespace. -func (*Service) List(ns string) (Collection, error) { - opts := metav1.ListOptions{} - - rr, err := conn.dialOrDie().CoreV1().Services(ns).List(opts) +func (s *Service) List(ns string) (Collection, error) { + rr, err := s.DialOrDie().CoreV1().Services(ns).List(metav1.ListOptions{}) if err != nil { - return Collection{}, err + return nil, err } - cc := make(Collection, len(rr.Items)) for i, r := range rr.Items { cc[i] = r @@ -36,7 +34,6 @@ func (*Service) List(ns string) (Collection, error) { } // Delete a Service. -func (*Service) Delete(ns, n string) error { - opts := metav1.DeleteOptions{} - return conn.dialOrDie().CoreV1().Services(ns).Delete(n, &opts) +func (s *Service) Delete(ns, n string) error { + return s.DialOrDie().CoreV1().Services(ns).Delete(n, nil) }