mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-07-17 09:49:13 +00:00
Merge pull request #2663 from monopole/smpAttempt
Start supporting strategic merge patch in kyaml/yaml/merge2.
This commit is contained in:
@@ -14,6 +14,7 @@ fmt:
|
|||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
|
||||||
generate:
|
generate:
|
||||||
|
(which $(GOPATH)/bin/stringer || go get golang.org/x/tools/cmd/stringer)
|
||||||
go generate ./...
|
go generate ./...
|
||||||
|
|
||||||
license:
|
license:
|
||||||
|
|||||||
@@ -4,6 +4,76 @@
|
|||||||
package merge2_test
|
package merge2_test
|
||||||
|
|
||||||
var listTestCases = []testCase{
|
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`,
|
{description: `merge k8s deployment containers`,
|
||||||
source: `
|
source: `
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
|
|||||||
@@ -4,6 +4,87 @@
|
|||||||
package merge2_test
|
package merge2_test
|
||||||
|
|
||||||
var mapTestCases = []testCase{
|
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`,
|
{description: `merge Map -- update field in dest`,
|
||||||
source: `
|
source: `
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
|
|||||||
@@ -60,8 +60,20 @@ func (m Merger) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.R
|
|||||||
// clear the value
|
// clear the value
|
||||||
return walk.ClearNode, nil
|
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) {
|
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