Files
capsule/pkg/runtime/client/patch.go
Oliver Bähler 0abc77b56a feat: diverse performance improvements (#1861)
Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2026-02-03 22:05:00 +01:00

313 lines
6.3 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
//nolint:dupl
package client
import (
"context"
"encoding/json"
"fmt"
"strings"
"gomodules.xyz/jsonpatch/v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/projectcapsule/capsule/pkg/api/meta"
)
type JSONPatch struct {
Operation JSONPatchOperation `json:"op"`
Path string `json:"path"`
Value any `json:"value,omitempty"`
}
type JSONPatchOperation string
const (
JSONPatchAdd JSONPatchOperation = "add"
JSONPatchReplace JSONPatchOperation = "replace"
JSONPatchRemove JSONPatchOperation = "remove"
)
func (j JSONPatchOperation) String() string {
return string(j)
}
func EscapeJSONPointer(s string) string {
s = strings.ReplaceAll(s, "~", "~0")
s = strings.ReplaceAll(s, "/", "~1")
return s
}
func JSONPatchesToRawPatch(patches []JSONPatch) (patch []byte, err error) {
return json.Marshal(patches)
}
func JSONPatchesToJSONPatchOperation(
patches []JSONPatch,
) []jsonpatch.JsonPatchOperation {
if len(patches) == 0 {
return nil
}
ops := make([]jsonpatch.JsonPatchOperation, 0, len(patches))
for _, p := range patches {
ops = append(ops, jsonpatch.JsonPatchOperation{
Operation: p.Operation.String(),
Path: p.Path,
Value: p.Value,
})
}
return ops
}
func ApplyPatches(
ctx context.Context,
c client.Client,
obj client.Object,
patches []JSONPatch,
manager string,
) (err error) {
if len(patches) == 0 {
return nil
}
rawPatch, err := JSONPatchesToRawPatch(patches)
if err != nil {
return err
}
return c.Patch(
ctx,
obj,
client.RawPatch(types.JSONPatchType, rawPatch),
client.FieldOwner(manager),
)
}
func AddLabelsPatch(labels map[string]string, keys map[string]string) []JSONPatch {
if len(keys) == 0 {
return nil
}
patches := make([]JSONPatch, 0, len(keys)+1)
// If labels is nil, /metadata/labels likely doesn't exist.
// JSONPatch add/replace to /metadata/labels/<k> requires /metadata/labels to exist.
if labels == nil {
patches = append(patches, JSONPatch{
Operation: JSONPatchAdd,
Path: "/metadata/labels",
Value: map[string]string{},
})
labels = map[string]string{} // local view for replace/add decision
}
for key, val := range keys {
op := JSONPatchAdd
if existing, ok := labels[key]; ok {
if existing == val {
continue
}
op = JSONPatchReplace
}
patches = append(patches, JSONPatch{
Operation: op,
Path: fmt.Sprintf("/metadata/labels/%s", EscapeJSONPointer(key)),
Value: val,
})
}
return patches
}
func AddAnnotationsPatch(annotations map[string]string, keys map[string]string) []JSONPatch {
if len(keys) == 0 {
return nil
}
patches := make([]JSONPatch, 0, len(keys)+1)
// If annotations is nil, /metadata/annotations likely doesn't exist.
// JSONPatch add/replace to /metadata/annotations/<k> requires /metadata/annotations to exist.
if annotations == nil {
patches = append(patches, JSONPatch{
Operation: JSONPatchAdd,
Path: "/metadata/annotations",
Value: map[string]string{},
})
annotations = map[string]string{}
}
for key, val := range keys {
op := JSONPatchAdd
if existing, ok := annotations[key]; ok {
if existing == val {
continue
}
op = JSONPatchReplace
}
patches = append(patches, JSONPatch{
Operation: op,
Path: fmt.Sprintf("/metadata/annotations/%s", EscapeJSONPointer(key)),
Value: val,
})
}
return patches
}
// PatchRemoveLabels returns a JSONPatch array for removing labels with matching keys.
func PatchRemoveLabels(labels map[string]string, keys []string) []JSONPatch {
var patches []JSONPatch
if labels == nil {
return patches
}
for _, key := range keys {
if _, ok := labels[key]; ok {
path := fmt.Sprintf("/metadata/labels/%s", EscapeJSONPointer(key))
patches = append(patches, JSONPatch{
Operation: JSONPatchRemove,
Path: path,
})
}
}
return patches
}
// PatchRemoveAnnotations returns a JSONPatch array for removing annotations with matching keys.
func PatchRemoveAnnotations(annotations map[string]string, keys []string) []JSONPatch {
var patches []JSONPatch
if annotations == nil {
return patches
}
for _, key := range keys {
if _, ok := annotations[key]; ok {
path := fmt.Sprintf("/metadata/annotations/%s", EscapeJSONPointer(key))
patches = append(patches, JSONPatch{
Operation: JSONPatchRemove,
Path: path,
})
}
}
return patches
}
func AddOwnerReferencePatch(
ownerrefs []metav1.OwnerReference,
ownerreference *metav1.OwnerReference,
) []JSONPatch {
if ownerreference == nil {
return nil
}
patches := make([]JSONPatch, 0, 2)
// Ensure parent exists if missing (nil slice usually means field absent)
if ownerrefs == nil {
patches = append(patches, JSONPatch{
Operation: JSONPatchAdd,
Path: "/metadata/ownerReferences",
Value: []metav1.OwnerReference{},
})
patches = append(patches, JSONPatch{
Operation: JSONPatchAdd,
Path: "/metadata/ownerReferences/-",
Value: ownerreference,
})
return patches
}
for i := range ownerrefs {
if ownerrefs[i].UID != ownerreference.UID {
continue
}
existing := ownerrefs[i]
if meta.LooseOwnerReferenceEqual(existing, *ownerreference) {
return nil
}
patches = append(patches, JSONPatch{
Operation: JSONPatchReplace,
Path: fmt.Sprintf("/metadata/ownerReferences/%d", i),
Value: ownerreference,
})
return patches
}
// Otherwise append
patches = append(patches, JSONPatch{
Operation: JSONPatchAdd,
Path: "/metadata/ownerReferences/-",
Value: ownerreference,
})
return patches
}
func RemoveOwnerReferencePatch(
ownerRefs []metav1.OwnerReference,
toRemove *metav1.OwnerReference,
) []JSONPatch {
if toRemove == nil {
return nil
}
if len(ownerRefs) == 0 {
return nil
}
idx := -1
for i := range ownerRefs {
if meta.LooseOwnerReferenceEqual(ownerRefs[i], *toRemove) {
idx = i
break
}
}
if idx == -1 {
return nil
}
patches := []JSONPatch{
{
Operation: JSONPatchRemove,
Path: fmt.Sprintf("/metadata/ownerReferences/%d", idx),
},
}
if len(ownerRefs) == 1 {
patches = append(patches, JSONPatch{
Operation: JSONPatchRemove,
Path: "/metadata/ownerReferences",
})
}
return patches
}