refactor + added node view and sorters

mine
derailed 2019-03-24 16:05:23 -06:00
parent 7542304adc
commit 6c3c9159de
38 changed files with 980 additions and 837 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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,
)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

18
internal/k8s/helpers.go Normal file
View File

@ -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)
}

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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) {

View File

@ -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, "_")
}

View File

@ -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
}
}

View File

@ -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,
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
})
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}