diff --git a/pkg/reconciler/route/domains/domains.go b/pkg/reconciler/route/domains/domains.go index 50cce3e561f6..6bd9ac077823 100644 --- a/pkg/reconciler/route/domains/domains.go +++ b/pkg/reconciler/route/domains/domains.go @@ -45,6 +45,11 @@ const HTTPScheme string = "http" var ErrDomainName = errors.New("domain name error") +type Domains struct { + Primary string + Expanded sets.Set[string] +} + // GetAllDomainsAndTags returns all of the domains and tags(including subdomains) associated with a Route func GetAllDomainsAndTags(ctx context.Context, r *v1.Route, names []string, visibility map[string]netv1alpha1.IngressVisibility) (map[string]string, error) { domainTagMap := make(map[string]string) @@ -69,10 +74,11 @@ func GetAllDomainsAndTags(ctx context.Context, r *v1.Route, names []string, visi } // GetDomainsForVisibility return all domains for the specified visibility. -func GetDomainsForVisibility(ctx context.Context, targetName string, r *v1.Route, visibility netv1alpha1.IngressVisibility) (sets.Set[string], error) { +func GetDomainsForVisibility(ctx context.Context, targetName string, r *v1.Route, visibility netv1alpha1.IngressVisibility) (Domains, error) { + domains := Domains{} hostname, err := HostnameFromTemplate(ctx, r.Name, targetName) if err != nil { - return nil, err + return domains, err } meta := r.ObjectMeta.DeepCopy() @@ -81,13 +87,15 @@ func GetDomainsForVisibility(ctx context.Context, targetName string, r *v1.Route domain, err := DomainNameFromTemplate(ctx, *meta, hostname) if err != nil { - return nil, err + return domains, err } - domains := []string{domain} + + domains.Primary = domain + domains.Expanded = sets.New(domain) if isClusterLocal { - domains = sets.List(ingress.ExpandedHosts(sets.New(domains...))) + domains.Expanded = ingress.ExpandedHosts(domains.Expanded) } - return sets.New(domains...), err + return domains, err } // DomainNameFromTemplate generates domain name base on the template specified in the `config-network` ConfigMap. diff --git a/pkg/reconciler/route/domains/domains_test.go b/pkg/reconciler/route/domains/domains_test.go index eac174d9637b..3fd3125a3f0d 100644 --- a/pkg/reconciler/route/domains/domains_test.go +++ b/pkg/reconciler/route/domains/domains_test.go @@ -281,6 +281,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility v1alpha1.IngressVisibility domainTemplate string tagTemplate string + primary string want sets.Set[string] }{ { @@ -289,6 +290,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility: v1alpha1.IngressVisibilityClusterLocal, domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}", tagTemplate: "{{.Name}}-{{.Tag}}", + primary: "myroute.default.svc.cluster.local", want: sets.New( "myroute.default", "myroute.default.svc", @@ -300,6 +302,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility: v1alpha1.IngressVisibilityExternalIP, domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}", tagTemplate: "{{.Name}}-{{.Tag}}", + primary: "myroute.default.example.com", want: sets.New( "myroute.default.example.com", ), @@ -309,6 +312,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility: v1alpha1.IngressVisibilityClusterLocal, domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}", tagTemplate: "{{.Name}}-{{.Tag}}", + primary: "myroute-test.default.svc.cluster.local", want: sets.New( "myroute-test.default", "myroute-test.default.svc", @@ -320,6 +324,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility: v1alpha1.IngressVisibilityExternalIP, domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}", tagTemplate: "{{.Name}}-{{.Tag}}", + primary: "myroute-test.default.example.com", want: sets.New( "myroute-test.default.example.com", ), @@ -329,6 +334,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility: v1alpha1.IngressVisibilityClusterLocal, domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}", tagTemplate: "{{.Tag}}-{{.Name}}", + primary: "myroute.default.svc.cluster.local", want: sets.New( "myroute.default", "myroute.default.svc", @@ -340,6 +346,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility: v1alpha1.IngressVisibilityExternalIP, domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}", tagTemplate: "{{.Tag}}-{{.Name}}", + primary: "myroute.default.example.com", want: sets.New( "myroute.default.example.com", ), @@ -349,6 +356,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility: v1alpha1.IngressVisibilityClusterLocal, domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}", tagTemplate: "{{.Tag}}-{{.Name}}", + primary: "test-myroute.default.svc.cluster.local", want: sets.New( "test-myroute.default", "test-myroute.default.svc", @@ -360,6 +368,7 @@ func TestGetDomainsForVisibility(t *testing.T) { visibility: v1alpha1.IngressVisibilityExternalIP, domainTemplate: "{{.Name}}.{{.Namespace}}.{{.Domain}}", tagTemplate: "{{.Tag}}-{{.Name}}", + primary: "test-myroute.default.example.com", want: sets.New( "test-myroute.default.example.com", ), @@ -384,11 +393,16 @@ func TestGetDomainsForVisibility(t *testing.T) { cfg.Network.TagTemplate = tt.tagTemplate ctx = config.ToContext(ctx, cfg) + want := Domains{ + Primary: tt.primary, + Expanded: tt.want, + } + got, err := GetDomainsForVisibility(ctx, tt.tag, route, tt.visibility) if err != nil { t.Errorf("failed calling GetDomainsForVisibility: %v", err) } - if diff := cmp.Diff(tt.want, got); diff != "" { + if diff := cmp.Diff(want, got); diff != "" { t.Error("GetDomainsForVisibility() diff (-want +got):", diff) } }) diff --git a/pkg/reconciler/route/resources/ingress.go b/pkg/reconciler/route/resources/ingress.go index ba593ebe421d..5a1ff1ab7c1f 100644 --- a/pkg/reconciler/route/resources/ingress.go +++ b/pkg/reconciler/route/resources/ingress.go @@ -79,11 +79,12 @@ func MakeIngressWithRollout( ingressClass string, acmeChallenges ...netv1alpha1.HTTP01Challenge, ) (*netv1alpha1.Ingress, error) { - spec, err := makeIngressSpec(ctx, r, tls, tc, ro, acmeChallenges...) + spec, tagToHost, err := makeIngressSpec(ctx, r, tls, tc, ro, acmeChallenges...) if err != nil { return nil, err } - return &netv1alpha1.Ingress{ + + ing := &netv1alpha1.Ingress{ ObjectMeta: metav1.ObjectMeta{ Name: names.Ingress(r), Namespace: r.Namespace, @@ -98,7 +99,24 @@ func MakeIngressWithRollout( OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(r)}, }, Spec: spec, - }, nil + } + + if len(tagToHost) > 0 { + ing.Annotations[networking.TagToHostAnnotationKey] = serializeTagToHostMap(ctx, tagToHost) + } + + return ing, nil +} + +func serializeTagToHostMap(ctx context.Context, mapping map[string][]string) string { + sr, err := json.Marshal(mapping) + if err != nil { + // This must never happen in the normal course of things. + logging.FromContext(ctx).Warnw("Error serializing tag to host mapping: "+spew.Sprint(mapping), + zap.Error(err)) + return "" + } + return string(sr) } func serializeRollout(ctx context.Context, r *traffic.Rollout) string { @@ -120,7 +138,9 @@ func makeIngressSpec( tc *traffic.Config, ro *traffic.Rollout, acmeChallenges ...netv1alpha1.HTTP01Challenge, -) (netv1alpha1.IngressSpec, error) { +) (netv1alpha1.IngressSpec, map[string][]string, error) { + tagToHost := make(map[string][]string) + // Domain should have been specified in route status // before calling this func. names := make([]string, 0, len(tc.Targets)) @@ -152,9 +172,9 @@ func makeIngressSpec( for _, visibility := range visibilities { domains, err := domains.GetDomainsForVisibility(ctx, name, r, visibility) if err != nil { - return netv1alpha1.IngressSpec{}, err + return netv1alpha1.IngressSpec{}, nil, err } - domainRules := makeIngressRules(domains, r.Namespace, + domainRules := makeIngressRules(domains.Expanded, r.Namespace, visibility, tc.Targets[name], ro.RolloutsByTag(name), networkConfig.SystemInternalTLSEnabled()) // Apply tag header routing and ACME merging to each rule @@ -205,19 +225,25 @@ func makeIngressSpec( } rules = append(rules, domainRules...) + + if name != traffic.DefaultTarget { + tagToHost[name] = append(tagToHost[name], domains.Primary) + } } } httpOption, err := servingnetworking.GetHTTPOption(ctx, config.FromContext(ctx).Network, r.GetAnnotations()) if err != nil { - return netv1alpha1.IngressSpec{}, err + return netv1alpha1.IngressSpec{}, nil, err } - return netv1alpha1.IngressSpec{ + spec := netv1alpha1.IngressSpec{ Rules: rules, TLS: tls, HTTPOption: httpOption, - }, nil + } + + return spec, tagToHost, nil } // MakeACMEIngressPath converts an ACME challenge into an HTTPIngressPath. diff --git a/pkg/reconciler/route/resources/ingress_test.go b/pkg/reconciler/route/resources/ingress_test.go index 2bfd12db0ff8..195b9e93e0b5 100644 --- a/pkg/reconciler/route/resources/ingress_test.go +++ b/pkg/reconciler/route/resources/ingress_test.go @@ -141,6 +141,7 @@ func TestMakeIngressWithTaggedRollout(t *testing.T) { networking.IngressClassAnnotationKey: ingressClass, networking.RolloutAnnotationKey: `{"configurations":[{"configurationName":"valhalla","percent":100,"revisions":[{"revisionName":"valhalla-01982","percent":100}],"stepParams":{}},{"configurationName":"thor","tag":"tagged","percent":100,"revisions":[{"revisionName":"thor-02020","percent":100}],"stepParams":{}}]}`, "test-annotation": "bar", + networking.TagToHostAnnotationKey: `{"tagged":["tagged-test-route.test-ns.svc.cluster.local","tagged-test-route.test-ns.example.com"]}`, }, OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(r)}, } @@ -245,6 +246,7 @@ func TestMakeIngressWithActualRollout(t *testing.T) { Annotations: map[string]string{ networking.IngressClassAnnotationKey: ingressClass, networking.RolloutAnnotationKey: serializeRollout(context.Background(), ro), + networking.TagToHostAnnotationKey: `{"hammer":["hammer-test-route.test-ns.svc.cluster.local","hammer-test-route.test-ns.example.com"]}`, }, OwnerReferences: []metav1.OwnerReference{*kmeta.NewControllerRef(r)}, } @@ -587,7 +589,7 @@ func TestMakeIngressSpecCorrectRules(t *testing.T) { tc := &traffic.Config{Targets: targets} ro := tc.BuildRollout() - ci, err := makeIngressSpec(testContext(), r, nil /*tls*/, tc, ro) + ci, _, err := makeIngressSpec(testContext(), r, nil /*tls*/, tc, ro) if err != nil { t.Error("Unexpected error", err) } @@ -662,7 +664,7 @@ func TestMakeIngressSpecCorrectRuleVisibility(t *testing.T) { Visibility: c.serviceVisibility, } ro := tc.BuildRollout() - ci, err := makeIngressSpec(testContext(), c.route, nil /*tls*/, tc, ro) + ci, _, err := makeIngressSpec(testContext(), c.route, nil /*tls*/, tc, ro) if err != nil { t.Error("Unexpected error", err) } @@ -842,7 +844,7 @@ func TestMakeIngressSpecCorrectRulesWithTagBasedRouting(t *testing.T) { tc := &traffic.Config{Targets: targets} ro := tc.BuildRollout() - ci, err := makeIngressSpec(ctx, r, nil /*tls*/, tc, ro) + ci, _, err := makeIngressSpec(ctx, r, nil /*tls*/, tc, ro) if err != nil { t.Error("Unexpected error", err) } @@ -1221,7 +1223,7 @@ func TestMakeIngressWithActivatorCA(t *testing.T) { tc := &traffic.Config{Targets: targets} ro := tc.BuildRollout() - ci, err := makeIngressSpec(testContextWithActivatorCA(), r, nil /*tls*/, tc, ro) + ci, _, err := makeIngressSpec(testContextWithActivatorCA(), r, nil /*tls*/, tc, ro) if err != nil { t.Error("Unexpected error", err) } @@ -1352,7 +1354,7 @@ func TestMakeIngressACMEChallenges(t *testing.T) { } ro := tc.BuildRollout() - ci, err := makeIngressSpec(testContext(), r, nil /*tls*/, tc, ro, acmeChallenge) + ci, _, err := makeIngressSpec(testContext(), r, nil /*tls*/, tc, ro, acmeChallenge) if err != nil { t.Error("Unexpected error", err) } @@ -1442,7 +1444,7 @@ func TestMakeIngressACMEChallengesWithTrafficTags(t *testing.T) { } ro := tc.BuildRollout() - ci, err := makeIngressSpec(testContext(), r, nil /*tls*/, tc, ro, acmeChallenges...) + ci, _, err := makeIngressSpec(testContext(), r, nil /*tls*/, tc, ro, acmeChallenges...) if err != nil { t.Fatal("Unexpected error", err) } diff --git a/pkg/reconciler/route/route.go b/pkg/reconciler/route/route.go index d6be181c957e..c5293efb98b7 100644 --- a/pkg/reconciler/route/route.go +++ b/pkg/reconciler/route/route.go @@ -326,7 +326,7 @@ func (c *Reconciler) clusterLocalDomainTLS(ctx context.Context, r *v1.Route, tc return nil, err } - desiredCert := resources.MakeClusterLocalCertificate(r, name, localDomains, certClass(ctx, r)) + desiredCert := resources.MakeClusterLocalCertificate(r, name, localDomains.Expanded, certClass(ctx, r)) cert, err := networkaccessor.ReconcileCertificate(ctx, r, desiredCert, c) if err != nil { if kaccessor.IsNotOwned(err) { @@ -342,19 +342,19 @@ func (c *Reconciler) clusterLocalDomainTLS(ctx context.Context, r *v1.Route, tc // r.Status.URL contains the major domain, // so only change if the cert is for the major domain - if localDomains.Has(r.Status.URL.Host) { + if localDomains.Expanded.Has(r.Status.URL.Host) { r.Status.URL.Scheme = "https" } r.Status.MarkCertificateReady(cert.Name) - tls = append(tls, resources.MakeIngressTLS(cert, sets.List(localDomains))) + tls = append(tls, resources.MakeIngressTLS(cert, sets.List(localDomains.Expanded))) } else if cert.IsFailed() { r.Status.MarkCertificateProvisionFailed(cert) } else { r.Status.MarkCertificateNotReady(cert) } - for s := range localDomains { + for s := range localDomains.Expanded { usedDomains[s] = s } }