Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Common Labels and Annotations to resources #18

Merged
merged 1 commit into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading