diff --git a/api/v1alpha1/automatedclusterdiscovery_types.go b/api/v1alpha1/automatedclusterdiscovery_types.go index 65e1c2d..4c169fb 100644 --- a/api/v1alpha1/automatedclusterdiscovery_types.go +++ b/api/v1alpha1/automatedclusterdiscovery_types.go @@ -17,6 +17,7 @@ limitations under the License. package v1alpha1 import ( + "github.com/fluxcd/pkg/apis/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -61,6 +62,8 @@ type AutomatedClusterDiscoverySpec struct { // AutomatedClusterDiscoveryStatus defines the observed state of AutomatedClusterDiscovery type AutomatedClusterDiscoveryStatus struct { + meta.ReconcileRequestStatus `json:",inline"` + // Inventory contains the list of Kubernetes resource object references that // have been successfully applied // +optional diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8c482be..e4fd7aa 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -143,6 +143,7 @@ func (in *AutomatedClusterDiscoverySpec) DeepCopy() *AutomatedClusterDiscoverySp // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AutomatedClusterDiscoveryStatus) DeepCopyInto(out *AutomatedClusterDiscoveryStatus) { *out = *in + out.ReconcileRequestStatus = in.ReconcileRequestStatus if in.Inventory != nil { in, out := &in.Inventory, &out.Inventory *out = new(ResourceInventory) diff --git a/config/crd/bases/clusters.weave.works_automatedclusterdiscoveries.yaml b/config/crd/bases/clusters.weave.works_automatedclusterdiscoveries.yaml index c5b3ac7..1644425 100644 --- a/config/crd/bases/clusters.weave.works_automatedclusterdiscoveries.yaml +++ b/config/crd/bases/clusters.weave.works_automatedclusterdiscoveries.yaml @@ -104,6 +104,11 @@ spec: type: object type: array type: object + lastHandledReconcileAt: + description: LastHandledReconcileAt holds the value of the most recent + reconcile request value, so a change of the annotation value can + be detected. + type: string type: object type: object served: true diff --git a/go.mod b/go.mod index dd3330b..8599dd4 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerservice/armcontainerservice/v4 v4.3.0 github.com/Azure/go-autorest/autorest v0.11.29 github.com/fluxcd/pkg/apis/meta v1.1.2 + github.com/fluxcd/pkg/runtime v0.35.0 github.com/google/go-cmp v0.5.9 github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.8.4 diff --git a/go.sum b/go.sum index d36b4eb..3f0777b 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,8 @@ github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2 github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/fluxcd/pkg/apis/meta v1.1.2 h1:Unjo7hxadtB2dvGpeFqZZUdsjpRA08YYSBb7dF2WIAM= github.com/fluxcd/pkg/apis/meta v1.1.2/go.mod h1:BHQyRHCskGMEDf6kDGbgQ+cyiNpUHbLsCOsaMYM2maI= +github.com/fluxcd/pkg/runtime v0.35.0 h1:9PYLcul8qdfLYQArcYpHe/QuMqyhAGGFN9F7uY/QVX4= +github.com/fluxcd/pkg/runtime v0.35.0/go.mod h1:sAaSTH8RHj3Y99xj0AtAndDTe5cv0DP4enyLV62EO78= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -200,7 +202,7 @@ github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJf github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= diff --git a/internal/controller/automatedclusterdiscovery_controller.go b/internal/controller/automatedclusterdiscovery_controller.go index 33e6f80..5f2cd8b 100644 --- a/internal/controller/automatedclusterdiscovery_controller.go +++ b/internal/controller/automatedclusterdiscovery_controller.go @@ -22,6 +22,11 @@ import ( "sort" "time" + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/predicates" + gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1" + clustersv1alpha1 "github.com/weaveworks/cluster-reflector-controller/api/v1alpha1" + "github.com/weaveworks/cluster-reflector-controller/pkg/providers" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -30,14 +35,11 @@ import ( "k8s.io/client-go/tools/clientcmd" "sigs.k8s.io/cli-utils/pkg/object" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/log" - - "github.com/fluxcd/pkg/apis/meta" - gitopsv1alpha1 "github.com/weaveworks/cluster-controller/api/v1alpha1" - clustersv1alpha1 "github.com/weaveworks/cluster-reflector-controller/api/v1alpha1" - "github.com/weaveworks/cluster-reflector-controller/pkg/providers" + "sigs.k8s.io/controller-runtime/pkg/predicate" ) const k8sManagedByLabel = "app.kubernetes.io/managed-by" @@ -77,6 +79,11 @@ func (r *AutomatedClusterDiscoveryReconciler) Reconcile(ctx context.Context, req "name", clusterDiscovery.Spec.Name, ) + // Set the value of the reconciliation request in status. + if v, ok := meta.ReconcileAnnotationValue(clusterDiscovery.GetAnnotations()); ok { + clusterDiscovery.Status.LastHandledReconcileAt = v + } + if clusterDiscovery.Spec.Type == "aks" { logger.Info("reconciling AKS cluster reflector", "name", clusterDiscovery.Spec.Name, @@ -127,7 +134,8 @@ func (r *AutomatedClusterDiscoveryReconciler) Reconcile(ctx context.Context, req // SetupWithManager sets up the controller with the Manager. func (r *AutomatedClusterDiscoveryReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&clustersv1alpha1.AutomatedClusterDiscovery{}). + For(&clustersv1alpha1.AutomatedClusterDiscovery{}, builder.WithPredicates( + predicate.Or(predicate.GenerationChangedPredicate{}, predicates.ReconcileRequestedPredicate{}))). Complete(r) } diff --git a/internal/controller/automatedclusterdiscovery_controller_test.go b/internal/controller/automatedclusterdiscovery_controller_test.go index aeea677..79f1b56 100644 --- a/internal/controller/automatedclusterdiscovery_controller_test.go +++ b/internal/controller/automatedclusterdiscovery_controller_test.go @@ -483,6 +483,88 @@ func TestAutomatedClusterDiscoveryReconciler(t *testing.T) { }) } +func TestReconcilingWithAnnotationChange(t *testing.T) { + ctx := context.TODO() + testEnv := &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "config", "crd", "bases"), + "testdata/crds"}, + } + cfg, err := testEnv.Start() + if err != nil { + t.Fatalf("Failed to start test environment: %v", err) + } + defer func() { + if err := testEnv.Stop(); err != nil { + t.Fatalf("Failed to stop test environment: %v", err) + } + }() + + scheme := runtime.NewScheme() + assert.NoError(t, clustersv1alpha1.AddToScheme(scheme)) + assert.NoError(t, gitopsv1alpha1.AddToScheme(scheme)) + assert.NoError(t, clientgoscheme.AddToScheme(scheme)) + + k8sClient, err := client.New(cfg, client.Options{Scheme: scheme}) + assert.NoError(t, err) + + mgr, err := manager.New(cfg, manager.Options{ + Scheme: scheme, + }) + assert.NoError(t, err) + + 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}, + }, + } + + err = k8sClient.Create(ctx, aksCluster) + assert.NoError(t, err) + defer deleteClusterDiscoveryAndInventory(t, k8sClient, aksCluster) + + reconciler := &AutomatedClusterDiscoveryReconciler{ + Client: k8sClient, + Scheme: scheme, + AKSProvider: func(providerID string) providers.Provider { + return &stubProvider{} + }, + } + assert.NoError(t, reconciler.SetupWithManager(mgr)) + + key := types.NamespacedName{Name: aksCluster.Name, Namespace: aksCluster.Namespace} + _, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: key}) + assert.NoError(t, err) + + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(aksCluster), aksCluster) + assert.NoError(t, err) + + assert.Equal(t, aksCluster.Status.LastHandledReconcileAt, "") + + // add an annotation + aksCluster.Annotations = map[string]string{ + meta.ReconcileRequestAnnotation: "testing", + } + err = k8sClient.Update(ctx, aksCluster) + assert.NoError(t, err) + + _, err = reconciler.Reconcile(ctx, ctrl.Request{NamespacedName: key}) + assert.NoError(t, err) + + err = k8sClient.Get(ctx, client.ObjectKeyFromObject(aksCluster), aksCluster) + assert.NoError(t, err) + assert.Equal(t, "testing", aksCluster.Annotations[meta.ReconcileRequestAnnotation]) + assert.Equal(t, aksCluster.Status.LastHandledReconcileAt, "testing") +} + type stubProvider struct { response []*providers.ProviderCluster clusterID string