diff --git a/api/v1alpha1/automatedclusterdiscovery_types.go b/api/v1alpha1/automatedclusterdiscovery_types.go index 4c169fb..5d616f7 100644 --- a/api/v1alpha1/automatedclusterdiscovery_types.go +++ b/api/v1alpha1/automatedclusterdiscovery_types.go @@ -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 @@ -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 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index e4fd7aa..bb6fb85 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -27,12 +27,6 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AKS) DeepCopyInto(out *AKS) { *out = *in - out.Filter = in.Filter - if in.Exclude != nil { - in, out := &in.Exclude, &out.Exclude - *out = make([]string, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AKS. @@ -45,21 +39,6 @@ func (in *AKS) DeepCopy() *AKS { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *AKSFilter) DeepCopyInto(out *AKSFilter) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AKSFilter. -func (in *AKSFilter) DeepCopy() *AKSFilter { - if in == nil { - return nil - } - out := new(AKSFilter) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AutomatedClusterDiscovery) DeepCopyInto(out *AutomatedClusterDiscovery) { *out = *in @@ -125,9 +104,23 @@ func (in *AutomatedClusterDiscoverySpec) DeepCopyInto(out *AutomatedClusterDisco if in.AKS != nil { in, out := &in.AKS, &out.AKS *out = new(AKS) - (*in).DeepCopyInto(*out) + **out = **in } out.Interval = in.Interval + if in.CommonLabels != nil { + in, out := &in.CommonLabels, &out.CommonLabels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.CommonAnnotations != nil { + in, out := &in.CommonAnnotations, &out.CommonAnnotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AutomatedClusterDiscoverySpec. diff --git a/config/crd/bases/clusters.weave.works_automatedclusterdiscoveries.yaml b/config/crd/bases/clusters.weave.works_automatedclusterdiscoveries.yaml index 1644425..029ef0b 100644 --- a/config/crd/bases/clusters.weave.works_automatedclusterdiscoveries.yaml +++ b/config/crd/bases/clusters.weave.works_automatedclusterdiscoveries.yaml @@ -39,24 +39,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 diff --git a/internal/controller/automatedclusterdiscovery_controller.go b/internal/controller/automatedclusterdiscovery_controller.go index 0638d53..faa8660 100644 --- a/internal/controller/automatedclusterdiscovery_controller.go +++ b/internal/controller/automatedclusterdiscovery_controller.go @@ -140,7 +140,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)) @@ -172,7 +172,7 @@ 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) @@ -180,15 +180,17 @@ func (r *AutomatedClusterDiscoveryReconciler) reconcileClusters(ctx context.Cont 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{ @@ -206,7 +208,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) @@ -215,11 +217,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 { @@ -236,9 +240,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 @@ -272,7 +276,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) } @@ -355,10 +359,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 } diff --git a/internal/controller/automatedclusterdiscovery_controller_test.go b/internal/controller/automatedclusterdiscovery_controller_test.go index 79f1b56..faef443 100644 --- a/internal/controller/automatedclusterdiscovery_controller_test.go +++ b/internal/controller/automatedclusterdiscovery_controller_test.go @@ -58,6 +58,11 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) { }) assert.NoError(t, err) + wantAnnotations := map[string]string{ + "test.example.com/annotation": "test", + "example.com/test": "annotation", + } + t.Run("Reconcile with AKS", func(t *testing.T) { aksCluster := &clustersv1alpha1.AutomatedClusterDiscovery{ ObjectMeta: metav1.ObjectMeta{ @@ -70,6 +75,10 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) { SubscriptionID: "subscription-123", }, Interval: metav1.Duration{Duration: time.Minute}, + CommonLabels: map[string]string{ + "example.com/label": "test", + }, + CommonAnnotations: wantAnnotations, }, } @@ -115,6 +124,7 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) { "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{} @@ -124,11 +134,13 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) { 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) value, err := clientcmd.Write(*testProvider.response[0].KubeConfig) assert.NoError(t, err) @@ -358,6 +370,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{ @@ -658,6 +671,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) &&