Skip to content

Commit

Permalink
Add Common Labels and Annotations to resources (#18)
Browse files Browse the repository at this point in the history
This adds functionality for adding a set of labels and annotations to the generated resources.

Setting commonLabels or commonAnnotations on the AutomatedClusterDiscovery resource will result in labels and annotations being applied to the generated GitopsCluster and Secret resources.
  • Loading branch information
bigkevmcd authored Nov 8, 2023
1 parent c6ea282 commit 27e0965
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 57 deletions.
16 changes: 5 additions & 11 deletions api/v1alpha1/automatedclusterdiscovery_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,6 @@ type AKS struct {
// SubscriptionID is the Azure subscription ID
// +required
SubscriptionID string `json:"subscriptionID"`

Filter AKSFilter `json:"filter,omitempty"`

// Exclude is the list of clusters to exclude
Exclude []string `json:"exclude,omitempty"`
}

// Filter criteria for AKS clusters
type AKSFilter struct {
// Location is the location of the AKS clusters
Location string `json:"location,omitempty"`
}

// AutomatedClusterDiscoverySpec defines the desired state of AutomatedClusterDiscovery
Expand All @@ -58,6 +47,11 @@ type AutomatedClusterDiscoverySpec struct {
// AutomatedClusterDiscovery.
// +optional
Suspend bool `json:"suspend,omitempty"`

// Labels to add to all generated resources.
CommonLabels map[string]string `json:"commonLabels,omitempty"`
// Annotations to add to all generated resources.
CommonAnnotations map[string]string `json:"commonAnnotations,omitempty"`
}

// AutomatedClusterDiscoveryStatus defines the observed state of AutomatedClusterDiscovery
Expand Down
37 changes: 15 additions & 22 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,22 @@ spec:
aks:
description: AKS defines the desired state of AKS
properties:
exclude:
description: Exclude is the list of clusters to exclude
items:
type: string
type: array
filter:
description: Filter criteria for AKS clusters
properties:
location:
description: Location is the location of the AKS clusters
type: string
type: object
subscriptionID:
description: SubscriptionID is the Azure subscription ID
type: string
required:
- subscriptionID
type: object
commonAnnotations:
additionalProperties:
type: string
description: Annotations to add to all generated resources.
type: object
commonLabels:
additionalProperties:
type: string
description: Labels to add to all generated resources.
type: object
interval:
description: The interval at which to run the discovery
type: string
Expand Down
46 changes: 34 additions & 12 deletions internal/controller/automatedclusterdiscovery_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ func (r *AutomatedClusterDiscoveryReconciler) SetupWithManager(mgr ctrl.Manager)
Complete(r)
}

func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Context, clusters []*providers.ProviderCluster, currentClusterID string, cd *clustersv1alpha1.AutomatedClusterDiscovery) ([]clustersv1alpha1.ResourceRef, error) {
func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Context, clusters []*providers.ProviderCluster, currentClusterID string, acd *clustersv1alpha1.AutomatedClusterDiscovery) ([]clustersv1alpha1.ResourceRef, error) {
logger := log.FromContext(ctx)
logger.Info("reconciling clusters", "count", len(clusters))

Expand Down Expand Up @@ -220,23 +220,25 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont

gitopsCluster := newGitopsCluster(secretName, types.NamespacedName{
Name: cluster.Name,
Namespace: cd.Namespace,
Namespace: acd.Namespace,
})

clusterRef, err := clustersv1alpha1.ResourceRefFromObject(gitopsCluster)
if err != nil {
return inventoryResources, err
}

if isExistingRef(clusterRef, cd.Status.Inventory) {
if isExistingRef(clusterRef, acd.Status.Inventory) {
existingClusters = append(existingClusters, clusterRef)
}

logger.Info("creating gitops cluster", "name", gitopsCluster.GetName())
if err := controllerutil.SetOwnerReference(cd, gitopsCluster, r.Scheme); err != nil {
if err := controllerutil.SetOwnerReference(acd, gitopsCluster, r.Scheme); err != nil {
return inventoryResources, fmt.Errorf("failed to set ownership on created GitopsCluster: %w", err)
}
gitopsCluster.SetLabels(labelsForResource(*cd))
gitopsCluster.SetLabels(labelsForResource(*acd))
gitopsCluster.SetAnnotations(acd.Spec.CommonAnnotations)

_, err = controllerutil.CreateOrPatch(ctx, r.Client, gitopsCluster, func() error {
gitopsCluster.Spec = gitopsv1alpha1.GitopsClusterSpec{
SecretRef: &meta.LocalObjectReference{
Expand All @@ -254,7 +256,7 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont

secret := newSecret(types.NamespacedName{
Name: secretName,
Namespace: cd.Namespace,
Namespace: acd.Namespace,
})

secretRef, err := clustersv1alpha1.ResourceRefFromObject(secret)
Expand All @@ -263,11 +265,13 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont
}

logger.Info("creating secret", "name", secret.GetName())
if err := controllerutil.SetOwnerReference(cd, secret, r.Scheme); err != nil {
if err := controllerutil.SetOwnerReference(acd, secret, r.Scheme); err != nil {
return inventoryResources, fmt.Errorf("failed to set ownership on created Secret: %w", err)
}

secret.SetLabels(labelsForResource(*cd))
secret.SetLabels(labelsForResource(*acd))
secret.SetAnnotations(acd.Spec.CommonAnnotations)

_, err = controllerutil.CreateOrPatch(ctx, r.Client, secret, func() error {
value, err := clientcmd.Write(*cluster.KubeConfig)
if err != nil {
Expand All @@ -284,9 +288,9 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont
inventoryResources = append(inventoryResources, secretRef)
}

if cd.Status.Inventory != nil {
if acd.Status.Inventory != nil {
clustersToDelete := []client.Object{}
for _, item := range cd.Status.Inventory.Entries {
for _, item := range acd.Status.Inventory.Entries {
obj, err := unstructuredFromResourceRef(item)
if err != nil {
return inventoryResources, err
Expand Down Expand Up @@ -320,7 +324,7 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont
}

secretToUpdate := &corev1.Secret{}
if err := r.Client.Get(ctx, types.NamespacedName{Name: existingCluster.Spec.SecretRef.Name, Namespace: cd.GetNamespace()}, secretToUpdate); err != nil {
if err := r.Client.Get(ctx, types.NamespacedName{Name: existingCluster.Spec.SecretRef.Name, Namespace: acd.GetNamespace()}, secretToUpdate); err != nil {
// TODO: don't error, create a new secret!
return inventoryResources, fmt.Errorf("failed to get the secret to update: %w", err)
}
Expand Down Expand Up @@ -403,10 +407,28 @@ func clustersToMapping(clusters []*providers.ProviderCluster) map[string]*provid
}

func labelsForResource(acd clustersv1alpha1.AutomatedClusterDiscovery) map[string]string {
return map[string]string{
appliedLabels := map[string]string{
k8sManagedByLabel: "cluster-reflector-controller",
"clusters.weave.works/origin-name": acd.GetName(),
"clusters.weave.works/origin-namespace": acd.GetNamespace(),
"clusters.weave.works/origin-type": acd.Spec.Type,
}

return mergeMaps(acd.Spec.CommonLabels, appliedLabels)
}

func mergeMaps[K comparable, V any](maps ...map[K]V) map[K]V {
result := map[K]V{}

for _, map_ := range maps {
if map_ == nil {
continue
}

for k, v := range map_ {
result[k] = v
}
}

return result
}
98 changes: 98 additions & 0 deletions internal/controller/automatedclusterdiscovery_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,91 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
assertHasOwnerReference(t, secret, clusterRef)
})

t.Run("Reconcile with common labels and annotations", func(t *testing.T) {
wantAnnotations := map[string]string{
"test.example.com/annotation": "test",
"example.com/test": "annotation",
}

aksCluster := &clustersv1alpha1.AutomatedClusterDiscovery{
ObjectMeta: metav1.ObjectMeta{
Name: "test-aks",
Namespace: "default",
},
Spec: clustersv1alpha1.AutomatedClusterDiscoverySpec{
Type: "aks",
AKS: &clustersv1alpha1.AKS{
SubscriptionID: "subscription-123",
},
Interval: metav1.Duration{Duration: time.Minute},
CommonLabels: map[string]string{
"example.com/label": "test",
},
CommonAnnotations: wantAnnotations,
},
}

testProvider := stubProvider{
response: []*providers.ProviderCluster{
{
Name: "cluster-1",
KubeConfig: &kubeconfig.Config{
APIVersion: "v1",
Clusters: map[string]*kubeconfig.Cluster{
"cluster-1": {
Server: "https://cluster-prod.example.com/",
CertificateAuthorityData: []uint8(testCAData),
},
},
},
},
},
}

reconciler := &AutomatedClusterDiscoveryReconciler{
Client: k8sClient,
Scheme: scheme,
AKSProvider: func(providerID string) providers.Provider {
return &testProvider
},
}

assert.NoError(t, reconciler.SetupWithManager(mgr))

ctx := context.TODO()
key := types.NamespacedName{Name: aksCluster.Name, Namespace: aksCluster.Namespace}
err = k8sClient.Create(ctx, aksCluster)
assert.NoError(t, err)
defer deleteClusterDiscoveryAndInventory(t, k8sClient, aksCluster)

result, err := reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: key})
assert.NoError(t, err)
assert.Equal(t, ctrl.Result{RequeueAfter: time.Minute}, result)

wantLabels := map[string]string{
"app.kubernetes.io/managed-by": "cluster-reflector-controller",
"clusters.weave.works/origin-name": "test-aks",
"clusters.weave.works/origin-namespace": "default",
"clusters.weave.works/origin-type": "aks",
"example.com/label": "test",
}

gitopsCluster := &gitopsv1alpha1.GitopsCluster{}
err = k8sClient.Get(ctx, types.NamespacedName{Name: "cluster-1", Namespace: aksCluster.Namespace}, gitopsCluster)
assert.NoError(t, err)
assert.Equal(t, gitopsv1alpha1.GitopsClusterSpec{
SecretRef: &meta.LocalObjectReference{Name: "cluster-1-kubeconfig"},
}, gitopsCluster.Spec)
assertHasLabels(t, gitopsCluster, wantLabels)
assertHasAnnotations(t, gitopsCluster, wantAnnotations)

secret := &corev1.Secret{}
err = k8sClient.Get(ctx, types.NamespacedName{Name: "cluster-1-kubeconfig", Namespace: aksCluster.Namespace}, secret)
assert.NoError(t, err)
assertHasLabels(t, secret, wantLabels)
assertHasAnnotations(t, secret, wantAnnotations)
})

t.Run("Reconcile when executing in cluster and cluster matches reflector cluster", func(t *testing.T) {
aksCluster := &clustersv1alpha1.AutomatedClusterDiscovery{
ObjectMeta: metav1.ObjectMeta{
Expand Down Expand Up @@ -362,6 +447,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, value, secret.Data["value"])
})

t.Run("Reconcile suspended cluster discovery resource", func(t *testing.T) {
ctx := context.TODO()
aksCluster := &clustersv1alpha1.AutomatedClusterDiscovery{
Expand Down Expand Up @@ -745,6 +831,18 @@ func assertHasLabels(t *testing.T, o client.Object, want map[string]string) {
}
}

func assertHasAnnotations(t *testing.T, o client.Object, want map[string]string) {
annotations := o.GetAnnotations()
for k, v := range want {
kv, ok := annotations[k]
if !ok {
t.Errorf("%s %s/%s is missing annotation %q with value %q", o.GetObjectKind().GroupVersionKind().Kind, o.GetNamespace(), o.GetName(), k, v)
continue
}
assert.Equal(t, v, kv)
}
}

func isOwnerReferenceEqual(a, b metav1.OwnerReference) bool {
return (a.APIVersion == b.APIVersion) &&
(a.Kind == b.Kind) &&
Expand Down

0 comments on commit 27e0965

Please sign in to comment.