@@ -181,8 +181,23 @@ func createPatch(originalObj, currentObj runtime.Object, target *resource.Info)
181181 // 5. If chart unchanged (old == new) but current != new, return current->new patch applied to current to restore chart state
182182 // (i.e., detect and revert manual changes by restoring chart-owned fields to the chart's desired state)
183183
184+ // Clean metadata fields that shouldn't be compared (they're server-managed)
185+ // This prevents "resourceVersion: Invalid value: 0" errors when dry-running patches
186+ cleanedOldData , err := cleanMetadataForPatch (oldData )
187+ if err != nil {
188+ return nil , types .MergePatchType , fmt .Errorf ("cleaning old metadata: %w" , err )
189+ }
190+ cleanedNewData , err := cleanMetadataForPatch (newData )
191+ if err != nil {
192+ return nil , types .MergePatchType , fmt .Errorf ("cleaning new metadata: %w" , err )
193+ }
194+ cleanedCurrentData , err := cleanMetadataForPatch (currentData )
195+ if err != nil {
196+ return nil , types .MergePatchType , fmt .Errorf ("cleaning current metadata: %w" , err )
197+ }
198+
184199 // Step 1: Create patch from old -> new (what the chart wants to change)
185- chartChanges , err := jsonpatch .CreateMergePatch (oldData , newData )
200+ chartChanges , err := jsonpatch .CreateMergePatch (cleanedOldData , cleanedNewData )
186201 if err != nil {
187202 return nil , types .MergePatchType , fmt .Errorf ("creating chart changes patch: %w" , err )
188203 }
@@ -192,22 +207,22 @@ func createPatch(originalObj, currentObj runtime.Object, target *resource.Info)
192207
193208 if chartChanged {
194209 // Step 2: Apply chart changes to current (merge chart changes with live state)
195- mergedData , err := jsonpatch .MergePatch (currentData , chartChanges )
210+ mergedData , err := jsonpatch .MergePatch (cleanedCurrentData , chartChanges )
196211 if err != nil {
197212 return nil , types .MergePatchType , fmt .Errorf ("applying chart changes to current: %w" , err )
198213 }
199214
200215 // Step 3: Create patch from current -> merged (what to apply to current)
201216 // This patch, when applied to current, will produce the merged result
202- patch , err := jsonpatch .CreateMergePatch (currentData , mergedData )
217+ patch , err := jsonpatch .CreateMergePatch (cleanedCurrentData , mergedData )
203218 return patch , types .MergePatchType , err
204219 }
205220
206221 // Chart didn't change (old == new), but we need to detect if current diverges
207222 // from the chart state. This is the case where manual changes were made to
208223 // chart-owned fields.
209224 // Create a patch from current -> new to detect and restore drift
210- patch , err := jsonpatch .CreateMergePatch (currentData , newData )
225+ patch , err := jsonpatch .CreateMergePatch (cleanedCurrentData , cleanedNewData )
211226 return patch , types .MergePatchType , err
212227 }
213228
@@ -224,6 +239,26 @@ func isPatchEmpty(patch []byte) bool {
224239 return len (patch ) == 0 || string (patch ) == "{}" || string (patch ) == "null"
225240}
226241
242+ func cleanMetadataForPatch (data []byte ) ([]byte , error ) {
243+ var objMap map [string ]interface {}
244+ if err := json .Unmarshal (data , & objMap ); err != nil {
245+ return nil , err
246+ }
247+
248+ if metadata , ok := objMap ["metadata" ].(map [string ]interface {}); ok {
249+ delete (metadata , "resourceVersion" )
250+ delete (metadata , "generation" )
251+ delete (metadata , "creationTimestamp" )
252+ delete (metadata , "uid" )
253+ delete (metadata , "managedFields" )
254+ delete (metadata , "selfLink" )
255+ }
256+
257+ delete (objMap , "status" )
258+
259+ return json .Marshal (objMap )
260+ }
261+
227262func objectKey (r * resource.Info ) string {
228263 gvk := r .Object .GetObjectKind ().GroupVersionKind ()
229264 return fmt .Sprintf ("%s/%s/%s/%s" , gvk .GroupVersion ().String (), gvk .Kind , r .Namespace , r .Name )
0 commit comments