blob: bc5190d42a225cede2444420367488cffcc823b2 [file] [log] [blame]
package k8s
import (
"context"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"go.skia.org/infra/go/kube/clusterconfig"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
)
// Client provides an interface for interacting with a Kubernetes cluster. It is
// a thin wrapper around the Kubernetes API which allows for mocking requests.
type Client interface {
// ListNamespaces retrieves all namespaces in the cluster.
ListNamespaces(ctx context.Context, opts metav1.ListOptions) ([]corev1.Namespace, error)
// ListPods retrieves all pods in the namespace.
ListPods(ctx context.Context, namespace string, opts metav1.ListOptions) ([]corev1.Pod, error)
// DeletePod deletes a pod.
DeletePod(ctx context.Context, namespace, name string, opts metav1.DeleteOptions) error
// ListStatefulSets retrieves all stateful sets in the namespace.
ListStatefulSets(ctx context.Context, namespace string, opts metav1.ListOptions) ([]appsv1.StatefulSet, error)
// GetStatefulSet retrieves a single StatefulSet.
GetStatefulSet(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*appsv1.StatefulSet, error)
// GetEvents retrieves events for the given namespace.
GetEvents(ctx context.Context, namespace string) ([]corev1.Event, error)
}
// ClientImpl implements Client.
type ClientImpl struct {
c *kubernetes.Clientset
}
// NewClient returns a ClientImpl instance.
// TODO(borenet): Handle more of the setup here.
func NewClient(ctx context.Context, clientset *kubernetes.Clientset) (*ClientImpl, error) {
return &ClientImpl{
c: clientset,
}, nil
}
// GetNamespaces implements Client.
func (c *ClientImpl) ListNamespaces(ctx context.Context, opts metav1.ListOptions) ([]corev1.Namespace, error) {
result, err := c.c.CoreV1().Namespaces().List(ctx, opts)
if err != nil {
return nil, skerr.Wrap(err)
}
return result.Items, nil
}
// GetPods implements Client.
func (c *ClientImpl) ListPods(ctx context.Context, namespace string, opts metav1.ListOptions) ([]corev1.Pod, error) {
result, err := c.c.CoreV1().Pods(namespace).List(ctx, opts)
if err != nil {
return nil, skerr.Wrap(err)
}
return result.Items, nil
}
// DeletePod implements Client.
func (c *ClientImpl) DeletePod(ctx context.Context, namespace, name string, opts metav1.DeleteOptions) error {
return skerr.Wrap(c.c.CoreV1().Pods(namespace).Delete(ctx, name, opts))
}
// ListStatefulSets implements Client.
func (c *ClientImpl) ListStatefulSets(ctx context.Context, namespace string, opts metav1.ListOptions) ([]appsv1.StatefulSet, error) {
result, err := c.c.AppsV1().StatefulSets(namespace).List(ctx, opts)
if err != nil {
return nil, skerr.Wrap(err)
}
return result.Items, nil
}
// GetStatefulSet implements Client.
func (c *ClientImpl) GetStatefulSet(ctx context.Context, namespace, name string, opts metav1.GetOptions) (*appsv1.StatefulSet, error) {
result, err := c.c.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return nil, skerr.Wrap(err)
}
return result, nil
}
// GetEvents implements Client.
func (c *ClientImpl) GetEvents(ctx context.Context, namespace string) ([]corev1.Event, error) {
resp, err := c.c.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, skerr.Wrap(err)
}
return resp.Items, nil
}
// Assert that ClientImpl implements Client.
var _ Client = &ClientImpl{}
// NewInClusterClient creates a Client which can be used to interact with the
// cluster in which this service is running.
func NewInClusterClient(ctx context.Context) (*ClientImpl, error) {
config, err := rest.InClusterConfig()
if err != nil {
return nil, skerr.Wrapf(err, "failed to get in-cluster config")
}
sklog.Infof("Auth username: %s", config.Username)
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, skerr.Wrapf(err, "failed to get in-cluster clientset")
}
return NewClient(ctx, clientset)
}
// NewLocalClient uses the local kubeconfig file, the clusters config.json file,
// and a cluster name to create a Client which can be used to interact with that
// cluster.
// TODO(borenet): This requires that the user has previously created credentials
// for the requested cluster.
func NewLocalClient(ctx context.Context, kubeConfigFile, clusterConfigFile, cluster string) (*ClientImpl, error) {
clusterConfig, err := clusterconfig.New(clusterConfigFile)
if err != nil {
return nil, skerr.Wrap(err)
}
clusterInfo, ok := clusterConfig.Clusters[cluster]
if !ok {
return nil, skerr.Fmt("unknown cluster %q", cluster)
}
cfg, err := clientcmd.LoadFromFile(kubeConfigFile)
if err != nil {
return nil, skerr.Wrap(err)
}
cfg.CurrentContext = clusterInfo.ContextName
clusterCfg, ok := cfg.Clusters[clusterInfo.ContextName]
if !ok {
return nil, skerr.Fmt("no cluster config for %q", clusterInfo.ContextName)
}
restCfg, err := clientcmd.BuildConfigFromKubeconfigGetter(clusterCfg.Server, func() (*api.Config, error) {
return cfg, nil
})
if err != nil {
return nil, skerr.Wrap(err)
}
clientset, err := kubernetes.NewForConfig(restCfg)
if err != nil {
return nil, skerr.Wrap(err)
}
return NewClient(ctx, clientset)
}