Skip to content

Commit

Permalink
Get kubeconfig secret instead of listing all secrets
Browse files Browse the repository at this point in the history
  • Loading branch information
sibucan committed Nov 20, 2024
1 parent 34341f1 commit 1f85f6d
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 37 deletions.
42 changes: 19 additions & 23 deletions internal/controller/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,20 @@ import (
"fmt"
"time"

argocdv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
"github.com/superorbital/capargo/pkg/providers"
"github.com/superorbital/capargo/pkg/types"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/reconcile"

argocdv1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

var logger = logf.Log.WithName("capargo-controller")
Expand Down Expand Up @@ -78,30 +79,25 @@ func (c *ClusterKubeconfigReconciler) deleteArgoCluster(ctx context.Context, nam
// createOrUpdateArgoCluster uploads the latest version of the cluster
// kubeconfig as an ArgoCD cluster secret to the cluster.
func (c *ClusterKubeconfigReconciler) createOrUpdateArgoCluster(ctx context.Context, cluster *capiv1beta1.Cluster) error {
// Find the kubeconfig secret located in the same namespace where the
// cluster's controlplaneref is.
cpr := cluster.Spec.ControlPlaneRef
secrets := &corev1.SecretList{}
err := c.List(ctx, secrets, client.InNamespace(cpr.Namespace))
// Find the kubeconfig secret.
capiSecret := &corev1.Secret{}
namespacedName, err := providers.GetCapiKubeconfigNamespacedName(cluster)
if err != nil {
return err
}
found := false
index := 0
for i, secret := range secrets.Items {
if providers.IsCapiKubeconfig(&secret, cluster) {
found = true
index = i
break
}
if err := c.Get(ctx, namespacedName, capiSecret, &client.GetOptions{}); err != nil {
return err
}

if !found {
return fmt.Errorf("kubeconfig secret not found for cluster %s/%s", cluster.Namespace, cluster.Name)
valid, err := providers.IsCapiKubeconfig(capiSecret, cluster)
if err != nil {
return err
}

// Get the kubeconfig from the secret.
capiSecret := secrets.Items[index]
// Ensure that the secret will contain a kubeconfig, and retrieve it.
if !valid {
return fmt.Errorf("secret %s does not contain kubeconfig for cluster %s/%s",
capiSecret.Name, cluster.Namespace, cluster.Name)
}
configBytes, ok := capiSecret.Data["value"]
if !ok {
return fmt.Errorf("secret %s/%s for cluster %s does not contain key \"value\"",
Expand Down
12 changes: 11 additions & 1 deletion pkg/providers/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)

type awsManagedControlPlane struct {
Expand All @@ -12,6 +13,15 @@ type awsManagedControlPlane struct {
APIVersion string
}

// GetNamespacedName returns the namespace and name of a cluster
// with an AWS managed control plane.
func (a awsManagedControlPlane) GetNamespacedName() types.NamespacedName {
return types.NamespacedName{
Name: fmt.Sprintf("%s-user-kubeconfig", a.Name),
Namespace: a.Namespace,
}
}

// IsKubeconfig determines whether the secret provided is an
// AWSManagedControlPlane kubeconfig or not.
func (a awsManagedControlPlane) IsKubeconfig(secret *corev1.Secret) bool {
Expand All @@ -34,7 +44,7 @@ func (a awsManagedControlPlane) IsKubeconfig(secret *corev1.Secret) bool {
return false
}
or := ors[0]
if or.Name != "AWSManagedControlPlane" {
if or.Kind != "AWSManagedControlPlane" {
logger.V(4).Info("Secret is not owned by AWSManagedControlPlane",
"secret namespace", secret.GetNamespace(),
"secret name", secret.GetName(),
Expand Down
12 changes: 11 additions & 1 deletion pkg/providers/kubeadm.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
)

type kubeadmControlPlane struct {
Expand All @@ -12,6 +13,15 @@ type kubeadmControlPlane struct {
APIVersion string
}

// GetNamespacedName returns the namespace and name of a cluster
// with a kubeadm-bootstrapped control plane.
func (k kubeadmControlPlane) GetNamespacedName() types.NamespacedName {
return types.NamespacedName{
Name: fmt.Sprintf("%s-kubeconfig", k.Name),
Namespace: k.Namespace,
}
}

// IsKubeconfig determines whether the secret provided is a
// KubeadmControlPlane kubeconfig or not.
func (k kubeadmControlPlane) IsKubeconfig(secret *corev1.Secret) bool {
Expand All @@ -34,7 +44,7 @@ func (k kubeadmControlPlane) IsKubeconfig(secret *corev1.Secret) bool {
return false
}
or := ors[0]
if or.Name != "KubeadmControlPlane" {
if or.Kind != "KubeadmControlPlane" {
logger.V(4).Info("Secret is not owned by KubeadmControlPlane",
"secret namespace", secret.GetNamespace(),
"secret name", secret.GetName(),
Expand Down
60 changes: 48 additions & 12 deletions pkg/providers/providers.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,80 @@
package providers

import (
"fmt"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
clusterv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1"
logf "sigs.k8s.io/controller-runtime/pkg/log"
)

var logger = logf.Log.WithName("capargo-providers")

// controlPlaneRefKind
type controlPlaneRefKind string

const (
// kubeadmKind
kubeadmKind controlPlaneRefKind = "KubeadmControlPlane"

// awsManagedKind
awsManagedKind controlPlaneRefKind = "AWSManagedControlPlane"

// vclusterKind
vclusterKind controlPlaneRefKind = "VCluster"
)

type provider interface {
GetNamespacedName() types.NamespacedName
IsKubeconfig(*corev1.Secret) bool
}

// isCapiKubeconfig determines whether the secret provided is a CAPI kubeconfig
// from a given control plane controller.
func IsCapiKubeconfig(secret *corev1.Secret, cluster *clusterv1beta1.Cluster) bool {
switch cluster.Spec.ControlPlaneRef.Kind {
case "KubeadmControlPlane":
// getProvider returns the provider interface for a given CAPI cluster,
func getProvider(cluster *clusterv1beta1.Cluster) (provider, error) {
switch controlPlaneRefKind(cluster.Spec.ControlPlaneRef.Kind) {
case kubeadmKind:
var p provider = kubeadmControlPlane{
APIVersion: cluster.Spec.ControlPlaneRef.APIVersion,
Name: cluster.Spec.ControlPlaneRef.Name,
Namespace: cluster.Spec.ControlPlaneRef.Namespace,
}
return p.IsKubeconfig(secret)
case "AWSManagedControlPlane":
return p, nil
case awsManagedKind:
var p provider = awsManagedControlPlane{
APIVersion: cluster.Spec.ControlPlaneRef.APIVersion,
Name: cluster.Spec.ControlPlaneRef.Name,
Namespace: cluster.Spec.ControlPlaneRef.Namespace,
}
return p.IsKubeconfig(secret)
case "VCluster":
return p, nil
case vclusterKind:
var p provider = vCluster{
APIVersion: cluster.Spec.ControlPlaneRef.APIVersion,
Name: cluster.Spec.ControlPlaneRef.Name,
Namespace: cluster.Spec.ControlPlaneRef.Namespace,
}
return p.IsKubeconfig(secret)
return p, nil
default:
logger.V(2).Info("ControlPlaneRef kind unsupported", "kind", cluster.Spec.ControlPlaneRef.Kind)
return false
return nil, fmt.Errorf("controlPlaneRef kind %s unsupported", cluster.Spec.ControlPlaneRef.Kind)
}
}

// IsCapiKubeconfig determines whether the secret provided is a CAPI kubeconfig
// from a given control plane controller.
func IsCapiKubeconfig(secret *corev1.Secret, cluster *clusterv1beta1.Cluster) (bool, error) {
p, err := getProvider(cluster)
if err != nil {
return false, err
}
return p.IsKubeconfig(secret), nil
}

// GetCapiKubeconfigNamespacedName retrieves the expected namespace and name
// for a CAPI cluster's kubeconfig.
func GetCapiKubeconfigNamespacedName(cluster *clusterv1beta1.Cluster) (types.NamespacedName, error) {
p, err := getProvider(cluster)
if err != nil {
return types.NamespacedName{}, err
}
return p.GetNamespacedName(), nil
}
170 changes: 170 additions & 0 deletions pkg/providers/providers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package providers

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/google/uuid"
"k8s.io/apimachinery/pkg/types"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
capiv1beta1 "sigs.k8s.io/cluster-api/api/v1beta1"
)

var _ = Describe("Provider functions", func() {
Context("When a cluster has a kubeadm controlPlaneRef", func() {
clusterName := "kubeadm-cluster"
clusterNamespace := "kubeadm-cluster-namespace"
cluster := capiv1beta1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: clusterNamespace,
UID: types.UID(uuid.New().String()),
},
Spec: capiv1beta1.ClusterSpec{
ControlPlaneRef: &corev1.ObjectReference{
APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
Kind: "KubeadmControlPlane",
Name: clusterName,
Namespace: clusterNamespace,
},
},
}

var kubeconfig = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName + "-kubeconfig",
Namespace: clusterNamespace,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "controlplane.cluster.x-k8s.io/v1beta1",
BlockOwnerDeletion: func(v bool) *bool { return &v }(true),
Controller: func(v bool) *bool { return &v }(true),
Kind: "KubeadmControlPlane",
UID: cluster.GetUID(),
},
},
},
Type: "cluster.x-k8s.io/secret",
Data: map[string][]byte{},
}
It("should return the proper name for the kubeconfig", func() {
By("providing a namespaced cluster object")
namespacedName, err := GetCapiKubeconfigNamespacedName(&cluster)

By("asserting that the name is correct")
Expect(err).NotTo(HaveOccurred())
Expect(namespacedName).To(Equal(types.NamespacedName{Name: kubeconfig.Name, Namespace: kubeconfig.Namespace}))
})

It("should validate the kubeconfig", func() {
By("providing a kubeconfig secret object")
validated, err := IsCapiKubeconfig(&kubeconfig, &cluster)

By("asserting that the secret is a kubeadm kubeconfig")
Expect(err).NotTo(HaveOccurred())
Expect(validated).To(BeTrue())
})
})

Context("When a cluster has a vcluster controlPlaneRef", func() {
clusterName := "vcluster-cluster"
clusterNamespace := "vcluster-cluster-namespace"
cluster := capiv1beta1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: clusterNamespace,
UID: types.UID(uuid.New().String()),
},
Spec: capiv1beta1.ClusterSpec{
ControlPlaneRef: &corev1.ObjectReference{
APIVersion: "infrastructure.cluster.x-k8s.io/v1alpha1",
Kind: "VCluster",
Name: clusterName,
Namespace: clusterNamespace,
},
},
}

var kubeconfig = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName + "-kubeconfig",
Namespace: clusterNamespace,
},
Data: map[string][]byte{},
}
It("should return the proper name for the kubeconfig", func() {
By("providing a namespaced cluster object")
namespacedName, err := GetCapiKubeconfigNamespacedName(&cluster)

By("asserting that the name is correct")
Expect(err).NotTo(HaveOccurred())
Expect(namespacedName).To(Equal(types.NamespacedName{Name: kubeconfig.Name, Namespace: kubeconfig.Namespace}))
})

It("should validate the kubeconfig", func() {
By("providing a kubeconfig secret object")
validated, err := IsCapiKubeconfig(&kubeconfig, &cluster)

By("asserting that the secret is a vcluster kubeconfig")
Expect(err).NotTo(HaveOccurred())
Expect(validated).To(BeTrue())
})
})

Context("When a cluster has an AWS managed controlPlaneRef", func() {
clusterName := "eks-cluster"
clusterNamespace := "eks-cluster-namespace"
cluster := capiv1beta1.Cluster{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName,
Namespace: clusterNamespace,
UID: types.UID(uuid.New().String()),
},
Spec: capiv1beta1.ClusterSpec{
ControlPlaneRef: &corev1.ObjectReference{
APIVersion: "controlplane.cluster.x-k8s.io/v1beta2",
Kind: "AWSManagedControlPlane",
Name: clusterName,
Namespace: clusterNamespace,
},
},
}

var kubeconfig = corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: clusterName + "-user-kubeconfig",
Namespace: clusterNamespace,
OwnerReferences: []metav1.OwnerReference{
{
APIVersion: "controlplane.cluster.x-k8s.io/v1beta2",
BlockOwnerDeletion: func(v bool) *bool { return &v }(true),
Controller: func(v bool) *bool { return &v }(true),
Kind: "AWSManagedControlPlane",
UID: cluster.GetUID(),
},
},
},
Type: "cluster.x-k8s.io/secret",
Data: map[string][]byte{},
}
It("should return the proper name for the kubeconfig", func() {
By("providing a namespaced cluster object")
namespacedName, err := GetCapiKubeconfigNamespacedName(&cluster)

By("asserting that the name is correct")
Expect(err).NotTo(HaveOccurred())
Expect(namespacedName).To(Equal(types.NamespacedName{Name: kubeconfig.Name, Namespace: kubeconfig.Namespace}))
})

It("should validate the kubeconfig", func() {
By("providing a kubeconfig secret object")
validated, err := IsCapiKubeconfig(&kubeconfig, &cluster)

By("asserting that the secret is an AWS managed kubeconfig")
Expect(err).NotTo(HaveOccurred())
Expect(validated).To(BeTrue())
})
})
})
Loading

0 comments on commit 1f85f6d

Please sign in to comment.