diff --git a/cmd/main.go b/cmd/main.go index f47b2d32..cd63d72d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -19,6 +19,7 @@ package main import ( "flag" "os" + "strings" "time" "k8s.io/apimachinery/pkg/runtime" @@ -26,6 +27,7 @@ import ( clientgoscheme "k8s.io/client-go/kubernetes/scheme" _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" @@ -79,17 +81,33 @@ func main() { } opts.BindFlags(flag.CommandLine) flag.Parse() + logger := zap.New(zap.UseFlagOptions(&opts)) + ctrl.SetLogger(logger) - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + var watchNamespaces = "WATCH_NAMESPACES" + defaultOptions := ctrl.Options{ Scheme: scheme, Metrics: metricsserver.Options{BindAddress: metricsAddr}, WebhookServer: webhook.NewServer(webhook.Options{Port: 9443}), HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "a3f98d6c.kuadrant.io", - }) + } + + if watch := os.Getenv(watchNamespaces); watch != "" { + namespaces := strings.Split(watch, ",") + logger.Info("watching namespaces set ", watchNamespaces, namespaces) + cacheOpts := cache.Options{ + DefaultNamespaces: map[string]cache.Config{}, + } + for _, ns := range namespaces { + cacheOpts.DefaultNamespaces[ns] = cache.Config{} + } + defaultOptions.Cache = cacheOpts + + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), defaultOptions) if err != nil { setupLog.Error(err, "unable to start manager") os.Exit(1) diff --git a/internal/controller/dnsrecord_controller.go b/internal/controller/dnsrecord_controller.go index 454b9973..4e6dd6df 100644 --- a/internal/controller/dnsrecord_controller.go +++ b/internal/controller/dnsrecord_controller.go @@ -32,6 +32,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" externaldnsendpoint "sigs.k8s.io/external-dns/endpoint" @@ -186,6 +187,23 @@ func (r *DNSRecordReconciler) SetupWithManager(mgr ctrl.Manager, requeueIn, vali return ctrl.NewControllerManagedBy(mgr). For(&v1alpha1.DNSRecord{}). + Watches(&v1alpha1.ManagedZone{}, handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request { + logger := log.FromContext(ctx) + toReconcile := []reconcile.Request{} + // list dns records in the maanagedzone namespace as they will be in the same namespace as the zone + records := &v1alpha1.DNSRecordList{} + if err := mgr.GetClient().List(ctx, records, &client.ListOptions{Namespace: o.GetNamespace()}); err != nil { + logger.Error(err, "failed to list dnsrecords ", "namespace", o.GetNamespace()) + return toReconcile + } + for _, record := range records.Items { + if record.Spec.ManagedZoneRef.Name == o.GetName() { + logger.Info("managed zone updated", "managedzone", o.GetNamespace()+"/"+o.GetName(), "enqueuing dnsrecord ", record.GetName()) + toReconcile = append(toReconcile, reconcile.Request{NamespacedName: client.ObjectKeyFromObject(&record)}) + } + } + return toReconcile + })). Complete(r) } diff --git a/internal/controller/dnsrecord_controller_test.go b/internal/controller/dnsrecord_controller_test.go index e39a10ea..03d59160 100644 --- a/internal/controller/dnsrecord_controller_test.go +++ b/internal/controller/dnsrecord_controller_test.go @@ -39,6 +39,7 @@ var _ = Describe("DNSRecordReconciler", func() { var dnsRecord2 *v1alpha1.DNSRecord var dnsProviderSecret *v1.Secret var managedZone *v1alpha1.ManagedZone + var brokenZone *v1alpha1.ManagedZone var testNamespace string BeforeEach(func() { @@ -46,9 +47,11 @@ var _ = Describe("DNSRecordReconciler", func() { dnsProviderSecret = testBuildInMemoryCredentialsSecret("inmemory-credentials", testNamespace) managedZone = testBuildManagedZone("mz-example-com", testNamespace, "example.com", dnsProviderSecret.Name) + brokenZone = testBuildManagedZone("mz-fix-com", testNamespace, "fix.com", "not-there") Expect(k8sClient.Create(ctx, dnsProviderSecret)).To(Succeed()) Expect(k8sClient.Create(ctx, managedZone)).To(Succeed()) + Expect(k8sClient.Create(ctx, brokenZone)).To(Succeed()) Eventually(func(g Gomega) { err := k8sClient.Get(ctx, client.ObjectKeyFromObject(managedZone), managedZone) g.Expect(err).NotTo(HaveOccurred()) @@ -61,6 +64,18 @@ var _ = Describe("DNSRecordReconciler", func() { ) }, TestTimeoutMedium, time.Second).Should(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(brokenZone), brokenZone) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(brokenZone.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(v1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionFalse), + "ObservedGeneration": Equal(brokenZone.Generation), + })), + ) + }, TestTimeoutMedium, time.Second).Should(Succeed()) + dnsRecord = &v1alpha1.DNSRecord{ ObjectMeta: metav1.ObjectMeta{ Name: "foo.example.com", @@ -72,7 +87,7 @@ var _ = Describe("DNSRecordReconciler", func() { ManagedZoneRef: &v1alpha1.ManagedZoneReference{ Name: managedZone.Name, }, - Endpoints: getTestEndpoints(), + Endpoints: getTestEndpoints("foo.example.com"), }, } }) @@ -90,6 +105,62 @@ var _ = Describe("DNSRecordReconciler", func() { err := k8sClient.Delete(ctx, managedZone) Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) } + if brokenZone != nil { + err := k8sClient.Delete(ctx, brokenZone) + Expect(client.IgnoreNotFound(err)).ToNot(HaveOccurred()) + } + }) + + It("dns records are reconciled once zone is fixed", func(ctx SpecContext) { + dnsRecord = &v1alpha1.DNSRecord{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo.fix.com", + Namespace: testNamespace, + }, + Spec: v1alpha1.DNSRecordSpec{ + OwnerID: "owner1", + RootHost: "foo.fix.com", + ManagedZoneRef: &v1alpha1.ManagedZoneReference{ + Name: brokenZone.Name, + }, + Endpoints: getTestEndpoints("foo.fix.com"), + HealthCheck: nil, + }, + } + Expect(k8sClient.Create(ctx, dnsRecord)).To(Succeed()) + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsRecord.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(v1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionFalse), + "Reason": Equal("ProviderError"), + "Message": ContainSubstring("The DNS provider failed to ensure the record"), + "ObservedGeneration": Equal(dnsRecord.Generation), + })), + ) + g.Expect(dnsRecord.Finalizers).To(ContainElement(DNSRecordFinalizer)) + }, TestTimeoutMedium, time.Second).Should(Succeed()) + + fixedZone := brokenZone.DeepCopy() + fixedZone.Spec.SecretRef.Name = dnsProviderSecret.Name + Expect(k8sClient.Update(ctx, fixedZone)).NotTo(HaveOccurred()) + + Eventually(func(g Gomega) { + err := k8sClient.Get(ctx, client.ObjectKeyFromObject(dnsRecord), dnsRecord) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(dnsRecord.Status.Conditions).To( + ContainElement(MatchFields(IgnoreExtras, Fields{ + "Type": Equal(string(v1alpha1.ConditionTypeReady)), + "Status": Equal(metav1.ConditionTrue), + "Reason": Equal("ProviderSuccess"), + "Message": Equal("Provider ensured the dns record"), + "ObservedGeneration": Equal(dnsRecord.Generation), + })), + ) + g.Expect(dnsRecord.Finalizers).To(ContainElement(DNSRecordFinalizer)) + }, TestTimeoutMedium, time.Second).Should(Succeed()) }) It("can delete a record with an invalid managed zone", func(ctx SpecContext) { @@ -104,7 +175,7 @@ var _ = Describe("DNSRecordReconciler", func() { ManagedZoneRef: &v1alpha1.ManagedZoneReference{ Name: "doesnotexist", }, - Endpoints: getTestEndpoints(), + Endpoints: getTestEndpoints("foo.example.com"), HealthCheck: nil, }, } diff --git a/internal/controller/helper_test.go b/internal/controller/helper_test.go index b2a2648d..41e2e1a2 100644 --- a/internal/controller/helper_test.go +++ b/internal/controller/helper_test.go @@ -47,10 +47,10 @@ func testBuildInMemoryCredentialsSecret(name, ns string) *v1.Secret { } } -func getTestEndpoints() []*externaldnsendpoint.Endpoint { +func getTestEndpoints(dnsName string) []*externaldnsendpoint.Endpoint { return []*externaldnsendpoint.Endpoint{ { - DNSName: "foo.example.com", + DNSName: dnsName, Targets: []string{ "127.0.0.1", },