mirror of
https://github.com/k3s-io/k3s.git
synced 2026-02-03 20:39:49 -05:00
Add JSON patch helper
Adds helper function for building JsonPatch operation lists, which allows modifying a resource without having to manually refresh the object and retry the change on conflict. Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
This commit is contained in:
parent
efcf8eb7ac
commit
ceebaade3e
1 changed files with 109 additions and 0 deletions
109
pkg/util/patch.go
Normal file
109
pkg/util/patch.go
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
// controllerPatcher matches the Patch function exposed by rancher/wrangler Controllers
|
||||
type controllerPatcher[T runtime.Object] interface {
|
||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (T, error)
|
||||
}
|
||||
|
||||
// clientPatcher matches the Patch function exposed by k8s.io/client-go Clients
|
||||
type clientPatcher[T runtime.Object] interface {
|
||||
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (T, error)
|
||||
}
|
||||
|
||||
// patchWrapper wraps the Patch functions provided by either wrangler or client-go
|
||||
type patchWrapper[T runtime.Object] struct {
|
||||
patcher any
|
||||
}
|
||||
|
||||
// NewPatcher wraps the provided controller or client for use as a generic patcher
|
||||
// note that the patcher is not validated for use until `Patch` is called
|
||||
func NewPatcher[T runtime.Object](patcher any) *patchWrapper[T] {
|
||||
return &patchWrapper[T]{
|
||||
patcher: patcher,
|
||||
}
|
||||
}
|
||||
|
||||
// Patch applies the provided PatchList to the specified resource
|
||||
func (p *patchWrapper[T]) Patch(ctx context.Context, pl *PatchList, name string, subresources ...string) (T, error) {
|
||||
var t T
|
||||
if pl == nil {
|
||||
pl = NewPatchList()
|
||||
}
|
||||
b, err := json.Marshal(pl.ops)
|
||||
if err != nil {
|
||||
return t, err
|
||||
}
|
||||
if patch, ok := p.patcher.(clientPatcher[T]); ok {
|
||||
return patch.Patch(ctx, name, types.JSONPatchType, b, metav1.PatchOptions{}, subresources...)
|
||||
}
|
||||
if patch, ok := p.patcher.(controllerPatcher[T]); ok {
|
||||
return patch.Patch(name, types.JSONPatchType, b, subresources...)
|
||||
}
|
||||
return t, fmt.Errorf("unable to patch %T with %T", t, p.patcher)
|
||||
}
|
||||
|
||||
// PatchList is a generic list of JSONPatch operations to apply to a resource
|
||||
type PatchList struct {
|
||||
ops []map[string]any
|
||||
}
|
||||
|
||||
// NewPatchList creates a new empty patch list
|
||||
func NewPatchList() *PatchList {
|
||||
return &PatchList{ops: []map[string]any{}}
|
||||
}
|
||||
|
||||
// Add appends an `add` operation to the patch list
|
||||
func (pl *PatchList) Add(value any, path ...string) *PatchList {
|
||||
if pl == nil {
|
||||
pl = NewPatchList()
|
||||
}
|
||||
if len(path) > 0 {
|
||||
for i := range path {
|
||||
path[i] = strings.ReplaceAll(path[i], "/", "~1")
|
||||
}
|
||||
pl.ops = append(pl.ops, map[string]any{
|
||||
"op": "add",
|
||||
"value": value,
|
||||
"path": "/" + strings.Join(path, "/"),
|
||||
})
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
// Remove appends a `remove` operation to the patch list
|
||||
func (pl *PatchList) Remove(path ...string) *PatchList {
|
||||
if pl == nil {
|
||||
pl = NewPatchList()
|
||||
}
|
||||
if len(path) > 0 {
|
||||
for i := range path {
|
||||
path[i] = strings.ReplaceAll(path[i], "/", "~1")
|
||||
}
|
||||
pl.ops = append(pl.ops, map[string]any{
|
||||
"op": "remove",
|
||||
"path": "/" + strings.Join(path, "/"),
|
||||
})
|
||||
}
|
||||
return pl
|
||||
}
|
||||
|
||||
// ToJSON returns a JSON string containing the patch operations
|
||||
// This is just a thin wrapper around `json.Marshal`
|
||||
func (pl *PatchList) ToJSON() (string, error) {
|
||||
if pl == nil {
|
||||
pl = NewPatchList()
|
||||
}
|
||||
b, err := json.Marshal(pl.ops)
|
||||
return string(b), err
|
||||
}
|
||||
Loading…
Reference in a new issue