mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-01-07 05:46:37 +00:00
support JSON Merge Patch and Strategic Merge Patch in Resource Modifiers
Signed-off-by: lou <alex1988@outlook.com>
This commit is contained in:
46
internal/resourcemodifiers/json_merge_patch.go
Normal file
46
internal/resourcemodifiers/json_merge_patch.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type JSONMergePatch struct {
|
||||
PatchBytes []byte `json:"patchBytes,omitempty"`
|
||||
}
|
||||
|
||||
type JSONMergePatcher struct {
|
||||
patches []JSONMergePatch
|
||||
}
|
||||
|
||||
func (p *JSONMergePatcher) Patch(u *unstructured.Unstructured, _ logrus.FieldLogger) (*unstructured.Unstructured, error) {
|
||||
objBytes, err := u.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in marshaling object %s", err)
|
||||
}
|
||||
|
||||
var modifiedObjBytes []byte
|
||||
for _, patch := range p.patches {
|
||||
patchBytes, err := yaml.YAMLToJSON(patch.PatchBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in converting YAML to JSON %s", err)
|
||||
}
|
||||
|
||||
modifiedObjBytes, err = jsonpatch.MergePatch(objBytes, patchBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in applying JSON Patch %s", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
updated := &unstructured.Unstructured{}
|
||||
err = updated.UnmarshalJSON(modifiedObjBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in unmarshalling modified object %s", err.Error())
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
96
internal/resourcemodifiers/json_patch.go
Normal file
96
internal/resourcemodifiers/json_patch.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/sirupsen/logrus"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
type JSONPatch struct {
|
||||
Operation string `json:"operation"`
|
||||
From string `json:"from,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
func (p *JSONPatch) ToString() string {
|
||||
if addQuotes(p.Value) {
|
||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": "%s"}`, p.Operation, p.From, p.Path, p.Value)
|
||||
}
|
||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": %s}`, p.Operation, p.From, p.Path, p.Value)
|
||||
}
|
||||
|
||||
func addQuotes(value string) bool {
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
// if value is null, then don't add quotes
|
||||
if value == "null" {
|
||||
return false
|
||||
}
|
||||
// if value is a boolean, then don't add quotes
|
||||
if _, err := strconv.ParseBool(value); err == nil {
|
||||
return false
|
||||
}
|
||||
// if value is a json object or array, then don't add quotes.
|
||||
if strings.HasPrefix(value, "{") || strings.HasPrefix(value, "[") {
|
||||
return false
|
||||
}
|
||||
// if value is a number, then don't add quotes
|
||||
if _, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type JSONPatcher struct {
|
||||
patches []JSONPatch `yaml:"patches"`
|
||||
}
|
||||
|
||||
func (p *JSONPatcher) Patch(u *unstructured.Unstructured, logger logrus.FieldLogger) (*unstructured.Unstructured, error) {
|
||||
modifiedObjBytes, err := p.applyPatch(u)
|
||||
if err != nil {
|
||||
if errors.Is(err, jsonpatch.ErrTestFailed) {
|
||||
logger.Infof("Test operation failed for JSON Patch %s", err.Error())
|
||||
return u.DeepCopy(), nil
|
||||
}
|
||||
return nil, fmt.Errorf("error in applying JSON Patch %s", err.Error())
|
||||
}
|
||||
|
||||
updated := &unstructured.Unstructured{}
|
||||
err = updated.UnmarshalJSON(modifiedObjBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in unmarshalling modified object %s", err.Error())
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (p *JSONPatcher) applyPatch(u *unstructured.Unstructured) ([]byte, error) {
|
||||
patchBytes := p.patchArrayToByteArray()
|
||||
jsonPatch, err := jsonpatch.DecodePatch(patchBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in decoding json patch %s", err.Error())
|
||||
}
|
||||
|
||||
objBytes, err := u.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in marshaling object %s", err.Error())
|
||||
}
|
||||
|
||||
return jsonPatch.Apply(objBytes)
|
||||
}
|
||||
|
||||
func (p *JSONPatcher) patchArrayToByteArray() []byte {
|
||||
var patches []string
|
||||
for _, patch := range p.patches {
|
||||
patches = append(patches, patch.ToString())
|
||||
}
|
||||
patchesStr := strings.Join(patches, ",\n\t")
|
||||
return []byte(fmt.Sprintf(`[%s]`, patchesStr))
|
||||
}
|
||||
@@ -3,16 +3,16 @@ package resourcemodifiers
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/vmware-tanzu/velero/pkg/util/collections"
|
||||
@@ -23,23 +23,20 @@ const (
|
||||
ResourceModifierSupportedVersionV1 = "v1"
|
||||
)
|
||||
|
||||
type JSONPatch struct {
|
||||
Operation string `json:"operation"`
|
||||
From string `json:"from,omitempty"`
|
||||
Path string `json:"path"`
|
||||
Value string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
type Conditions struct {
|
||||
Namespaces []string `json:"namespaces,omitempty"`
|
||||
GroupResource string `json:"groupResource"`
|
||||
ResourceNameRegex string `json:"resourceNameRegex,omitempty"`
|
||||
LabelSelector *metav1.LabelSelector `json:"labelSelector,omitempty"`
|
||||
Matches []JSONPatch `json:"matches,omitempty"`
|
||||
}
|
||||
|
||||
type ResourceModifierRule struct {
|
||||
Conditions Conditions `json:"conditions"`
|
||||
Patches []JSONPatch `json:"patches"`
|
||||
Conditions Conditions `json:"conditions"`
|
||||
PatchData string `json:"patchData,omitempty"`
|
||||
Patches []JSONPatch `json:"patches,omitempty"`
|
||||
MergePatches []JSONMergePatch `json:"mergePatches,omitempty"`
|
||||
StrategicPatches []StrategicMergePatch `json:"strategicPatches,omitempty"`
|
||||
}
|
||||
|
||||
type ResourceModifiers struct {
|
||||
@@ -68,10 +65,10 @@ func GetResourceModifiersFromConfig(cm *v1.ConfigMap) (*ResourceModifiers, error
|
||||
return resModifiers, nil
|
||||
}
|
||||
|
||||
func (p *ResourceModifiers) ApplyResourceModifierRules(obj *unstructured.Unstructured, groupResource string, log logrus.FieldLogger) []error {
|
||||
func (p *ResourceModifiers) ApplyResourceModifierRules(obj *unstructured.Unstructured, groupResource string, scheme *runtime.Scheme, log logrus.FieldLogger) []error {
|
||||
var errs []error
|
||||
for _, rule := range p.ResourceModifierRules {
|
||||
err := rule.Apply(obj, groupResource, log)
|
||||
err := rule.apply(obj, groupResource, scheme, log)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
@@ -80,13 +77,19 @@ func (p *ResourceModifiers) ApplyResourceModifierRules(obj *unstructured.Unstruc
|
||||
return errs
|
||||
}
|
||||
|
||||
func (r *ResourceModifierRule) Apply(obj *unstructured.Unstructured, groupResource string, log logrus.FieldLogger) error {
|
||||
func (r *ResourceModifierRule) apply(obj *unstructured.Unstructured, groupResource string, scheme *runtime.Scheme, log logrus.FieldLogger) error {
|
||||
namespaceInclusion := collections.NewIncludesExcludes().Includes(r.Conditions.Namespaces...)
|
||||
if !namespaceInclusion.ShouldInclude(obj.GetNamespace()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if r.Conditions.GroupResource != groupResource {
|
||||
g, err := glob.Compile(r.Conditions.GroupResource)
|
||||
if err != nil {
|
||||
log.Errorf("bad glob pattern %s, err: %s", r.Conditions.GroupResource, err)
|
||||
return err
|
||||
}
|
||||
|
||||
if !g.Match(groupResource) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -110,57 +113,44 @@ func (r *ResourceModifierRule) Apply(obj *unstructured.Unstructured, groupResour
|
||||
}
|
||||
}
|
||||
|
||||
patches, err := r.PatchArrayToByteArray()
|
||||
match, err := matchConditions(obj, r.Conditions.Matches, log)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !match {
|
||||
log.Info("Conditions do not match, skip it")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Infof("Applying resource modifier patch on %s/%s", obj.GetNamespace(), obj.GetName())
|
||||
err = ApplyPatch(patches, obj, log)
|
||||
err = r.applyPatch(obj, scheme, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PatchArrayToByteArray converts all JsonPatch to string array with the format of jsonpatch.Patch and then convert it to byte array
|
||||
func (r *ResourceModifierRule) PatchArrayToByteArray() ([]byte, error) {
|
||||
var patches []string
|
||||
for _, patch := range r.Patches {
|
||||
patches = append(patches, patch.ToString())
|
||||
func matchConditions(u *unstructured.Unstructured, patches []JSONPatch, logger logrus.FieldLogger) (bool, error) {
|
||||
if len(patches) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
patchesStr := strings.Join(patches, ",\n\t")
|
||||
return []byte(fmt.Sprintf(`[%s]`, patchesStr)), nil
|
||||
}
|
||||
|
||||
func (p *JSONPatch) ToString() string {
|
||||
if addQuotes(p.Value) {
|
||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": "%s"}`, p.Operation, p.From, p.Path, p.Value)
|
||||
var fixed []JSONPatch
|
||||
for _, patch := range patches {
|
||||
patch.From = ""
|
||||
patch.Operation = "test"
|
||||
fixed = append(fixed, patch)
|
||||
}
|
||||
return fmt.Sprintf(`{"op": "%s", "from": "%s", "path": "%s", "value": %s}`, p.Operation, p.From, p.Path, p.Value)
|
||||
}
|
||||
|
||||
func ApplyPatch(patch []byte, obj *unstructured.Unstructured, log logrus.FieldLogger) error {
|
||||
jsonPatch, err := jsonpatch.DecodePatch(patch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in decoding json patch %s", err.Error())
|
||||
}
|
||||
objBytes, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in marshaling object %s", err.Error())
|
||||
}
|
||||
modifiedObjBytes, err := jsonPatch.Apply(objBytes)
|
||||
p := &JSONPatcher{patches: fixed}
|
||||
_, err := p.applyPatch(u)
|
||||
if err != nil {
|
||||
if errors.Is(err, jsonpatch.ErrTestFailed) {
|
||||
log.Infof("Test operation failed for JSON Patch %s", err.Error())
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
return fmt.Errorf("error in applying JSON Patch %s", err.Error())
|
||||
return false, err
|
||||
}
|
||||
err = obj.UnmarshalJSON(modifiedObjBytes)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in unmarshalling modified object %s", err.Error())
|
||||
}
|
||||
return nil
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func unmarshalResourceModifiers(yamlData []byte) (*ResourceModifiers, error) {
|
||||
@@ -172,25 +162,27 @@ func unmarshalResourceModifiers(yamlData []byte) (*ResourceModifiers, error) {
|
||||
return resModifiers, nil
|
||||
}
|
||||
|
||||
func addQuotes(value string) bool {
|
||||
if value == "" {
|
||||
return true
|
||||
}
|
||||
// if value is null, then don't add quotes
|
||||
if value == "null" {
|
||||
return false
|
||||
}
|
||||
// if value is a boolean, then don't add quotes
|
||||
if _, err := strconv.ParseBool(value); err == nil {
|
||||
return false
|
||||
}
|
||||
// if value is a json object or array, then don't add quotes.
|
||||
if strings.HasPrefix(value, "{") || strings.HasPrefix(value, "[") {
|
||||
return false
|
||||
}
|
||||
// if value is a number, then don't add quotes
|
||||
if _, err := strconv.ParseFloat(value, 64); err == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
type patcher interface {
|
||||
Patch(u *unstructured.Unstructured, logger logrus.FieldLogger) (*unstructured.Unstructured, error)
|
||||
}
|
||||
|
||||
func (r *ResourceModifierRule) applyPatch(u *unstructured.Unstructured, scheme *runtime.Scheme, logger logrus.FieldLogger) error {
|
||||
var p patcher
|
||||
if len(r.Patches) > 0 {
|
||||
p = &JSONPatcher{patches: r.Patches}
|
||||
} else if len(r.MergePatches) > 0 {
|
||||
p = &JSONMergePatcher{patches: r.MergePatches}
|
||||
} else if len(r.StrategicPatches) > 0 {
|
||||
p = &StrategicMergePatcher{patches: r.StrategicPatches, scheme: scheme}
|
||||
} else {
|
||||
return fmt.Errorf("no patch data found")
|
||||
}
|
||||
|
||||
updated, err := p.Patch(u, logger)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error in applying patch %s", err)
|
||||
}
|
||||
|
||||
u.SetUnstructuredContent(updated.Object)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func TestGetResourceModifiersFromConfig(t *testing.T) {
|
||||
@@ -704,7 +708,7 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
Version: tt.fields.Version,
|
||||
ResourceModifierRules: tt.fields.ResourceModifierRules,
|
||||
}
|
||||
got := p.ApplyResourceModifierRules(tt.args.obj, tt.args.groupResource, logrus.New())
|
||||
got := p.ApplyResourceModifierRules(tt.args.obj, tt.args.groupResource, nil, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.args.obj)
|
||||
@@ -712,6 +716,633 @@ func TestResourceModifiers_ApplyResourceModifierRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var podYAMLWithNginxImage = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
namespace: fake
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx
|
||||
name: nginx
|
||||
`
|
||||
|
||||
var podYAMLWithNginx1Image = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
namespace: fake
|
||||
spec:
|
||||
containers:
|
||||
- image: nginx1
|
||||
name: nginx
|
||||
`
|
||||
|
||||
var podYAMLWithNFSVolume = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
namespace: fake
|
||||
spec:
|
||||
containers:
|
||||
- image: fake
|
||||
name: fake
|
||||
volumeMounts:
|
||||
- mountPath: /fake1
|
||||
name: vol1
|
||||
- mountPath: /fake2
|
||||
name: vol2
|
||||
volumes:
|
||||
- name: vol1
|
||||
nfs:
|
||||
path: /fake2
|
||||
- name: vol2
|
||||
emptyDir: {}
|
||||
`
|
||||
|
||||
var podYAMLWithPVCVolume = `
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: pod1
|
||||
namespace: fake
|
||||
spec:
|
||||
containers:
|
||||
- image: fake
|
||||
name: fake
|
||||
volumeMounts:
|
||||
- mountPath: /fake1
|
||||
name: vol1
|
||||
- mountPath: /fake2
|
||||
name: vol2
|
||||
volumes:
|
||||
- name: vol1
|
||||
persistentVolumeClaim:
|
||||
claimName: pvc1
|
||||
- name: vol2
|
||||
emptyDir: {}
|
||||
`
|
||||
|
||||
var svcYAMLWithPort8000 = `
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: svc1
|
||||
namespace: fake
|
||||
spec:
|
||||
ports:
|
||||
- name: fake1
|
||||
port: 8001
|
||||
protocol: TCP
|
||||
targetPort: 8001
|
||||
- name: fake
|
||||
port: 8000
|
||||
protocol: TCP
|
||||
targetPort: 8000
|
||||
- name: fake2
|
||||
port: 8002
|
||||
protocol: TCP
|
||||
targetPort: 8002
|
||||
`
|
||||
|
||||
var svcYAMLWithPort9000 = `
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: svc1
|
||||
namespace: fake
|
||||
spec:
|
||||
ports:
|
||||
- name: fake1
|
||||
port: 8001
|
||||
protocol: TCP
|
||||
targetPort: 8001
|
||||
- name: fake
|
||||
port: 9000
|
||||
protocol: TCP
|
||||
targetPort: 9000
|
||||
- name: fake2
|
||||
port: 8002
|
||||
protocol: TCP
|
||||
targetPort: 8002
|
||||
`
|
||||
|
||||
var cmYAMLWithLabelAToB = `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
namespace: fake
|
||||
labels:
|
||||
a: b
|
||||
c: d
|
||||
`
|
||||
|
||||
var cmYAMLWithLabelAToC = `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
namespace: fake
|
||||
labels:
|
||||
a: c
|
||||
c: d
|
||||
`
|
||||
|
||||
var cmYAMLWithoutLabelA = `
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
namespace: fake
|
||||
labels:
|
||||
c: d
|
||||
`
|
||||
|
||||
func TestResourceModifiers_ApplyResourceModifierRules_StrategicMergePatch(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
utilruntime.Must(clientgoscheme.AddToScheme(scheme))
|
||||
unstructuredSerializer := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
o1, _, err := unstructuredSerializer.Decode([]byte(podYAMLWithNFSVolume), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
podWithNFSVolume := o1.(*unstructured.Unstructured)
|
||||
|
||||
o2, _, err := unstructuredSerializer.Decode([]byte(podYAMLWithPVCVolume), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
podWithPVCVolume := o2.(*unstructured.Unstructured)
|
||||
|
||||
o3, _, err := unstructuredSerializer.Decode([]byte(svcYAMLWithPort8000), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
svcWithPort8000 := o3.(*unstructured.Unstructured)
|
||||
|
||||
o4, _, err := unstructuredSerializer.Decode([]byte(svcYAMLWithPort9000), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
svcWithPort9000 := o4.(*unstructured.Unstructured)
|
||||
|
||||
o5, _, err := unstructuredSerializer.Decode([]byte(podYAMLWithNginxImage), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
podWithNginxImage := o5.(*unstructured.Unstructured)
|
||||
|
||||
o6, _, err := unstructuredSerializer.Decode([]byte(podYAMLWithNginx1Image), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
podWithNginx1Image := o6.(*unstructured.Unstructured)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rm *ResourceModifiers
|
||||
obj *unstructured.Unstructured
|
||||
groupResource string
|
||||
wantErr bool
|
||||
wantObj *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "update image",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"spec":{"containers":[{"name":"nginx","image":"nginx1"}]}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: podWithNginxImage.DeepCopy(),
|
||||
groupResource: "pods",
|
||||
wantErr: false,
|
||||
wantObj: podWithNginx1Image.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "update image with yaml format",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx1`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: podWithNginxImage.DeepCopy(),
|
||||
groupResource: "pods",
|
||||
wantErr: false,
|
||||
wantObj: podWithNginx1Image.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "replace nfs with pvc in volume",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"spec":{"volumes":[{"nfs":null,"name":"vol1","persistentVolumeClaim":{"claimName":"pvc1"}}]}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: podWithNFSVolume.DeepCopy(),
|
||||
groupResource: "pods",
|
||||
wantErr: false,
|
||||
wantObj: podWithPVCVolume.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "replace any other volume source with pvc in volume",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "pods",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"spec":{"volumes":[{"$retainKeys":["name","persistentVolumeClaim"],"name":"vol1","persistentVolumeClaim":{"claimName":"pvc1"}}]}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: podWithNFSVolume.DeepCopy(),
|
||||
groupResource: "pods",
|
||||
wantErr: false,
|
||||
wantObj: podWithPVCVolume.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "update a service port",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "services",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
StrategicPatches: []StrategicMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"spec":{"$setElementOrder/ports":[{"port":8001},{"port":9000},{"port":8002}],"ports":[{"name":"fake","port":9000,"protocol":"TCP","targetPort":9000},{"$patch":"delete","port":8000}]}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: svcWithPort8000.DeepCopy(),
|
||||
groupResource: "services",
|
||||
wantErr: false,
|
||||
wantObj: svcWithPort9000.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rm.ApplyResourceModifierRules(tt.obj, tt.groupResource, scheme, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceModifiers_ApplyResourceModifierRules_JSONMergePatch(t *testing.T) {
|
||||
unstructuredSerializer := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
o1, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToB), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToB := o1.(*unstructured.Unstructured)
|
||||
|
||||
o2, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToC), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToC := o2.(*unstructured.Unstructured)
|
||||
|
||||
o3, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithoutLabelA), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithoutLabelA := o3.(*unstructured.Unstructured)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rm *ResourceModifiers
|
||||
obj *unstructured.Unstructured
|
||||
groupResource string
|
||||
wantErr bool
|
||||
wantObj *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "update labels",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"metadata":{"labels":{"a":"c"}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "update labels in yaml format",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`metadata:
|
||||
labels:
|
||||
a: c`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "delete labels",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"metadata":{"labels":{"a":null}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithoutLabelA.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "add labels",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"metadata":{"labels":{"a":"b"}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithoutLabelA.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToB.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "delete non-existing labels",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "configmaps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"metadata":{"labels":{"a":null}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithoutLabelA.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithoutLabelA.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rm.ApplyResourceModifierRules(tt.obj, tt.groupResource, nil, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceModifiers_wildcard_in_GroupResource(t *testing.T) {
|
||||
unstructuredSerializer := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
o1, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToB), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToB := o1.(*unstructured.Unstructured)
|
||||
|
||||
o2, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToC), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToC := o2.(*unstructured.Unstructured)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rm *ResourceModifiers
|
||||
obj *unstructured.Unstructured
|
||||
groupResource string
|
||||
wantErr bool
|
||||
wantObj *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "match all groups and resources",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"metadata":{"labels":{"a":"c"}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "match all resources in group apps",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*.apps",
|
||||
Namespaces: []string{"fake"},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"metadata":{"labels":{"a":"c"}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "fake.apps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rm.ApplyResourceModifierRules(tt.obj, tt.groupResource, nil, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceModifiers_conditional_patches(t *testing.T) {
|
||||
unstructuredSerializer := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
|
||||
o1, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToB), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToB := o1.(*unstructured.Unstructured)
|
||||
|
||||
o2, _, err := unstructuredSerializer.Decode([]byte(cmYAMLWithLabelAToC), nil, nil)
|
||||
assert.NoError(t, err)
|
||||
cmWithLabelAToC := o2.(*unstructured.Unstructured)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
rm *ResourceModifiers
|
||||
obj *unstructured.Unstructured
|
||||
groupResource string
|
||||
wantErr bool
|
||||
wantObj *unstructured.Unstructured
|
||||
}{
|
||||
{
|
||||
name: "match conditions and apply patches",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*",
|
||||
Namespaces: []string{"fake"},
|
||||
Matches: []JSONPatch{
|
||||
{
|
||||
Path: "/metadata/labels/a",
|
||||
Value: "b",
|
||||
},
|
||||
},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"metadata":{"labels":{"a":"c"}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToC.DeepCopy(),
|
||||
},
|
||||
{
|
||||
name: "mismatch conditions and skip patches",
|
||||
rm: &ResourceModifiers{
|
||||
Version: "v1",
|
||||
ResourceModifierRules: []ResourceModifierRule{
|
||||
{
|
||||
Conditions: Conditions{
|
||||
GroupResource: "*",
|
||||
Namespaces: []string{"fake"},
|
||||
Matches: []JSONPatch{
|
||||
{
|
||||
Path: "/metadata/labels/a",
|
||||
Value: "c",
|
||||
},
|
||||
},
|
||||
},
|
||||
MergePatches: []JSONMergePatch{
|
||||
{
|
||||
PatchBytes: []byte(`{"metadata":{"labels":{"a":"c"}}}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
obj: cmWithLabelAToB.DeepCopy(),
|
||||
groupResource: "configmaps",
|
||||
wantErr: false,
|
||||
wantObj: cmWithLabelAToB.DeepCopy(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := tt.rm.ApplyResourceModifierRules(tt.obj, tt.groupResource, nil, logrus.New())
|
||||
|
||||
assert.Equal(t, tt.wantErr, len(got) > 0)
|
||||
assert.Equal(t, *tt.wantObj, *tt.obj)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJSONPatch_ToString(t *testing.T) {
|
||||
type fields struct {
|
||||
Operation string
|
||||
|
||||
157
internal/resourcemodifiers/strategic_merge_patch.go
Normal file
157
internal/resourcemodifiers/strategic_merge_patch.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package resourcemodifiers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/mergepatch"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"sigs.k8s.io/json"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type StrategicMergePatch struct {
|
||||
PatchBytes []byte `json:"patchBytes,omitempty"`
|
||||
}
|
||||
|
||||
type StrategicMergePatcher struct {
|
||||
patches []StrategicMergePatch
|
||||
scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
func (p *StrategicMergePatcher) Patch(u *unstructured.Unstructured, _ logrus.FieldLogger) (*unstructured.Unstructured, error) {
|
||||
gvk := u.GetObjectKind().GroupVersionKind()
|
||||
schemaReferenceObj, err := p.scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in unmarshaling object %s", err.Error())
|
||||
}
|
||||
|
||||
origin := u.DeepCopy()
|
||||
updated := u.DeepCopy()
|
||||
for _, patch := range p.patches {
|
||||
patchBytes, err := yaml.YAMLToJSON(patch.PatchBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in converting YAML to JSON %s", err)
|
||||
}
|
||||
|
||||
err = strategicPatchObject(origin, patchBytes, updated, schemaReferenceObj, metav1.FieldValidationStrict)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in applying JSON Patch %s", err.Error())
|
||||
}
|
||||
|
||||
origin = updated.DeepCopy()
|
||||
}
|
||||
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
// strategicPatchObject applies a strategic merge patch of `patchBytes` to
|
||||
// `originalObject` and stores the result in `objToUpdate`.
|
||||
// It additionally returns the map[string]interface{} representation of the
|
||||
// `originalObject` and `patchBytes`.
|
||||
// NOTE: Both `originalObject` and `objToUpdate` are supposed to be versioned.
|
||||
func strategicPatchObject(
|
||||
originalObject runtime.Object,
|
||||
patchBytes []byte,
|
||||
objToUpdate runtime.Object,
|
||||
schemaReferenceObj runtime.Object,
|
||||
validationDirective string,
|
||||
) error {
|
||||
originalObjMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(originalObject)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patchMap := make(map[string]interface{})
|
||||
var strictErrs []error
|
||||
if validationDirective == metav1.FieldValidationWarn || validationDirective == metav1.FieldValidationStrict {
|
||||
strictErrs, err = json.UnmarshalStrict(patchBytes, &patchMap)
|
||||
if err != nil {
|
||||
return apierrors.NewBadRequest(err.Error())
|
||||
}
|
||||
} else {
|
||||
if err = json.UnmarshalCaseSensitivePreserveInts(patchBytes, &patchMap); err != nil {
|
||||
return apierrors.NewBadRequest(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if err := applyPatchToObject(originalObjMap, patchMap, objToUpdate, schemaReferenceObj, strictErrs, validationDirective); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyPatchToObject applies a strategic merge patch of <patchMap> to
|
||||
// <originalMap> and stores the result in <objToUpdate>.
|
||||
// NOTE: <objToUpdate> must be a versioned object.
|
||||
func applyPatchToObject(
|
||||
originalMap map[string]interface{},
|
||||
patchMap map[string]interface{},
|
||||
objToUpdate runtime.Object,
|
||||
schemaReferenceObj runtime.Object,
|
||||
strictErrs []error,
|
||||
validationDirective string,
|
||||
) error {
|
||||
patchedObjMap, err := strategicpatch.StrategicMergeMapPatch(originalMap, patchMap, schemaReferenceObj)
|
||||
if err != nil {
|
||||
return interpretStrategicMergePatchError(err)
|
||||
}
|
||||
|
||||
// Rather than serialize the patched map to JSON, then decode it to an object, we go directly from a map to an object
|
||||
converter := runtime.DefaultUnstructuredConverter
|
||||
returnUnknownFields := validationDirective == metav1.FieldValidationWarn || validationDirective == metav1.FieldValidationStrict
|
||||
if err := converter.FromUnstructuredWithValidation(patchedObjMap, objToUpdate, returnUnknownFields); err != nil {
|
||||
strictError, isStrictError := runtime.AsStrictDecodingError(err)
|
||||
switch {
|
||||
case !isStrictError:
|
||||
// disregard any sttrictErrs, because it's an incomplete
|
||||
// list of strict errors given that we don't know what fields were
|
||||
// unknown because StrategicMergeMapPatch failed.
|
||||
// Non-strict errors trump in this case.
|
||||
return apierrors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
|
||||
field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), err.Error()),
|
||||
})
|
||||
case validationDirective == metav1.FieldValidationWarn:
|
||||
//addStrictDecodingWarnings(requestContext, append(strictErrs, strictError.Errors()...))
|
||||
default:
|
||||
strictDecodingError := runtime.NewStrictDecodingError(append(strictErrs, strictError.Errors()...))
|
||||
return apierrors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
|
||||
field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), strictDecodingError.Error()),
|
||||
})
|
||||
}
|
||||
} else if len(strictErrs) > 0 {
|
||||
switch {
|
||||
case validationDirective == metav1.FieldValidationWarn:
|
||||
//addStrictDecodingWarnings(requestContext, strictErrs)
|
||||
default:
|
||||
return apierrors.NewInvalid(schema.GroupKind{}, "", field.ErrorList{
|
||||
field.Invalid(field.NewPath("patch"), fmt.Sprintf("%+v", patchMap), runtime.NewStrictDecodingError(strictErrs).Error()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// interpretStrategicMergePatchError interprets the error type and returns an error with appropriate HTTP code.
|
||||
func interpretStrategicMergePatchError(err error) error {
|
||||
switch err {
|
||||
case mergepatch.ErrBadJSONDoc, mergepatch.ErrBadPatchFormatForPrimitiveList, mergepatch.ErrBadPatchFormatForRetainKeys, mergepatch.ErrBadPatchFormatForSetElementOrderList, mergepatch.ErrUnsupportedStrategicMergePatchFormat:
|
||||
return apierrors.NewBadRequest(err.Error())
|
||||
case mergepatch.ErrNoListOfLists, mergepatch.ErrPatchContentNotMatchRetainKeys:
|
||||
return apierrors.NewGenericServerResponse(http.StatusUnprocessableEntity, "", schema.GroupResource{}, "", err.Error(), 0, false)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -1353,7 +1353,7 @@ func (ctx *restoreContext) restoreItem(obj *unstructured.Unstructured, groupReso
|
||||
}
|
||||
|
||||
if ctx.resourceModifiers != nil {
|
||||
if errList := ctx.resourceModifiers.ApplyResourceModifierRules(obj, groupResource.String(), ctx.log); errList != nil {
|
||||
if errList := ctx.resourceModifiers.ApplyResourceModifierRules(obj, groupResource.String(), ctx.kbClient.Scheme(), ctx.log); errList != nil {
|
||||
for _, err := range errList {
|
||||
errs.Add(namespace, err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user