pkg/kubernetes/client.go (196 lines of code) (raw):
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
package kubernetes
import (
"context"
"time"
log "github.com/sirupsen/logrus"
appsv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
policy "k8s.io/api/policy/v1beta1"
policyv1 "k8s.io/api/policy/v1beta1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
)
const (
evictionKind = "Eviction"
evictionSubresource = "pods/eviction"
)
// ClientSetClient is a Kubernetes client hooked up to a live api server.
type ClientSetClient struct {
clientset *kubernetes.Clientset
interval, timeout time.Duration
}
// NewClient returns a KubernetesClient hooked up to the api server at the apiserverURL.
func NewClient(apiserverURL, kubeConfig string, interval, timeout time.Duration) (*ClientSetClient, error) {
config, err := clientcmd.BuildConfigFromKubeconfigGetter(apiserverURL, func() (*clientcmdapi.Config, error) {
return clientcmd.Load([]byte(kubeConfig))
})
if err != nil {
return nil, err
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, err
}
return &ClientSetClient{clientset: clientset, interval: interval, timeout: timeout}, nil
}
// ListPods returns Pods running on the passed in node.
func (c *ClientSetClient) ListPods(node *v1.Node) (*v1.PodList, error) {
return c.ListPodsByOptions(metav1.NamespaceAll, metav1.ListOptions{
FieldSelector: fields.SelectorFromSet(fields.Set{"spec.nodeName": node.Name}).String()})
}
// ListAllPods returns all Pods running.
func (c *ClientSetClient) ListAllPods() (*v1.PodList, error) {
return c.ListPodsByOptions(metav1.NamespaceAll, metav1.ListOptions{})
}
// ListPodsByOptions returns Pods based on the passed in list options.
func (c *ClientSetClient) ListPodsByOptions(namespace string, opts metav1.ListOptions) (*v1.PodList, error) {
return c.clientset.CoreV1().Pods(namespace).List(context.TODO(), opts)
}
// ListNodes returns a list of Nodes registered in the api server.
func (c *ClientSetClient) ListNodes() (*v1.NodeList, error) {
return c.ListNodesByOptions(metav1.ListOptions{})
}
// ListNodesByOptions returns a list of Nodes registered in the api server.
func (c *ClientSetClient) ListNodesByOptions(opts metav1.ListOptions) (*v1.NodeList, error) {
return c.clientset.CoreV1().Nodes().List(context.TODO(), opts)
}
// ListServiceAccounts returns a list of Service Accounts in the provided namespace.
func (c *ClientSetClient) ListServiceAccounts(namespace string) (*v1.ServiceAccountList, error) {
return c.ListServiceAccountsByOptions(namespace, metav1.ListOptions{})
}
// ListServiceAccountsByOptions returns a list of Service Accounts in the provided namespace.
func (c *ClientSetClient) ListServiceAccountsByOptions(namespace string, opts metav1.ListOptions) (*v1.ServiceAccountList, error) {
return c.clientset.CoreV1().ServiceAccounts(namespace).List(context.TODO(), opts)
}
// ListPodSecurityPolices returns the list of Pod Security Policies
func (c *ClientSetClient) ListPodSecurityPolices(opts metav1.ListOptions) (*policyv1.PodSecurityPolicyList, error) {
return c.clientset.PolicyV1beta1().PodSecurityPolicies().List(context.TODO(), opts)
}
// ListDeployments returns a list of deployments in the provided namespace.
func (c *ClientSetClient) ListDeployments(namespace string, opts metav1.ListOptions) (*appsv1.DeploymentList, error) {
return c.clientset.AppsV1().Deployments(namespace).List(context.TODO(), opts)
}
// ListDaemonSets returns a list of daemonsets in the provided namespace.
func (c *ClientSetClient) ListDaemonSets(namespace string, opts metav1.ListOptions) (*appsv1.DaemonSetList, error) {
return c.clientset.AppsV1().DaemonSets(namespace).List(context.TODO(), opts)
}
// ListSecrets returns a list of secrets in the provided namespace.
func (c *ClientSetClient) ListSecrets(namespace string, opts metav1.ListOptions) (*v1.SecretList, error) {
return c.clientset.CoreV1().Secrets(namespace).List(context.TODO(), opts)
}
// PatchDeployment applies a JSON patch to a deployment in the provided namespace.
func (c *ClientSetClient) PatchDeployment(namespace, name, jsonPatch string) (*appsv1.Deployment, error) {
opts := metav1.PatchOptions{}
return c.clientset.AppsV1().Deployments(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, []byte(jsonPatch), opts)
}
// PatchDaemonSet applies a JSON patch to a daemonset in the provided namespace.
func (c *ClientSetClient) PatchDaemonSet(namespace, name, jsonPatch string) (*appsv1.DaemonSet, error) {
opts := metav1.PatchOptions{}
return c.clientset.AppsV1().DaemonSets(namespace).Patch(context.TODO(), name, types.StrategicMergePatchType, []byte(jsonPatch), opts)
}
// GetNode returns details about node with passed in name.
func (c *ClientSetClient) GetNode(name string) (*v1.Node, error) {
return c.clientset.CoreV1().Nodes().Get(context.TODO(), name, metav1.GetOptions{})
}
// UpdateNode updates the node in the api server with the passed in info.
func (c *ClientSetClient) UpdateNode(node *v1.Node) (*v1.Node, error) {
opts := metav1.UpdateOptions{}
return c.clientset.CoreV1().Nodes().Update(context.TODO(), node, opts)
}
// DeleteNode deregisters the node in the api server.
func (c *ClientSetClient) DeleteNode(name string) error {
return c.clientset.CoreV1().Nodes().Delete(context.TODO(), name, metav1.DeleteOptions{})
}
// DeleteServiceAccount deletes the passed in service account.
func (c *ClientSetClient) DeleteServiceAccount(sa *v1.ServiceAccount) error {
return c.clientset.CoreV1().ServiceAccounts(sa.Namespace).Delete(context.TODO(), sa.Name, metav1.DeleteOptions{})
}
// SupportEviction queries the api server to discover if it supports eviction, and returns supported type if it is supported.
func (c *ClientSetClient) SupportEviction() (string, error) {
discoveryClient := c.clientset.Discovery()
groupList, err := discoveryClient.ServerGroups()
if err != nil {
return "", err
}
foundPolicyGroup := false
var policyGroupVersion string
for _, group := range groupList.Groups {
if group.Name == "policy" {
foundPolicyGroup = true
policyGroupVersion = group.PreferredVersion.GroupVersion
break
}
}
if !foundPolicyGroup {
return "", nil
}
resourceList, err := discoveryClient.ServerResourcesForGroupVersion("v1")
if err != nil {
return "", err
}
for _, resource := range resourceList.APIResources {
if resource.Name == evictionSubresource && resource.Kind == evictionKind {
return policyGroupVersion, nil
}
}
return "", nil
}
// DeleteClusterRole deletes the passed in cluster role.
func (c *ClientSetClient) DeleteClusterRole(role *rbacv1.ClusterRole) error {
return c.clientset.RbacV1().ClusterRoles().Delete(context.TODO(), role.Name, metav1.DeleteOptions{})
}
// DeleteDaemonSet deletes the passed in daemonset.
func (c *ClientSetClient) DeleteDaemonSet(daemonset *appsv1.DaemonSet) error {
return c.clientset.AppsV1().DaemonSets(daemonset.Namespace).Delete(context.TODO(), daemonset.Name, metav1.DeleteOptions{})
}
// DeleteDeployment deletes the passed in daemonset.
func (c *ClientSetClient) DeleteDeployment(deployment *appsv1.Deployment) error {
return c.clientset.AppsV1().Deployments(deployment.Namespace).Delete(context.TODO(), deployment.Name, metav1.DeleteOptions{})
}
// DeletePod deletes the passed in pod.
func (c *ClientSetClient) DeletePod(pod *v1.Pod) error {
return c.clientset.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{})
}
// DeletePods deletes all pods in a namespace that match the option filters.
func (c *ClientSetClient) DeletePods(namespace string, opts metav1.ListOptions) error {
return c.clientset.CoreV1().Pods(namespace).DeleteCollection(context.TODO(), metav1.DeleteOptions{}, opts)
}
// DeleteSecret deletes the passed in secret.
func (c *ClientSetClient) DeleteSecret(secret *v1.Secret) error {
return c.clientset.CoreV1().Secrets(secret.Namespace).Delete(context.TODO(), secret.Name, metav1.DeleteOptions{})
}
// EvictPod evicts the passed in pod using the passed in api version.
func (c *ClientSetClient) EvictPod(pod *v1.Pod, policyGroupVersion string) error {
eviction := &policy.Eviction{
TypeMeta: metav1.TypeMeta{
APIVersion: policyGroupVersion,
Kind: evictionKind,
},
ObjectMeta: metav1.ObjectMeta{
Name: pod.Name,
Namespace: pod.Namespace,
},
}
return c.clientset.PolicyV1beta1().Evictions(eviction.Namespace).Evict(context.TODO(), eviction)
}
// GetPod returns the pod.
func (c *ClientSetClient) getPod(namespace, name string) (*v1.Pod, error) {
return c.clientset.CoreV1().Pods(namespace).Get(context.TODO(), name, metav1.GetOptions{})
}
// WaitForDelete waits until all pods are deleted. Returns all pods not deleted and an error on failure.
func (c *ClientSetClient) WaitForDelete(logger *log.Entry, pods []v1.Pod, usingEviction bool) ([]v1.Pod, error) {
verbStr := "deleted"
if usingEviction {
verbStr = "evicted"
}
ctx, cancel := context.WithTimeout(context.Background(), c.timeout)
defer cancel()
err := wait.PollUntilContextCancel(ctx, c.interval, true, func(ctx context.Context) (bool, error) {
pendingPods := []v1.Pod{}
for i, pod := range pods {
p, err := c.getPod(pod.Namespace, pod.Name)
if apierrors.IsNotFound(err) || (p != nil && p.ObjectMeta.UID != pod.ObjectMeta.UID) {
logger.Infof("%s pod successfully %s", pod.Name, verbStr)
continue
} else if err != nil {
return false, err
} else {
pendingPods = append(pendingPods, pods[i])
}
}
pods = pendingPods
if len(pendingPods) > 0 {
return false, nil
}
return true, nil
})
return pods, err
}
// GetDaemonSet returns a given daemonset in a namespace.
func (c *ClientSetClient) GetDaemonSet(namespace, name string) (*appsv1.DaemonSet, error) {
return c.clientset.AppsV1().DaemonSets(namespace).Get(context.TODO(), name, metav1.GetOptions{})
}
// GetDeployment returns a given deployment in a namespace.
func (c *ClientSetClient) GetDeployment(namespace, name string) (*appsv1.Deployment, error) {
return c.clientset.AppsV1().Deployments(namespace).Get(context.TODO(), name, metav1.GetOptions{})
}
// UpdateDeployment updates a deployment to match the given specification.
func (c *ClientSetClient) UpdateDeployment(namespace string, deployment *appsv1.Deployment) (*appsv1.Deployment, error) {
opts := metav1.UpdateOptions{}
return c.clientset.AppsV1().Deployments(namespace).Update(context.TODO(), deployment, opts)
}