cli: refactor kubernetes package (#2232)

* Clean up CLI kubernetes package

* Rename CLI kubernetes pkg to kubecmd

* Unify kubernetes clients

* Refactor attestation config upgrade

* Update CODEOWNERS file

* Remove outdated GetMeasurementSalt

---------

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2023-08-21 16:15:32 +02:00 committed by GitHub
parent 3bf316e28f
commit afa7fd0edb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1024 additions and 1160 deletions

View file

@ -4,8 +4,10 @@ Copyright (c) Edgeless Systems GmbH
SPDX-License-Identifier: AGPL-3.0-only
*/
// Package kubectl provides a kubectl-like interface for Kubernetes.
// Functions defined here should not make use of [os/exec].
/*
Package kubectl provides a kubectl-like interface for Kubernetes.
Functions defined here should not make use of [os/exec].
*/
package kubectl
import (
@ -26,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/scale/scheme"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/retry"
@ -38,35 +41,34 @@ type Kubectl struct {
apiextensionClient apiextensionsclientv1.ApiextensionsV1Interface
}
// New returns an empty Kubectl client. Need to call Initialize before usable.
func New() *Kubectl {
// NewUninitialized returns an empty Kubectl client.
// Initialize needs to be called before the client is usable.
func NewUninitialized() *Kubectl {
return &Kubectl{}
}
// NewFromConfig returns a Kubectl client using the given kubeconfig.
func NewFromConfig(kubeconfigPath string) (*Kubectl, error) {
clientConfig, err := clientcmd.BuildConfigFromFlags("", kubeconfigPath)
if err != nil {
return nil, fmt.Errorf("creating k8s client from kubeconfig: %w", err)
}
k := &Kubectl{}
if err := k.initialize(clientConfig); err != nil {
return nil, fmt.Errorf("initializing kubectl: %w", err)
}
return k, nil
}
// Initialize sets sets all required fields so the Kubectl client can be used.
func (k *Kubectl) Initialize(kubeconfig []byte) error {
clientConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig)
if err != nil {
return fmt.Errorf("creating k8s client config from kubeconfig: %w", err)
}
clientset, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
return fmt.Errorf("creating k8s client from kubeconfig: %w", err)
if err := k.initialize(clientConfig); err != nil {
return fmt.Errorf("initializing kubectl: %w", err)
}
k.Interface = clientset
dynamicClient, err := dynamic.NewForConfig(clientConfig)
if err != nil {
return fmt.Errorf("creating unstructed client: %w", err)
}
k.dynamicClient = dynamicClient
apiextensionClient, err := apiextensionsclientv1.NewForConfig(clientConfig)
if err != nil {
return fmt.Errorf("creating api extension client from kubeconfig: %w", err)
}
k.apiextensionClient = apiextensionClient
return nil
}
@ -86,8 +88,8 @@ func (k *Kubectl) ApplyCRD(ctx context.Context, rawCRD []byte) error {
return err
}
// GetCRDs retrieves all custom resource definitions currently installed in the cluster.
func (k *Kubectl) GetCRDs(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error) {
// ListCRDs retrieves all custom resource definitions currently installed in the cluster.
func (k *Kubectl) ListCRDs(ctx context.Context) ([]apiextensionsv1.CustomResourceDefinition, error) {
crds, err := k.apiextensionClient.CustomResourceDefinitions().List(ctx, metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("listing CRDs: %w", err)
@ -96,10 +98,9 @@ func (k *Kubectl) GetCRDs(ctx context.Context) ([]apiextensionsv1.CustomResource
return crds.Items, nil
}
// GetCRs retrieves all objects for a given CRD.
func (k *Kubectl) GetCRs(ctx context.Context, gvr schema.GroupVersionResource) ([]unstructured.Unstructured, error) {
crdClient := k.dynamicClient.Resource(gvr)
unstructuredList, err := crdClient.List(ctx, metav1.ListOptions{})
// ListCRs retrieves all objects for a given CRD.
func (k *Kubectl) ListCRs(ctx context.Context, gvr schema.GroupVersionResource) ([]unstructured.Unstructured, error) {
unstructuredList, err := k.dynamicClient.Resource(gvr).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, fmt.Errorf("listing CRs for GroupVersionResource %+v: %w", gvr, err)
}
@ -107,15 +108,35 @@ func (k *Kubectl) GetCRs(ctx context.Context, gvr schema.GroupVersionResource) (
return unstructuredList.Items, nil
}
// GetCR retrieves a Custom Resource given it's name and group version resource.
func (k *Kubectl) GetCR(ctx context.Context, gvr schema.GroupVersionResource, name string) (*unstructured.Unstructured, error) {
return k.dynamicClient.Resource(gvr).Get(ctx, name, metav1.GetOptions{})
}
// UpdateCR updates a Custom Resource given it's and group version resource.
func (k *Kubectl) UpdateCR(ctx context.Context, gvr schema.GroupVersionResource, obj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
return k.dynamicClient.Resource(gvr).Update(ctx, obj, metav1.UpdateOptions{})
}
// CreateConfigMap creates the provided configmap.
func (k *Kubectl) CreateConfigMap(ctx context.Context, configMap corev1.ConfigMap) error {
_, err := k.CoreV1().ConfigMaps(configMap.ObjectMeta.Namespace).Create(ctx, &configMap, metav1.CreateOptions{})
func (k *Kubectl) CreateConfigMap(ctx context.Context, configMap *corev1.ConfigMap) error {
_, err := k.CoreV1().ConfigMaps(configMap.ObjectMeta.Namespace).Create(ctx, configMap, metav1.CreateOptions{})
if err != nil {
return err
}
return nil
}
// GetConfigMap returns a ConfigMap given it's name and namespace.
func (k *Kubectl) GetConfigMap(ctx context.Context, namespace, name string) (*corev1.ConfigMap, error) {
return k.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
}
// UpdateConfigMap updates the given ConfigMap.
func (k *Kubectl) UpdateConfigMap(ctx context.Context, configMap *corev1.ConfigMap) (*corev1.ConfigMap, error) {
return k.CoreV1().ConfigMaps(configMap.ObjectMeta.Namespace).Update(ctx, configMap, metav1.UpdateOptions{})
}
// AnnotateNode adds the provided annotations to the node, identified by name.
func (k *Kubectl) AnnotateNode(ctx context.Context, nodeName, annotationKey, annotationValue string) error {
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
@ -132,20 +153,13 @@ func (k *Kubectl) AnnotateNode(ctx context.Context, nodeName, annotationKey, ann
})
}
// PatchFirstNodePodCIDR patches the firstNodePodCIDR of the first control-plane node for Cilium.
func (k *Kubectl) PatchFirstNodePodCIDR(ctx context.Context, firstNodePodCIDR string) error {
selector := labels.Set{"node-role.kubernetes.io/control-plane": ""}.AsSelector()
controlPlaneList, err := k.CoreV1().Nodes().List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
// KubernetesVersion returns the Kubernetes version of the cluster.
func (k *Kubectl) KubernetesVersion() (string, error) {
serverVersion, err := k.Discovery().ServerVersion()
if err != nil {
return err
return "", err
}
if len(controlPlaneList.Items) != 1 {
return fmt.Errorf("expected 1 control-plane node, got %d", len(controlPlaneList.Items))
}
nodeName := controlPlaneList.Items[0].Name
// Update the node's spec
_, err = k.CoreV1().Nodes().Patch(context.Background(), nodeName, types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"podCIDR":"%s"}}`, firstNodePodCIDR)), metav1.PatchOptions{})
return err
return serverVersion.GitVersion, nil
}
// ListAllNamespaces returns all namespaces in the cluster.
@ -162,6 +176,22 @@ func (k *Kubectl) GetNodes(ctx context.Context) ([]corev1.Node, error) {
return nodes.Items, nil
}
// PatchFirstNodePodCIDR patches the firstNodePodCIDR of the first control-plane node for Cilium.
func (k *Kubectl) PatchFirstNodePodCIDR(ctx context.Context, firstNodePodCIDR string) error {
selector := labels.Set{"node-role.kubernetes.io/control-plane": ""}.AsSelector()
controlPlaneList, err := k.CoreV1().Nodes().List(ctx, metav1.ListOptions{LabelSelector: selector.String()})
if err != nil {
return err
}
if len(controlPlaneList.Items) != 1 {
return fmt.Errorf("expected 1 control-plane node, got %d", len(controlPlaneList.Items))
}
nodeName := controlPlaneList.Items[0].Name
// Update the node's spec
_, err = k.CoreV1().Nodes().Patch(context.Background(), nodeName, types.MergePatchType, []byte(fmt.Sprintf(`{"spec":{"podCIDR":"%s"}}`, firstNodePodCIDR)), metav1.PatchOptions{})
return err
}
// EnforceCoreDNSSpread adds a pod anti-affinity to the CoreDNS deployment to ensure that
// CoreDNS pods are spread across nodes.
func (k *Kubectl) EnforceCoreDNSSpread(ctx context.Context) error {
@ -250,6 +280,28 @@ func (k *Kubectl) AddNodeSelectorsToDeployment(ctx context.Context, selectors ma
return nil
}
func (k *Kubectl) initialize(clientConfig *rest.Config) error {
clientset, err := kubernetes.NewForConfig(clientConfig)
if err != nil {
return fmt.Errorf("creating k8s client from kubeconfig: %w", err)
}
k.Interface = clientset
dynamicClient, err := dynamic.NewForConfig(clientConfig)
if err != nil {
return fmt.Errorf("creating unstructured client: %w", err)
}
k.dynamicClient = dynamicClient
apiextensionClient, err := apiextensionsclientv1.NewForConfig(clientConfig)
if err != nil {
return fmt.Errorf("creating api extension client from kubeconfig: %w", err)
}
k.apiextensionClient = apiextensionClient
return nil
}
// parseCRD takes a byte slice of data and tries to create a CustomResourceDefinition object from it.
func parseCRD(crdString []byte) (*v1.CustomResourceDefinition, error) {
sch := runtime.NewScheme()