@@ -7,13 +7,15 @@ package resource
77
88import (
99 "context"
10+ "maps"
1011 "reflect"
1112 "sort"
1213
1314 certificatesv1b1 "k8s.io/api/certificates/v1beta1"
1415 corev1 "k8s.io/api/core/v1"
1516 discoveryv1 "k8s.io/api/discovery/v1"
1617 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
18+ "k8s.io/apimachinery/pkg/runtime"
1719 gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
1820 gwapiv1b1 "sigs.k8s.io/gateway-api/apis/v1beta1"
1921 mcsapiv1a1 "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1"
@@ -502,3 +504,83 @@ func (r *Resources) Sort() {
502504 return tsI .Before (& tsJ )
503505 })
504506}
507+
508+ // StatusDeepCopy returns a shallow copy of Resources in which every status-bearing
509+ // object is itself shallow-copied with only its Status field deep-copied.
510+ //
511+ // The translator mutates resource Status in place while building status updates,
512+ // but the input tree is shared with the watchable coalesce goroutine, which walks it
513+ // with reflect.DeepEqual. Deep-copying only the Status fields isolates those in-place
514+ // mutations from the shared objects without the memory cost of a full DeepCopy of the
515+ // (much larger, immutable-during-translation) Spec.
516+ func (r * Resources ) StatusDeepCopy () * Resources {
517+ if r == nil {
518+ return nil
519+ }
520+
521+ // Shallow copy of the struct: all fields (including the shared, non-status-bearing
522+ // slices such as Namespaces, Services, Secrets, ...) are carried over as-is.
523+ out := * r
524+
525+ out .GatewayClass = statusDeepCopyObject (r .GatewayClass , func (dst , src * gwapiv1.GatewayClass ) { src .Status .DeepCopyInto (& dst .Status ) })
526+ out .EnvoyProxyForGatewayClass = statusDeepCopyObject (r .EnvoyProxyForGatewayClass , func (dst , src * egv1a1.EnvoyProxy ) { src .Status .DeepCopyInto (& dst .Status ) })
527+ out .EnvoyProxiesForGateways = statusDeepCopySlice (r .EnvoyProxiesForGateways , func (dst , src * egv1a1.EnvoyProxy ) { src .Status .DeepCopyInto (& dst .Status ) })
528+ out .Gateways = statusDeepCopySlice (r .Gateways , func (dst , src * gwapiv1.Gateway ) { src .Status .DeepCopyInto (& dst .Status ) })
529+ out .ListenerSets = statusDeepCopySlice (r .ListenerSets , func (dst , src * gwapiv1.ListenerSet ) { src .Status .DeepCopyInto (& dst .Status ) })
530+ out .HTTPRoutes = statusDeepCopySlice (r .HTTPRoutes , func (dst , src * gwapiv1.HTTPRoute ) { src .Status .DeepCopyInto (& dst .Status ) })
531+ out .GRPCRoutes = statusDeepCopySlice (r .GRPCRoutes , func (dst , src * gwapiv1.GRPCRoute ) { src .Status .DeepCopyInto (& dst .Status ) })
532+ out .TLSRoutes = statusDeepCopySlice (r .TLSRoutes , func (dst , src * gwapiv1.TLSRoute ) { src .Status .DeepCopyInto (& dst .Status ) })
533+ out .TCPRoutes = statusDeepCopySlice (r .TCPRoutes , func (dst , src * gwapiv1.TCPRoute ) { src .Status .DeepCopyInto (& dst .Status ) })
534+ out .UDPRoutes = statusDeepCopySlice (r .UDPRoutes , func (dst , src * gwapiv1.UDPRoute ) { src .Status .DeepCopyInto (& dst .Status ) })
535+ out .EnvoyPatchPolicies = statusDeepCopySlice (r .EnvoyPatchPolicies , func (dst , src * egv1a1.EnvoyPatchPolicy ) { src .Status .DeepCopyInto (& dst .Status ) })
536+ out .ClientTrafficPolicies = statusDeepCopySlice (r .ClientTrafficPolicies , func (dst , src * egv1a1.ClientTrafficPolicy ) { src .Status .DeepCopyInto (& dst .Status ) })
537+ out .BackendTrafficPolicies = statusDeepCopySlice (r .BackendTrafficPolicies , func (dst , src * egv1a1.BackendTrafficPolicy ) { src .Status .DeepCopyInto (& dst .Status ) })
538+ out .SecurityPolicies = statusDeepCopySlice (r .SecurityPolicies , func (dst , src * egv1a1.SecurityPolicy ) { src .Status .DeepCopyInto (& dst .Status ) })
539+ out .BackendTLSPolicies = statusDeepCopySlice (r .BackendTLSPolicies , func (dst , src * gwapiv1.BackendTLSPolicy ) { src .Status .DeepCopyInto (& dst .Status ) })
540+ out .EnvoyExtensionPolicies = statusDeepCopySlice (r .EnvoyExtensionPolicies , func (dst , src * egv1a1.EnvoyExtensionPolicy ) { src .Status .DeepCopyInto (& dst .Status ) })
541+ out .Backends = statusDeepCopySlice (r .Backends , func (dst , src * egv1a1.Backend ) { src .Status .DeepCopyInto (& dst .Status ) })
542+ out .ExtensionServerPolicies = statusDeepCopyUnstructured (r .ExtensionServerPolicies )
543+
544+ return & out
545+ }
546+
547+ // statusDeepCopyObject returns a shallow copy of obj with copyStatus applied to
548+ // deep-copy its Status field, or nil if obj is nil.
549+ func statusDeepCopyObject [T any ](obj * T , copyStatus func (dst , src * T )) * T {
550+ if obj == nil {
551+ return nil
552+ }
553+ cp := * obj
554+ copyStatus (& cp , obj )
555+ return & cp
556+ }
557+
558+ // statusDeepCopySlice returns a new slice of shallow copies, each with copyStatus
559+ // applied to deep-copy its Status field, preserving the original length and nil-ness.
560+ func statusDeepCopySlice [T any ](in []* T , copyStatus func (dst , src * T )) []* T {
561+ if in == nil {
562+ return nil
563+ }
564+ out := make ([]* T , len (in ))
565+ for i , obj := range in {
566+ out [i ] = statusDeepCopyObject (obj , copyStatus )
567+ }
568+ return out
569+ }
570+
571+ // statusDeepCopyUnstructured returns shallow copies with deep-copied status entries
572+ // for unstructured resources (ExtensionServerPolicies).
573+ func statusDeepCopyUnstructured (in []unstructured.Unstructured ) []unstructured.Unstructured {
574+ if in == nil {
575+ return nil
576+ }
577+ out := make ([]unstructured.Unstructured , len (in ))
578+ for i , p := range in {
579+ p .Object = maps .Clone (p .Object ) // shallow copy map - no shared ref for "status" key
580+ if statusObj , ok := in [i ].Object ["status" ].(map [string ]any ); ok {
581+ p .Object ["status" ] = runtime .DeepCopyJSON (statusObj )
582+ }
583+ out [i ] = p
584+ }
585+ return out
586+ }
0 commit comments