diff --git a/internal/controller/gateway_controller.go b/internal/controller/gateway_controller.go index c95c033d..1afe2956 100644 --- a/internal/controller/gateway_controller.go +++ b/internal/controller/gateway_controller.go @@ -329,16 +329,6 @@ func (r *GatewayReconciler) ensureDownstreamGateway( listenerCertHealth, ) - // A listener the user asked for that never reaches the downstream gateway - // makes the gateway dishonest about being programmed, whatever held it back. - // Derived from the listener set actually built, so a new reason to withhold - // one is covered without being enumerated here. - droppedListeners := summarizeDroppedListeners( - upstreamGateway, - desiredDownstreamGateway, - listenerCertHealth, - ) - if err := writeDownstreamGateway(ctx, downstreamStrategy, upstreamGateway, downstreamGateway, desiredDownstreamGateway.Spec, awaitsHostnameClaim(hostnameRefusals)); err != nil { result.Err = err @@ -388,6 +378,17 @@ func (r *GatewayReconciler) ensureDownstreamGateway( clearCertificateServiceFailing(upstreamClusterName, upstreamGateway) } + // A listener the user asked for that never reaches the downstream gateway + // makes the gateway dishonest about being programmed, whatever held it back. + // Derived from the listener set actually built, so a new reason to withhold + // one is covered without being enumerated here. Summarized after the + // certificate service so an entitlement block is known. + droppedListeners := summarizeDroppedListeners( + upstreamGateway, + desiredDownstreamGateway, + listenerCertHealth, + ) + certResult := r.ensureListenerCertificates( ctx, upstreamGateway, @@ -955,8 +956,8 @@ func (r *GatewayReconciler) getDesiredDownstreamGateway( } // listenerDropReport names the spec listeners that never reached the downstream -// gateway, and records whether an unusable certificate held back every one of -// them. +// gateway, and records whether a certificate still on its way held back every +// one of them. type listenerDropReport struct { names []gatewayv1.SectionName allCertWithheld bool @@ -1030,7 +1031,8 @@ func summarizeDroppedListeners( report.names = append(report.names, l.Name) - if status, gated := listenerCertHealth[l.Name]; !gated || status.healthy { + if status, gated := listenerCertHealth[l.Name]; !gated || status.healthy || + status.issuanceReason == certificateServiceReasonWildcardNotEntitled { report.allCertWithheld = false } } diff --git a/internal/controller/gateway_controller_test.go b/internal/controller/gateway_controller_test.go index f1e5913b..fc9fc59c 100644 --- a/internal/controller/gateway_controller_test.go +++ b/internal/controller/gateway_controller_test.go @@ -2161,6 +2161,21 @@ func TestReconcileGatewayStatus_DroppedListenerIsNotProgrammed(t *testing.T) { expectStatus: metav1.ConditionFalse, expectReason: string(gatewayv1.GatewayReasonPending), }, + { + name: "listener withheld by a wildcard the project is not entitled to", + listeners: []gatewayv1.Listener{customListener("custom-https", "*.claimed.example.com")}, + claimedHostnames: []string{"*.claimed.example.com"}, + certHealth: map[gatewayv1.SectionName]listenerCertStatus{ + "custom-https": { + reason: gatewayv1.ListenerReasonInvalidCertificateRef, + message: "wildcard hostnames are not enabled", + issuanceBlocked: true, + issuanceReason: certificateServiceReasonWildcardNotEntitled, + }, + }, + expectStatus: metav1.ConditionFalse, + expectReason: string(gatewayv1.GatewayReasonListenersNotValid), + }, { name: "certificate and hostname drops report the hostname", listeners: []gatewayv1.Listener{ diff --git a/internal/controller/wildcard_entitlement_test.go b/internal/controller/wildcard_entitlement_test.go index 771c6133..d8d887ca 100644 --- a/internal/controller/wildcard_entitlement_test.go +++ b/internal/controller/wildcard_entitlement_test.go @@ -492,3 +492,26 @@ func seriesMatching(t *testing.T, vec prometheus.Collector, want prometheus.Labe } return count } + +func TestWildcardNotEntitledIsNotReportedPending(t *testing.T) { + f := newEntitlementFixture(t, "*.shop.example.com", false, fakeWildcardEntitlements{entitled: false}) + + _, created, _ := f.h.reconcile() + require.NotNil(t, created, "downstream gateway") + downstream := &gatewayv1.Gateway{} + require.NoError(t, f.h.downstream.Get(f.h.ctx, client.ObjectKeyFromObject(created), downstream)) + apimeta.SetStatusCondition(&downstream.Status.Conditions, metav1.Condition{ + Type: string(gatewayv1.GatewayConditionProgrammed), + Status: metav1.ConditionTrue, + Reason: string(gatewayv1.GatewayReasonProgrammed), + }) + require.NoError(t, f.h.downstream.Status().Update(f.h.ctx, downstream)) + + upstream, _, _ := f.h.reconcile() + + programmed := apimeta.FindStatusCondition(upstream.Status.Conditions, string(gatewayv1.GatewayConditionProgrammed)) + require.NotNil(t, programmed, "upstream Programmed condition") + assert.Equal(t, metav1.ConditionFalse, programmed.Status) + assert.Equal(t, string(gatewayv1.GatewayReasonListenersNotValid), programmed.Reason, + "a wildcard the project is not entitled to never gets a certificate, so it is not Pending") +}