mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-05-06 08:15:48 -04:00
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:
parent
3bf316e28f
commit
afa7fd0edb
24 changed files with 1024 additions and 1160 deletions
|
@ -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()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue