mirror of
https://github.com/k3s-io/k3s.git
synced 2026-02-03 20:39:49 -05:00
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>
109 lines
3 KiB
Go
109 lines
3 KiB
Go
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
|
|
}
|