mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
Start supporting strategic merge patch in kyaml merge2.
This commit is contained in:
@@ -14,6 +14,7 @@ fmt:
|
||||
go fmt ./...
|
||||
|
||||
generate:
|
||||
(which $(GOPATH)/bin/stringer || go get golang.org/x/tools/cmd/stringer)
|
||||
go generate ./...
|
||||
|
||||
license:
|
||||
|
||||
@@ -4,6 +4,76 @@
|
||||
package merge2_test
|
||||
|
||||
var listTestCases = []testCase{
|
||||
{description: `strategic merge patch delete 1`,
|
||||
source: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo1
|
||||
$patch: delete
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
dest: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
},
|
||||
{description: `strategic merge patch delete 2`,
|
||||
source: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
$patch: delete
|
||||
`,
|
||||
dest: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
- name: foo3
|
||||
`,
|
||||
expected: `
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo1
|
||||
- name: foo2
|
||||
`,
|
||||
},
|
||||
{description: `merge k8s deployment containers`,
|
||||
source: `
|
||||
apiVersion: apps/v1
|
||||
|
||||
@@ -4,6 +4,87 @@
|
||||
package merge2_test
|
||||
|
||||
var mapTestCases = []testCase{
|
||||
|
||||
{description: `strategic merge patch delete 1`,
|
||||
source: `
|
||||
kind: Deployment
|
||||
$patch: delete
|
||||
`,
|
||||
dest: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
foo: bar1
|
||||
`,
|
||||
expected: ``,
|
||||
},
|
||||
|
||||
{description: `strategic merge patch delete 2`,
|
||||
source: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
$patch: delete
|
||||
`,
|
||||
dest: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
foo: bar
|
||||
color: red
|
||||
`,
|
||||
expected: `
|
||||
kind: Deployment
|
||||
`,
|
||||
},
|
||||
|
||||
{description: `strategic merge patch delete 3`,
|
||||
source: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
metadata:
|
||||
name: wut
|
||||
template:
|
||||
$patch: delete
|
||||
`,
|
||||
dest: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
metadata:
|
||||
name: wut
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: foo
|
||||
- name: bar
|
||||
`,
|
||||
expected: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
metadata:
|
||||
name: wut
|
||||
`,
|
||||
},
|
||||
|
||||
{description: `strategic merge patch replace 1`,
|
||||
source: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
metal: heavy
|
||||
$patch: replace
|
||||
veggie: carrot
|
||||
`,
|
||||
dest: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
river: nile
|
||||
color: red
|
||||
`,
|
||||
expected: `
|
||||
kind: Deployment
|
||||
spec:
|
||||
metal: heavy
|
||||
veggie: carrot
|
||||
`,
|
||||
},
|
||||
|
||||
{description: `merge Map -- update field in dest`,
|
||||
source: `
|
||||
kind: Deployment
|
||||
|
||||
@@ -60,8 +60,20 @@ func (m Merger) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.R
|
||||
// clear the value
|
||||
return walk.ClearNode, nil
|
||||
}
|
||||
// Recursively Merge dest
|
||||
return nodes.Dest(), nil
|
||||
|
||||
ps, err := determineMappingNodePatchStrategy(nodes.Origin())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch ps {
|
||||
case smpDelete:
|
||||
return walk.ClearNode, nil
|
||||
case smpReplace:
|
||||
return nodes.Origin(), nil
|
||||
default:
|
||||
return nodes.Dest(), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (m Merger) VisitScalar(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) {
|
||||
|
||||
71
kyaml/yaml/merge2/smpdirective.go
Normal file
71
kyaml/yaml/merge2/smpdirective.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package merge2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// A strategic merge patch directive.
|
||||
// See https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
|
||||
//
|
||||
//go:generate stringer -type=smpDirective -linecomment
|
||||
type smpDirective int
|
||||
|
||||
const (
|
||||
smpUnknown smpDirective = iota // unknown
|
||||
smpReplace // replace
|
||||
smpDelete // delete
|
||||
smpMerge // merge
|
||||
)
|
||||
|
||||
const strategicMergePatchDirectiveKey = "$patch"
|
||||
|
||||
// Examine patch for a strategic merge patch directive.
|
||||
// If found, return it, and remove the directive from the patch.
|
||||
func determineSmpDirective(patch *yaml.RNode) (smpDirective, error) {
|
||||
if patch == nil {
|
||||
return smpMerge, nil
|
||||
}
|
||||
switch patch.YNode().Kind {
|
||||
case yaml.SequenceNode:
|
||||
return determineSequenceNodePatchStrategy(patch)
|
||||
case yaml.MappingNode:
|
||||
return determineMappingNodePatchStrategy(patch)
|
||||
default:
|
||||
return smpUnknown, fmt.Errorf(
|
||||
"no implemented strategic merge patch strategy for '%s' ('%s')",
|
||||
patch.YNode().ShortTag(), patch.MustString())
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: what should this do?
|
||||
func determineSequenceNodePatchStrategy(_ *yaml.RNode) (smpDirective, error) {
|
||||
return smpMerge, nil
|
||||
}
|
||||
|
||||
func determineMappingNodePatchStrategy(patch *yaml.RNode) (smpDirective, error) {
|
||||
node, err := patch.Pipe(yaml.Get(strategicMergePatchDirectiveKey))
|
||||
if err != nil || node == nil || node.YNode() == nil {
|
||||
return smpMerge, nil
|
||||
}
|
||||
v := node.YNode().Value
|
||||
if v == smpDelete.String() {
|
||||
return smpDelete, elidePatchDirective(patch)
|
||||
}
|
||||
if v == smpReplace.String() {
|
||||
return smpReplace, elidePatchDirective(patch)
|
||||
}
|
||||
if v == smpMerge.String() {
|
||||
return smpMerge, elidePatchDirective(patch)
|
||||
}
|
||||
return smpUnknown, fmt.Errorf(
|
||||
"unknown patch strategy '%s'", v)
|
||||
}
|
||||
|
||||
func elidePatchDirective(patch *yaml.RNode) error {
|
||||
return patch.PipeE(yaml.Clear(strategicMergePatchDirectiveKey))
|
||||
}
|
||||
26
kyaml/yaml/merge2/smpdirective_string.go
Normal file
26
kyaml/yaml/merge2/smpdirective_string.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Code generated by "stringer -type=smpDirective -linecomment"; DO NOT EDIT.
|
||||
|
||||
package merge2
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[smpUnknown-0]
|
||||
_ = x[smpReplace-1]
|
||||
_ = x[smpDelete-2]
|
||||
_ = x[smpMerge-3]
|
||||
}
|
||||
|
||||
const _smpDirective_name = "unknownreplacedeletemerge"
|
||||
|
||||
var _smpDirective_index = [...]uint8{0, 7, 14, 20, 25}
|
||||
|
||||
func (i smpDirective) String() string {
|
||||
if i < 0 || i >= smpDirective(len(_smpDirective_index)-1) {
|
||||
return "smpDirective(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _smpDirective_name[_smpDirective_index[i]:_smpDirective_index[i+1]]
|
||||
}
|
||||
112
kyaml/yaml/merge2/smpdirective_test.go
Normal file
112
kyaml/yaml/merge2/smpdirective_test.go
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
package merge2
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
func Test_determineSmpDirective(t *testing.T) {
|
||||
var cases = map[string]struct {
|
||||
patch string
|
||||
elided string
|
||||
expected smpDirective
|
||||
errExpected string
|
||||
}{
|
||||
`scalar`: {
|
||||
patch: "dumb",
|
||||
expected: smpMerge,
|
||||
errExpected: "no implemented strategic merge patch strategy",
|
||||
},
|
||||
`list merge`: {
|
||||
patch: `
|
||||
- one
|
||||
- two
|
||||
- three
|
||||
`,
|
||||
expected: smpMerge,
|
||||
elided: `- one
|
||||
- two
|
||||
- three
|
||||
`,
|
||||
},
|
||||
`map replace`: {
|
||||
patch: `
|
||||
metal: heavy
|
||||
$patch: replace
|
||||
veggie: carrot
|
||||
`,
|
||||
expected: smpReplace,
|
||||
elided: `metal: heavy
|
||||
veggie: carrot
|
||||
`,
|
||||
},
|
||||
`map delete`: {
|
||||
patch: `
|
||||
metal: heavy
|
||||
$patch: delete
|
||||
veggie: carrot
|
||||
`,
|
||||
expected: smpDelete,
|
||||
elided: `metal: heavy
|
||||
veggie: carrot
|
||||
`,
|
||||
},
|
||||
`map merge`: {
|
||||
patch: `
|
||||
metal: heavy
|
||||
$patch: merge
|
||||
veggie: carrot
|
||||
`,
|
||||
expected: smpMerge,
|
||||
elided: `metal: heavy
|
||||
veggie: carrot
|
||||
`,
|
||||
},
|
||||
`map default`: {
|
||||
patch: `
|
||||
metal: heavy
|
||||
veggie: carrot
|
||||
`,
|
||||
expected: smpMerge,
|
||||
elided: `metal: heavy
|
||||
veggie: carrot
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for n := range cases {
|
||||
tc := cases[n]
|
||||
t.Run(n, func(t *testing.T) {
|
||||
p, err := yaml.Parse(tc.patch)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected parse err %v", err)
|
||||
}
|
||||
unwrapped := yaml.NewRNode(p.YNode())
|
||||
actual, err := determineSmpDirective(unwrapped)
|
||||
if err == nil {
|
||||
if tc.errExpected != "" {
|
||||
t.Fatalf("should have seen an error")
|
||||
}
|
||||
if tc.expected != actual {
|
||||
t.Fatalf("expected %s, got %s", tc.expected, actual)
|
||||
}
|
||||
if tc.elided != unwrapped.MustString() {
|
||||
t.Fatalf(
|
||||
"expected %s, got %s",
|
||||
tc.elided, unwrapped.MustString())
|
||||
}
|
||||
} else {
|
||||
if tc.errExpected == "" {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if !strings.Contains(err.Error(), tc.errExpected) {
|
||||
t.Fatalf("expected some error other than: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user