@@ -5,19 +5,24 @@ package dynamichelper
55
66import (
77 "context"
8+ "encoding/json"
9+ "fmt"
810 "strings"
911
1012 "github.com/sirupsen/logrus"
1113
1214 kerrors "k8s.io/apimachinery/pkg/api/errors"
1315 "k8s.io/apimachinery/pkg/api/meta"
16+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1417 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1518 kruntime "k8s.io/apimachinery/pkg/runtime"
1619 "k8s.io/apimachinery/pkg/runtime/schema"
20+ "k8s.io/apimachinery/pkg/types"
1721 "k8s.io/client-go/dynamic"
1822 "k8s.io/client-go/kubernetes/scheme"
1923 "k8s.io/client-go/rest"
2024 "k8s.io/client-go/util/retry"
25+ "k8s.io/utils/pointer"
2126
2227 "sigs.k8s.io/controller-runtime/pkg/client"
2328
@@ -97,9 +102,19 @@ func (dh *dynamicHelper) EnsureDeletedGVR(ctx context.Context, groupKind, namesp
97102func (dh * dynamicHelper ) Ensure (ctx context.Context , objs ... kruntime.Object ) error {
98103 for _ , o := range objs {
99104 if un , ok := o .(* unstructured.Unstructured ); ok {
100- err := dh .ensureUnstructuredObj (ctx , un )
101- if err != nil {
102- return err
105+ // ValidatingAdmissionPolicy and ValidatingAdmissionPolicyBinding
106+ // are handled via server-side apply so that all fields are
107+ // correctly reconciled. The Gatekeeper-specific path only
108+ // compares enforcementAction and would silently skip updates
109+ // to other resource types.
110+ if isAdmissionRegistrationResource (un ) {
111+ if err := dh .ensureByServerSideApply (ctx , un ); err != nil {
112+ return err
113+ }
114+ } else {
115+ if err := dh .ensureGatekeeperConstraint (ctx , un ); err != nil {
116+ return err
117+ }
103118 }
104119 continue
105120 }
@@ -160,6 +175,40 @@ func (dh *dynamicHelper) mergeWithLogic(name, groupKind string, old, new kruntim
160175 return clienthelper .Merge (old .(client.Object ), new .(client.Object ))
161176}
162177
178+ // isAdmissionRegistrationResource returns true for ValidatingAdmissionPolicy
179+ // and ValidatingAdmissionPolicyBinding resources that should be managed via
180+ // server-side apply rather than the Gatekeeper-specific path.
181+ func isAdmissionRegistrationResource (uns * unstructured.Unstructured ) bool {
182+ return uns .GroupVersionKind ().Group == "admissionregistration.k8s.io"
183+ }
184+
185+ // ensureByServerSideApply creates or updates a single unstructured object
186+ // using server-side apply. This correctly reconciles all fields, unlike
187+ // ensureUnstructuredObj which only compares Gatekeeper's enforcementAction.
188+ func (dh * dynamicHelper ) ensureByServerSideApply (ctx context.Context , uns * unstructured.Unstructured ) error {
189+ gvr , err := dh .Resolve (uns .GroupVersionKind ().GroupKind ().String (), uns .GroupVersionKind ().Version )
190+ if err != nil {
191+ return err
192+ }
193+
194+ data , err := json .Marshal (uns )
195+ if err != nil {
196+ return fmt .Errorf ("marshalling %s/%s: %w" , uns .GroupVersionKind ().GroupKind (), uns .GetName (), err )
197+ }
198+
199+ dh .log .Infof ("Apply %s" , keyFunc (uns .GroupVersionKind ().GroupKind (), uns .GetNamespace (), uns .GetName ()))
200+ _ , err = dh .dynamicClient .Resource (* gvr ).
201+ Namespace (uns .GetNamespace ()).
202+ Patch (ctx , uns .GetName (), types .ApplyPatchType , data , metav1.PatchOptions {
203+ FieldManager : "aro-operator" ,
204+ Force : pointer .Bool (true ),
205+ })
206+ if err != nil {
207+ return fmt .Errorf ("server-side apply %s/%s: %w" , uns .GroupVersionKind ().GroupKind (), uns .GetName (), err )
208+ }
209+ return nil
210+ }
211+
163212func makeURLSegments (gvr * schema.GroupVersionResource , namespace , name string ) (url []string ) {
164213 if gvr .Group == "" {
165214 url = append (url , "api" )
0 commit comments