diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 443e7ab5..ef88a203 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -188,6 +188,10 @@ func setupControllers(mgr ctrl.Manager, conf *config.Config, lgr logr.Logger, cl if err := keyvault.NewPlaceholderPodController(mgr, conf, ingressManager); err != nil { return fmt.Errorf("setting up placeholder pod controller: %w", err) } + lgr.Info("setting up ingress tls reconciler") + if err := keyvault.NewIngressTlsReconciler(mgr, conf, ingressManager); err != nil { + return fmt.Errorf("setting up ingress tls reconciler: %w", err) + } lgr.Info("setting up keyvault event mirror") if err = keyvault.NewEventMirror(mgr, conf); err != nil { return fmt.Errorf("setting up event mirror: %w", err) diff --git a/pkg/controller/keyvault/consts.go b/pkg/controller/keyvault/consts.go new file mode 100644 index 00000000..3c728d11 --- /dev/null +++ b/pkg/controller/keyvault/consts.go @@ -0,0 +1,6 @@ +package keyvault + +const ( + tlsCertKvUriAnnotation = "kubernetes.azure.com/tls-cert-keyvault-uri" + tlsCertManagedAnnotation = "kubernetes.azure.com/tls-cert-keyvault-managed" +) diff --git a/pkg/controller/keyvault/ingress_secret_provider_class.go b/pkg/controller/keyvault/ingress_secret_provider_class.go index 7952840e..7571694b 100644 --- a/pkg/controller/keyvault/ingress_secret_provider_class.go +++ b/pkg/controller/keyvault/ingress_secret_provider_class.go @@ -150,7 +150,7 @@ func (i *IngressSecretProviderClassReconciler) buildSPC(ing *netv1.Ingress, spc return false, nil } - certURI := ing.Annotations["kubernetes.azure.com/tls-cert-keyvault-uri"] + certURI := ing.Annotations[tlsCertKvUriAnnotation] if certURI == "" { return false, nil } @@ -185,7 +185,7 @@ func (i *IngressSecretProviderClassReconciler) buildSPC(ing *netv1.Ingress, spc spc.Spec = secv1.SecretProviderClassSpec{ Provider: secv1.Provider("azure"), SecretObjects: []*secv1.SecretObject{{ - SecretName: fmt.Sprintf("keyvault-%s", ing.Name), + SecretName: certSecretName(ing.Name), Type: "kubernetes.io/tls", Data: []*secv1.SecretObjectData{ { @@ -214,3 +214,7 @@ func (i *IngressSecretProviderClassReconciler) buildSPC(ing *netv1.Ingress, spc return true, nil } + +func certSecretName(ingressName string) string { + return fmt.Sprintf("keyvault-%s", ingressName) +} diff --git a/pkg/controller/keyvault/ingress_secret_provider_class_test.go b/pkg/controller/keyvault/ingress_secret_provider_class_test.go index 90ded194..b8d6c57b 100644 --- a/pkg/controller/keyvault/ingress_secret_provider_class_test.go +++ b/pkg/controller/keyvault/ingress_secret_provider_class_test.go @@ -469,4 +469,9 @@ func TestIngressSecretProviderClassReconcilerBuildSPCFailedIsManaging(t *testing require.False(t, ok) require.Error(t, err) require.ErrorContains(t, err, "determining if ingress is managed") -} \ No newline at end of file +} + +func TestCertSecretName(t *testing.T) { + require.Equal(t, "keyvault-ingressname", certSecretName("ingressname")) + require.Equal(t, "keyvault-anotheringressname", certSecretName("anotheringressname")) +} diff --git a/pkg/controller/keyvault/ingress_tls.go b/pkg/controller/keyvault/ingress_tls.go new file mode 100644 index 00000000..2d6c5d42 --- /dev/null +++ b/pkg/controller/keyvault/ingress_tls.go @@ -0,0 +1,113 @@ +package keyvault + +import ( + "context" + "errors" + "fmt" + "reflect" + "strings" + + "github.com/Azure/aks-app-routing-operator/pkg/config" + "github.com/Azure/aks-app-routing-operator/pkg/controller/controllername" + "github.com/Azure/aks-app-routing-operator/pkg/controller/metrics" + "github.com/Azure/aks-app-routing-operator/pkg/util" + "github.com/go-logr/logr" + netv1 "k8s.io/api/networking/v1" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var ingressTlsControllerName = controllername.New("keyvault", "ingress", "tls") + +// ingressTlsReconciler manages the TLS spec of an Ingress object using the Key Vault integration. Ingresses with +// the App Routing Key Vault annotation will have their TLS spec populated with the Key Vault secret name. +type ingressTlsReconciler struct { + client client.Client + events record.EventRecorder + ingressManager IngressManager +} + +func NewIngressTlsReconciler(manager ctrl.Manager, conf *config.Config, ingressManager IngressManager) error { + metrics.InitControllerMetrics(ingressTlsControllerName) + + if conf.DisableKeyvault { + return nil + } + + return ingressTlsControllerName.AddToController( + ctrl. + NewControllerManagedBy(manager). + For(&netv1.Ingress{}), manager.GetLogger(), + ).Complete(&ingressTlsReconciler{ + client: manager.GetClient(), + events: manager.GetEventRecorderFor("aks-app-routing-operator"), + ingressManager: ingressManager, + }) +} + +func (i *ingressTlsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res ctrl.Result, err error) { + defer func() { + metrics.HandleControllerReconcileMetrics(ingressTlsControllerName, res, err) + }() + + logger, err := logr.FromContext(ctx) + if err != nil { + return ctrl.Result{}, errors.New("getting logger from context") + } + logger = ingressTlsControllerName.AddToLogger(logger).WithValues("name", req.Name, "namespace", req.Namespace) + + logger.Info("getting Ingress") + ing := &netv1.Ingress{} + if err = i.client.Get(ctx, req.NamespacedName, ing); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + managed, err := i.ingressManager.IsManaging(ing) + if err != nil { + logger.Error(err, "error checking if ingress is managed") + return ctrl.Result{}, fmt.Errorf("checking if ingress is managed: %w", err) + } + + if !managed { + logger.Info("ingress is not managed by app routing") + return ctrl.Result{}, nil + } + + if val, ok := ing.Annotations[tlsCertManagedAnnotation]; !ok || strings.ToLower(val) != "true" { + logger.Info("ingress does not have managed annotation") + return ctrl.Result{}, nil + } + + if _, ok := ing.Annotations[tlsCertKvUriAnnotation]; !ok { + logger.Info("ingress does not have keyvault annotation") + i.events.Eventf(ing, "Warning", "KeyvaultUriAnnotationMissing", "Ingress has %[1]s annotation but is missing %[2]s annotation. %[2]s annotation is needed to manage Ingress TLS.", tlsCertManagedAnnotation, tlsCertKvUriAnnotation) + return ctrl.Result{}, nil + } + + oldTls := ing.Spec.TLS + logger.Info("adding TLS spec to ingress") + ing.Spec.TLS = []netv1.IngressTLS{ + { + SecretName: certSecretName(ing.Name), + Hosts: []string{}, + }, + } + + for _, rule := range ing.Spec.Rules { + if host := rule.Host; host != "" { + ing.Spec.TLS[0].Hosts = append(ing.Spec.TLS[0].Hosts, host) + } + } + + if !reflect.DeepEqual(oldTls, ing.Spec.TLS) { + logger.Info("overwriting TLS spec on ingress", "old", fmt.Sprintf("%s", oldTls), "new", fmt.Sprintf("%s", ing.Spec.TLS)) + } + + if err := util.Upsert(ctx, i.client, ing); err != nil { + logger.Error(err, "error updating ingress") + return ctrl.Result{}, fmt.Errorf("updating ingress: %w", err) + } + + return ctrl.Result{}, nil +} diff --git a/pkg/controller/keyvault/ingress_tls_test.go b/pkg/controller/keyvault/ingress_tls_test.go new file mode 100644 index 00000000..4b6d5172 --- /dev/null +++ b/pkg/controller/keyvault/ingress_tls_test.go @@ -0,0 +1,329 @@ +package keyvault + +import ( + "context" + "testing" + + "github.com/Azure/aks-app-routing-operator/pkg/controller/metrics" + "github.com/Azure/aks-app-routing-operator/pkg/controller/testutils" + "github.com/Azure/aks-app-routing-operator/pkg/util" + "github.com/go-logr/logr" + "github.com/stretchr/testify/require" + netv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestIngressTlsReconciler(t *testing.T) { + managedIngressClassName := "managed.ingress.class" + + c := fake.NewClientBuilder().Build() + recorder := record.NewFakeRecorder(10) + i := &ingressTlsReconciler{ + client: c, + ingressManager: NewIngressManagerFromFn(func(ing *netv1.Ingress) (bool, error) { + if *ing.Spec.IngressClassName == managedIngressClassName { + return true, nil + } + + return false, nil + }), + events: recorder, + } + + ctx := context.Background() + ctx = logr.NewContext(ctx, logr.Discard()) + + // prove it does nothing to an unmanaged ingress + t.Run("unmanaged ingress", func(t *testing.T) { + unmanagedIngress := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "unmanaged", + }, + Spec: netv1.IngressSpec{ + IngressClassName: util.ToPtr("unmanaged.ingress.class"), + Rules: []netv1.IngressRule{ + { + Host: "unmanaged.example.com", + }, + }, + }, + } + require.NoError(t, c.Create(ctx, unmanagedIngress), "there should be no error creating an unmanaged ingress") + req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: unmanagedIngress.Namespace, Name: unmanagedIngress.Name}} + beforeErrCount := testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount := testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err := i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling an unmanaged ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling an unmanaged ingress") + require.Equal(t, beforeRequestCount+1, testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess), "there should be one more successful reconcile count reconciling an unmanaged ingress") + + got := &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.Nil(t, got.Spec.TLS, "we shouldn't have changed an unmanaged ingress") + + unmanagedIngress.Annotations = map[string]string{ + tlsCertKvUriAnnotation: "https://mykv.vault.azure.net/secrets/mycert", + tlsCertManagedAnnotation: "true", + } + require.NoError(t, c.Update(ctx, unmanagedIngress), "there should be no error updating an unmanaged ingress") + beforeErrCount = testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount = testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err = i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling an unmanaged ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling an unmanaged ingress") + + got = &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.Nil(t, got.Spec.TLS, "we shouldn't have changed an unmanaged ingress") + }) + + // prove it does nothing to an ingress with no managed annotation + t.Run("managed ingress with no managed annotation", func(t *testing.T) { + managedIngressNoAnnotation := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "managed-no-annotation", + }, + Spec: netv1.IngressSpec{ + IngressClassName: &managedIngressClassName, + Rules: []netv1.IngressRule{ + { + Host: "managed.example.com", + }, + }, + }, + } + require.NoError(t, c.Create(ctx, managedIngressNoAnnotation), "there should be no error creating a managed ingress") + req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: managedIngressNoAnnotation.Namespace, Name: managedIngressNoAnnotation.Name}} + beforeErrCount := testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount := testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err = i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling a managed ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling a managed ingress") + require.Equal(t, beforeRequestCount+1, testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess), "there should be one more successful reconcile count reconciling a managed ingress") + + got := &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.Nil(t, got.Spec.TLS, "we shouldn't have changed a managed ingress with no annotation") + + managedIngressNoAnnotation.Annotations = map[string]string{ + tlsCertKvUriAnnotation: "https://mykv.vault.azure.net/secrets/mycert", + } + require.NoError(t, c.Update(ctx, managedIngressNoAnnotation), "there should be no error updating a managed ingress") + beforeErrCount = testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount = testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err = i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling a managed ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling a managed ingress") + + got = &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.Nil(t, got.Spec.TLS, "we shouldn't have changed a managed ingress with no managed annotation") + }) + + // prove it doesn't reconcile an ingress with only the managed annotation + t.Run("managed ingress with only managed annotation", func(t *testing.T) { + managedIngressOnlyManagedAnnotation := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "managed-only-managed-annotation", + Annotations: map[string]string{ + tlsCertManagedAnnotation: "true", + }, + }, + Spec: netv1.IngressSpec{ + IngressClassName: &managedIngressClassName, + Rules: []netv1.IngressRule{ + { + Host: "managed.example.com", + }, + }, + }, + } + require.NoError(t, c.Create(ctx, managedIngressOnlyManagedAnnotation), "there should be no error creating a managed ingress") + + req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: managedIngressOnlyManagedAnnotation.Namespace, Name: managedIngressOnlyManagedAnnotation.Name}} + beforeErrCount := testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount := testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err = i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling a managed ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling a managed ingress") + require.Equal(t, beforeRequestCount+1, testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess), "there should be one more successful reconcile count reconciling a managed ingress") + + got := &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.Nil(t, got.Spec.TLS, "we shouldn't have changed a managed ingress with only the managed annotation") + + // prove we sent a warning event + require.Equal(t, "Warning KeyvaultUriAnnotationMissing Ingress has kubernetes.azure.com/tls-cert-keyvault-managed annotation but is missing kubernetes.azure.com/tls-cert-keyvault-uri annotation. kubernetes.azure.com/tls-cert-keyvault-uri annotation is needed to manage Ingress TLS.", <-recorder.Events, "warning event should have been sent") + }) + + // prove it reconciles an ingress with an annotation but pre-existing TLS + t.Run("managed ingress with annotation and pre-existing TLS", func(t *testing.T) { + managedIngressDefinedTls := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "managed-defined-tls", + Annotations: map[string]string{ + tlsCertKvUriAnnotation: "https://mykv.vault.azure.net/secrets/mycert", + tlsCertManagedAnnotation: "true", + }, + }, + Spec: netv1.IngressSpec{ + IngressClassName: &managedIngressClassName, + TLS: []netv1.IngressTLS{ + { + SecretName: "custom-tls-secret", + Hosts: []string{"custom.host"}, + }, + }, + Rules: []netv1.IngressRule{ + { + Host: "managed.example.com", + }, + { + Host: "managed.example.com2", + }, + }, + }, + } + require.NoError(t, c.Create(ctx, managedIngressDefinedTls), "there should be no error creating a managed ingress") + req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: managedIngressDefinedTls.Namespace, Name: managedIngressDefinedTls.Name}} + beforeErrCount := testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount := testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err = i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling a managed ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling a managed ingress") + require.Equal(t, beforeRequestCount+1, testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess), "there should be one more successful reconcile count reconciling a managed ingress") + + got := &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.Len(t, got.Spec.TLS, 1, "we should have replaced the TLS entry") + require.Equal(t, certSecretName(managedIngressDefinedTls.Name), got.Spec.TLS[0].SecretName, "we should have changed the preexisting TLS secret name") + require.Equal(t, []string{"managed.example.com", "managed.example.com2"}, got.Spec.TLS[0].Hosts, "we should have changed the managed hosts to the TLS entry") + }) + + // prove it properly reconciles single host + t.Run("managed ingress with single host", func(t *testing.T) { + managedIngress := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "managed-single-host", + Annotations: map[string]string{ + tlsCertKvUriAnnotation: "https://mykv.vault.azure.net/secrets/mycert", + tlsCertManagedAnnotation: "true", + }, + }, + Spec: netv1.IngressSpec{ + IngressClassName: &managedIngressClassName, + Rules: []netv1.IngressRule{ + { + Host: "managed.example.com", + }, + }, + }, + } + require.NoError(t, c.Create(ctx, managedIngress), "there should be no error creating a managed ingress") + req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: managedIngress.Namespace, Name: managedIngress.Name}} + beforeErrCount := testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount := testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err = i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling a managed ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling a managed ingress") + require.Equal(t, beforeRequestCount+1, testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess), "there should be one more successful reconcile count reconciling a managed ingress") + + got := &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.NotNil(t, got.Spec.TLS, "we should have added TLS to a managed ingress") + require.Equal(t, 1, len(got.Spec.TLS), "we should have added TLS to a managed ingress") + require.Equal(t, "managed.example.com", got.Spec.TLS[0].Hosts[0], "we should have added TLS to a managed ingress") + require.Equal(t, certSecretName(managedIngress.Name), got.Spec.TLS[0].SecretName, "we should have added TLS to a managed ingress") + }) + + // prove it properly reconciles multiple hosts + t.Run("managed ingress with multiple hosts", func(t *testing.T) { + managedIngressMultipleHosts := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "managed-multiple-hosts", + Annotations: map[string]string{ + tlsCertKvUriAnnotation: "https://mykv.vault.azure.net/secrets/mycert", + tlsCertManagedAnnotation: "true", + }, + }, + Spec: netv1.IngressSpec{ + IngressClassName: &managedIngressClassName, + Rules: []netv1.IngressRule{ + { + Host: "managed.example.com", + }, + { + Host: "managed2.example.com", + }, + { + Host: "managed3.example.com", + }, + }, + }, + } + require.NoError(t, c.Create(ctx, managedIngressMultipleHosts), "there should be no error creating a managed ingress") + req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: managedIngressMultipleHosts.Namespace, Name: managedIngressMultipleHosts.Name}} + beforeErrCount := testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount := testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err = i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling a managed ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling a managed ingress") + require.Equal(t, beforeRequestCount+1, testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess), "there should be one more successful reconcile count reconciling a managed ingress") + + got := &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.NotNil(t, got.Spec.TLS, "we should have added TLS to a managed ingress") + require.Equal(t, 1, len(got.Spec.TLS), "we should have added TLS to a managed ingress") + require.Equal(t, 3, len(got.Spec.TLS[0].Hosts), "we should have added TLS to a managed ingress") + require.Equal(t, "managed.example.com", got.Spec.TLS[0].Hosts[0], "we should have added TLS to a managed ingress") + require.Equal(t, "managed2.example.com", got.Spec.TLS[0].Hosts[1], "we should have added TLS to a managed ingress") + require.Equal(t, "managed3.example.com", got.Spec.TLS[0].Hosts[2], "we should have added TLS to a managed ingress") + require.Equal(t, certSecretName(managedIngressMultipleHosts.Name), got.Spec.TLS[0].SecretName, "we should have added TLS to a managed ingress") + }) + + // prove it properly reconciles multiple hosts + t.Run("managed ingress with some hosts", func(t *testing.T) { + managedIngressSomeHosts := &netv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{ + Name: "managed-some-hosts", + Annotations: map[string]string{ + tlsCertKvUriAnnotation: "https://mykv.vault.azure.net/secrets/mycert", + tlsCertManagedAnnotation: "true", + }, + }, + Spec: netv1.IngressSpec{ + IngressClassName: &managedIngressClassName, + Rules: []netv1.IngressRule{ + { + Host: "managed.example.com", + }, + {}, // empty host, shouldn't do anything + { + Host: "managed3.example.com", + }, + }, + }, + } + require.NoError(t, c.Create(ctx, managedIngressSomeHosts), "there should be no error creating a managed ingress") + req := ctrl.Request{NamespacedName: types.NamespacedName{Namespace: managedIngressSomeHosts.Namespace, Name: managedIngressSomeHosts.Name}} + beforeErrCount := testutils.GetErrMetricCount(t, ingressTlsControllerName) + beforeRequestCount := testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess) + _, err = i.Reconcile(ctx, req) + require.NoError(t, err, "there should be no error reconciling a managed ingress") + require.Equal(t, beforeErrCount, testutils.GetErrMetricCount(t, ingressTlsControllerName), "there should be no change in the error count reconciling a managed ingress") + require.Equal(t, beforeRequestCount+1, testutils.GetReconcileMetricCount(t, ingressTlsControllerName, metrics.LabelSuccess), "there should be one more successful reconcile count reconciling a managed ingress") + + got := &netv1.Ingress{} + require.NoError(t, c.Get(ctx, req.NamespacedName, got)) + require.NotNil(t, got.Spec.TLS, "we should have added TLS to a managed ingress") + require.Equal(t, 1, len(got.Spec.TLS), "we should have added TLS to a managed ingress") + require.Equal(t, 2, len(got.Spec.TLS[0].Hosts), "we should have added TLS to a managed ingress") + require.Equal(t, "managed.example.com", got.Spec.TLS[0].Hosts[0], "we should have added TLS to a managed ingress") + require.Equal(t, "managed3.example.com", got.Spec.TLS[0].Hosts[1], "we should have added TLS to a managed ingress") + require.Equal(t, certSecretName(managedIngressSomeHosts.Name), got.Spec.TLS[0].SecretName, "we should have added TLS to a managed ingress") + }) +}