Use OpenAPI when merging (3way) resources

- When merging (3way) resources use the patch strategy from the openAPI if the definition exists for the field
- Allow disabling of guessing patch strategy merge keys when no definition exists
- Support defining strategy and key directly on configuration fields through line and header coments
- Support attaching schema to parent fields of lists, and propagating -- e.g. that a field is a PodTemplate
This commit is contained in:
Phillip Wittrock
2020-02-27 10:08:40 -08:00
parent 8991b193c6
commit 5d1a0346b5
24 changed files with 991 additions and 507 deletions

View File

@@ -7,14 +7,14 @@ var elementTestCases = []testCase{
//
// Test Case
//
{`Add an element to an existing list`,
`
{description: `Add an element to an existing list`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:1
`,
`
update: `
kind: Deployment
containers:
- name: foo
@@ -22,13 +22,13 @@ containers:
- name: baz
image: baz:2
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:1
`,
`
expected: `
kind: Deployment
containers:
- name: foo
@@ -36,65 +36,65 @@ containers:
- image: baz:2
name: baz
`, nil},
`},
//
// Test Case
//
{`Add an element to a non-existing list`,
`
{description: `Add an element to a non-existing list`,
origin: `
kind: Deployment`,
`
update: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
local: `
kind: Deployment
`,
`
expected: `
kind: Deployment
containers:
- image: foo:bar
name: foo
`, nil},
`},
{`Add an element to a non-existing list, existing in dest`,
`
{description: `Add an element to a non-existing list, existing in dest`,
origin: `
kind: Deployment`,
`
update: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
local: `
kind: Deployment
containers:
- name: baz
image: baz:bar
`,
`
expected: `
kind: Deployment
containers:
- name: baz
image: baz:bar
- image: foo:bar
name: foo
`, nil},
`},
//
// Test Case
// TODO(pwittrock): Figure out if there is something better we can do here
// This element is missing from the destination -- only the new fields are added
{`Add a field to the element, element missing from dest`,
`
{description: `Add a field to the element, element missing from dest`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar`,
`
update: `
kind: Deployment
containers:
- name: foo
@@ -102,22 +102,22 @@ containers:
command:
- run.sh
`,
`
local: `
kind: Deployment
`,
`
expected: `
kind: Deployment
containers:
- command:
- run.sh
name: foo
`, nil},
`},
//
// Test Case
//
{`Update a field on the elem, element missing from the dest`,
`
{description: `Update a field on the elem, element missing from the dest`,
origin: `
kind: Deployment
containers:
- name: foo
@@ -125,7 +125,7 @@ containers:
command:
- run.sh
`,
`
update: `
kind: Deployment
containers:
- name: foo
@@ -133,210 +133,210 @@ containers:
command:
- run2.sh
`,
`
local: `
kind: Deployment
`,
`
expected: `
kind: Deployment
containers:
- command:
- run2.sh
name: foo
`, nil},
`},
//
// Test Case
//
{`Update a field on the elem, element present in the dest`,
`
{description: `Update a field on the elem, element present in the dest`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run.sh']
`,
`
update: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run2.sh']
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run.sh']
`,
`
expected: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run2.sh']
`, nil},
`},
//
// Test Case
//
{`Add a field on the elem, element present in the dest`,
`
{description: `Add a field on the elem, element present in the dest`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
update: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run2.sh']
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
expected: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run2.sh']
`, nil},
`},
//
// Test Case
//
{`Add a field on the elem, element and field present in the dest`,
`
{description: `Add a field on the elem, element and field present in the dest`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
update: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run2.sh']
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run.sh']
`,
`
expected: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run2.sh']
`, nil},
`},
//
// Test Case
//
{`Ignore an element`,
`
{description: `Ignore an element`,
origin: `
kind: Deployment
containers: {}
`,
`
update: `
kind: Deployment
containers: {}
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
expected: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`, nil},
`},
//
// Test Case
//
{`Leave deleted`,
`
{description: `Leave deleted`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
update: `
kind: Deployment
`,
`
local: `
kind: Deployment
`,
`
expected: `
kind: Deployment
`, nil},
`},
//
// Test Case
//
{`Remove an element -- matching`,
`
{description: `Remove an element -- matching`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
update: `
kind: Deployment
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
expected: `
kind: Deployment
`, nil},
`},
//
// Test Case
//
{`Remove an element -- field missing from update`,
`
{description: `Remove an element -- field missing from update`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
update: `
kind: Deployment
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run.sh']
`,
`
expected: `
kind: Deployment
`, nil},
`},
//
// Test Case
//
{`Remove an element -- element missing`,
`
{description: `Remove an element -- element missing`,
origin: `
kind: Deployment
containers:
- name: foo
@@ -344,13 +344,13 @@ containers:
- name: baz
image: baz:bar
`,
`
update: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
local: `
kind: Deployment
containers:
- name: foo
@@ -359,60 +359,273 @@ containers:
- name: baz
image: baz:bar
`,
`
expected: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run.sh']
`, nil},
`},
//
// Test Case
//
{`Remove an element -- empty containers`,
`
{description: `Remove an element -- empty containers`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
update: `
kind: Deployment
containers: {}
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run.sh']
`,
`
expected: `
kind: Deployment
`, nil},
`},
//
// Test Case
//
{`Remove an element -- missing list field`,
`
{description: `Remove an element -- missing list field`,
origin: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
`
update: `
kind: Deployment
`,
`
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
command: ['run.sh']
`,
`
expected: `
kind: Deployment
`, nil},
`},
//
// Test Case
//
{description: `no infer merge keys no merge'`,
origin: `
kind: Deployment
containers:
- name: foo
`,
update: `
kind: Deployment
containers:
- name: foo
command: ['run2.sh']
`,
local: `
kind: Deployment
containers:
- name: foo
image: foo:bar
`,
expected: `
kind: Deployment
containers:
- name: foo
command: ['run2.sh']
`,
noInfer: true,
},
//
// Test Case
//
{description: `no infer merge keys merge using schema`,
origin: `
kind: Deployment
apiVersion: apps/v1
spec:
template:
spec:
containers:
- name: foo
`,
update: `
kind: Deployment
apiVersion: apps/v1
spec:
template:
spec:
containers:
- name: foo
command: ['run2.sh']
`,
local: `
kind: Deployment
apiVersion: apps/v1
spec:
template:
spec:
containers:
- name: foo
image: foo:bar
`,
expected: `
kind: Deployment
apiVersion: apps/v1
spec:
template:
spec:
containers:
- name: foo
image: foo:bar
command: ['run2.sh']
`,
noInfer: true,
},
//
// Test Case
//
{description: `no infer merge keys merge using explicit schema as line comment'`,
origin: `
kind: Deployment
containers:
- name: foo
`,
update: `
kind: Deployment
containers:
- name: foo
command: ['run2.sh']
`,
local: `
kind: Deployment
containers: # {"items":{"$ref": "#/definitions/io.k8s.api.core.v1.Container"},"type":"array","x-kubernetes-patch-merge-key":"name","x-kubernetes-patch-strategy": "merge"}
- name: foo # hell ow
image: foo:bar
`,
expected: `
kind: Deployment
containers: # {"items":{"$ref": "#/definitions/io.k8s.api.core.v1.Container"},"type":"array","x-kubernetes-patch-merge-key":"name","x-kubernetes-patch-strategy": "merge"}
- name: foo # hell ow
image: foo:bar
command: ['run2.sh']
`,
noInfer: true,
},
//
// Test Case
//
{description: `no infer merge keys merge using explicit schema as head comment'`,
origin: `
kind: Deployment
containers:
- name: foo
`,
update: `
kind: Deployment
containers:
- name: foo
command: ['run2.sh']
`,
local: `
kind: Deployment
# {"items":{"$ref": "#/definitions/io.k8s.api.core.v1.Container"},"type":"array","x-kubernetes-patch-merge-key":"name","x-kubernetes-patch-strategy": "merge"}
containers:
- name: foo # hell ow
image: foo:bar
`,
expected: `
kind: Deployment
# {"items":{"$ref": "#/definitions/io.k8s.api.core.v1.Container"},"type":"array","x-kubernetes-patch-merge-key":"name","x-kubernetes-patch-strategy": "merge"}
containers:
- name: foo # hell ow
image: foo:bar
command: ['run2.sh']
`,
noInfer: true,
},
//
// Test Case
//
{description: `no infer merge keys merge using explicit schema to parent field'`,
origin: `
kind: Deployment
spec:
containers:
- name: foo
`,
update: `
kind: Deployment
spec:
containers:
- name: foo
command: ['run2.sh']
`,
local: `
kind: Deployment
spec: # {"$ref":"#/definitions/io.k8s.api.core.v1.PodSpec"}
containers:
- name: foo # hell ow
image: foo:bar
`,
expected: `
kind: Deployment
spec: # {"$ref":"#/definitions/io.k8s.api.core.v1.PodSpec"}
containers:
- name: foo # hell ow
image: foo:bar
command: ['run2.sh']
`,
noInfer: true,
},
//
// Test Case
//
{description: `no infer merge keys merge using explicit schema to parent field header'`,
origin: `
kind: Deployment
spec:
containers:
- name: foo
`,
update: `
kind: Deployment
spec:
containers:
- name: foo
command: ['run2.sh']
`,
local: `
kind: Deployment
# {"$ref":"#/definitions/io.k8s.api.core.v1.PodSpec"}
spec:
containers:
- name: foo # hell ow
image: foo:bar
`,
expected: `
kind: Deployment
# {"$ref":"#/definitions/io.k8s.api.core.v1.PodSpec"}
spec:
containers:
- name: foo # hell ow
image: foo:bar
command: ['run2.sh']
`,
noInfer: true,
},
}

View File

@@ -9,224 +9,226 @@ var listTestCases = []testCase{
//
// Test Case
//
{`Replace list`,
`
{description: `Replace list`,
origin: `
list:
- 1
- 2
- 3`,
`
update: `
list:
- 2
- 3
- 4`,
`
local: `
list:
- 1
- 2
- 3`,
`
expected: `
list:
- 2
- 3
- 4`, nil},
- 4`},
//
// Test Case
//
{`Add an updated list`,
`
{description: `Add an updated list`,
origin: `
apiVersion: apps/v1
list: # old value
- 1
- 2
- 3
`,
`
update: `
apiVersion: apps/v1
list: # new value
- 2
- 3
- 4
`,
`
local: `
apiVersion: apps/v1`,
`
expected: `
apiVersion: apps/v1
list:
- 2
- 3
- 4
`, nil},
`},
//
// Test Case
//
{`Add keep an omitted field`,
`
{description: `Add keep an omitted field`,
origin: `
apiVersion: apps/v1
kind: Deployment`,
`
update: `
apiVersion: apps/v1
kind: StatefulSet`,
`
local: `
apiVersion: apps/v1
list: # not present in sources
- 2
- 3
- 4
`,
`
expected: `
apiVersion: apps/v1
list: # not present in sources
- 2
- 3
- 4
kind: StatefulSet
`, nil},
`},
//
// Test Case
//
// TODO(#36): consider making this an error
{`Change an updated field`,
`
{description: `Change an updated field`,
origin: `
apiVersion: apps/v1
list: # old value
- 1
- 2
- 3`,
`
update: `
apiVersion: apps/v1
list: # new value
- 2
- 3
- 4`,
`
local: `
apiVersion: apps/v1
list: # conflicting value
- a
- b
- c`,
`
expected: `
apiVersion: apps/v1
list: # conflicting value
- 2
- 3
- 4
`, nil},
`},
//
// Test Case
//
{`Ignore a field -- set`,
`
{description: `Ignore a field -- set`,
origin: `
apiVersion: apps/v1
list: # ignore value
- 1
- 2
- 3
`,
`
update: `
apiVersion: apps/v1
list: # ignore value
- 1
- 2
- 3`, `
- 3`,
local: `
apiVersion: apps/v1
list:
- 2
- 3
- 4
`, `
`,
expected: `
apiVersion: apps/v1
list:
- 2
- 3
- 4
`, nil},
`},
//
// Test Case
//
{`Ignore a field -- empty`,
`
{description: `Ignore a field -- empty`,
origin: `
apiVersion: apps/v1
list: # ignore value
- 1
- 2
- 3`,
`
update: `
apiVersion: apps/v1
list: # ignore value
- 1
- 2
- 3`,
`
local: `
apiVersion: apps/v1
`,
`
expected: `
apiVersion: apps/v1
`, nil},
`},
//
// Test Case
//
{`Explicitly clear a field`,
`
{description: `Explicitly clear a field`,
origin: `
apiVersion: apps/v1`,
`
update: `
apiVersion: apps/v1
list: null # clear`,
`
local: `
apiVersion: apps/v1
list: # value to clear
- 1
- 2
- 3`,
`
apiVersion: apps/v1`, nil},
expected: `
apiVersion: apps/v1`},
//
// Test Case
//
{`Implicitly clear a field`,
`
{description: `Implicitly clear a field`,
origin: `
apiVersion: apps/v1
list: # clear value
- 1
- 2
- 3`,
`
update: `
apiVersion: apps/v1`,
`
local: `
apiVersion: apps/v1
list: # old value
- 1
- 2
- 3`,
`
apiVersion: apps/v1`, nil},
expected: `
apiVersion: apps/v1`},
//
// Test Case
//
// TODO(#36): consider making this an error
{`Implicitly clear a changed field`,
`
{description: `Implicitly clear a changed field`,
origin: `
apiVersion: apps/v1
list: # old value
- 1
- 2
- 3`,
`
update: `
apiVersion: apps/v1`,
`
local: `
apiVersion: apps/v1
list: # old value
- a
- b
- c`,
`
apiVersion: apps/v1`, nil},
expected: `
apiVersion: apps/v1`},
}

View File

@@ -7,267 +7,267 @@ var mapTestCases = []testCase{
//
// Test Case
//
{`Add the annotations map field`,
`
{description: `Add the annotations map field`,
origin: `
kind: Deployment`,
`
update: `
kind: Deployment
metadata:
annotations:
d: e # add these annotations
`,
`
local: `
kind: Deployment`,
`
expected: `
kind: Deployment
metadata:
annotations:
d: e # add these annotations`, nil},
d: e # add these annotations`},
//
// Test Case
//
{`Add an annotation to the field`,
`
{description: `Add an annotation to the field`,
origin: `
kind: Deployment
metadata:
annotations:
a: b`,
`
update: `
kind: Deployment
metadata:
annotations:
a: b
d: e # add these annotations`,
`
local: `
kind: Deployment
metadata:
annotations:
g: h # keep these annotations`,
`
expected: `
kind: Deployment
metadata:
annotations:
g: h # keep these annotations
d: e # add these annotations`, nil},
d: e # add these annotations`},
//
// Test Case
//
{`Add an annotation to the field, field missing from dest`,
`
{description: `Add an annotation to the field, field missing from dest`,
origin: `
kind: Deployment
metadata:
annotations:
a: b # ignored because unchanged`,
`
update: `
kind: Deployment
metadata:
annotations:
a: b # ignore because unchanged
d: e`,
`
local: `
kind: Deployment`,
`
expected: `
kind: Deployment
metadata:
annotations:
d: e`, nil},
d: e`},
//
// Test Case
//
{`Update an annotation on the field, field messing rom the dest`,
`
{description: `Update an annotation on the field, field messing rom the dest`,
origin: `
kind: Deployment
metadata:
annotations:
a: b
d: c`,
`
update: `
kind: Deployment
metadata:
annotations:
a: b
d: e # set these annotations`,
`
local: `
kind: Deployment
metadata:
annotations:
g: h # keep these annotations`,
`
expected: `
kind: Deployment
metadata:
annotations:
g: h # keep these annotations
d: e # set these annotations`, nil},
d: e # set these annotations`},
//
// Test Case
//
{`Add an annotation to the field, field missing from dest`,
`
{description: `Add an annotation to the field, field missing from dest`,
origin: `
kind: Deployment
metadata:
annotations:
a: b # ignored because unchanged`,
`
update: `
kind: Deployment
metadata:
annotations:
a: b # ignore because unchanged
d: e`,
`
local: `
kind: Deployment`,
`
expected: `
kind: Deployment
metadata:
annotations:
d: e`, nil},
d: e`},
//
// Test Case
//
{`Remove an annotation`,
`
{description: `Remove an annotation`,
origin: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
a: b`,
`
update: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations: {}`,
`
local: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
c: d
a: b`,
`
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
c: d`, nil},
c: d`},
//
// Test Case
//
// TODO(#36) support ~annotations~: {} deletion
{`Specify a field as empty that isn't present in the source`,
`
{description: `Specify a field as empty that isn't present in the source`,
origin: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo`,
`
update: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
annotations: null`,
`
local: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
annotations:
a: b`,
`
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo`, nil},
name: foo`},
//
// Test Case
//
{`Remove an annotation`,
`
{description: `Remove an annotation`,
origin: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
a: b`,
`
update: `
apiVersion: apps/v1
kind: Deployment`,
`
local: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
c: d
a: b`,
`
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
c: d`, nil},
c: d`},
//
// Test Case
//
{`Remove annotations field`,
`
{description: `Remove annotations field`,
origin: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
a: b`,
`
update: `
apiVersion: apps/v1
kind: Deployment`,
`
local: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo`,
`
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
`, nil},
`},
//
// Test Case
//
{`Remove annotations field, but keep in dest`,
`
{description: `Remove annotations field, but keep in dest`,
origin: `
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
a: b`,
`
update: `
apiVersion: apps/v1
kind: Deployment`,
`
local: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
annotations:
foo: bar # keep this annotation even though the parent field was removed`,
`
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
annotations:
foo: bar # keep this annotation even though the parent field was removed`, nil},
foo: bar # keep this annotation even though the parent field was removed`},
//
// Test Case
//
{`Remove annotations, but they are already empty`,
`
{description: `Remove annotations, but they are already empty`,
origin: `
apiVersion: apps/v1
kind: Deployment
metadata:
@@ -275,24 +275,24 @@ metadata:
annotations:
a: b
`,
`
update: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
`,
`
local: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
annotations: {}
`,
`
expected: `
apiVersion: apps/v1
kind: Deployment
metadata:
name: foo
annotations: {}
`, nil},
`},
}

View File

@@ -13,11 +13,12 @@ import (
func Merge(dest, original, update *yaml.RNode) (*yaml.RNode, error) {
// if update == nil && original != nil => declarative deletion
return walk.Walker{Visitor: Visitor{},
return walk.Walker{
Visitor: Visitor{},
Sources: []*yaml.RNode{dest, original, update}}.Walk()
}
func MergeStrings(dest, original, update string) (string, error) {
func MergeStrings(dest, original, update string, infer bool) (string, error) {
srcOriginal, err := yaml.Parse(original)
if err != nil {
return "", err
@@ -31,7 +32,10 @@ func MergeStrings(dest, original, update string) (string, error) {
return "", err
}
result, err := Merge(d, srcOriginal, srcUpdated)
result, err := walk.Walker{
InferAssociativeLists: infer,
Visitor: Visitor{},
Sources: []*yaml.RNode{d, srcOriginal, srcUpdated}}.Walk()
if err != nil {
return "", err
}

View File

@@ -15,24 +15,27 @@ var testCases = [][]testCase{scalarTestCases, listTestCases, mapTestCases, eleme
func TestMerge(t *testing.T) {
for i := range testCases {
for _, tc := range testCases[i] {
actual, err := MergeStrings(tc.local, tc.origin, tc.update)
if tc.err == nil {
if !assert.NoError(t, err, tc.description) {
t.FailNow()
for j := range testCases[i] {
tc := testCases[i][j]
t.Run(tc.description, func(t *testing.T) {
actual, err := MergeStrings(tc.local, tc.origin, tc.update, !tc.noInfer)
if tc.err == nil {
if !assert.NoError(t, err, tc.description) {
t.FailNow()
}
if !assert.Equal(t,
strings.TrimSpace(tc.expected), strings.TrimSpace(actual), tc.description) {
t.FailNow()
}
} else {
if !assert.Errorf(t, err, tc.description) {
t.FailNow()
}
if !assert.Contains(t, tc.err.Error(), err.Error()) {
t.FailNow()
}
}
if !assert.Equal(t,
strings.TrimSpace(tc.expected), strings.TrimSpace(actual), tc.description) {
t.FailNow()
}
} else {
if !assert.Errorf(t, err, tc.description) {
t.FailNow()
}
if !assert.Contains(t, tc.err.Error(), err.Error()) {
t.FailNow()
}
}
})
}
}
}
@@ -44,4 +47,5 @@ type testCase struct {
local string
expected string
err error
noInfer bool
}

View File

@@ -8,128 +8,128 @@ var scalarTestCases = []testCase{
//
// Test Case
//
{`Set and updated a field`,
`kind: Deployment`,
`kind: StatefulSet`,
`kind: Deployment`,
`kind: StatefulSet`, nil},
{description: `Set and updated a field`,
origin: `kind: Deployment`,
update: `kind: StatefulSet`,
local: `kind: Deployment`,
expected: `kind: StatefulSet`},
{`Add an updated field`,
`
{description: `Add an updated field`,
origin: `
apiVersion: apps/v1
kind: Deployment # old value`,
`
update: `
apiVersion: apps/v1
kind: StatefulSet # new value`,
`
local: `
apiVersion: apps/v1`,
`
expected: `
apiVersion: apps/v1
kind: StatefulSet # new value`, nil},
kind: StatefulSet # new value`},
{`Add keep an omitted field`,
`
{description: `Add keep an omitted field`,
origin: `
apiVersion: apps/v1
kind: Deployment`,
`
update: `
apiVersion: apps/v1
kind: StatefulSet`,
`
local: `
apiVersion: apps/v1
spec: foo # field not present in source
`,
`
expected: `
apiVersion: apps/v1
spec: foo # field not present in source
kind: StatefulSet
`, nil},
`},
//
// Test Case
//
// TODO(#36): consider making this an error
{`Change an updated field`,
`
{description: `Change an updated field`,
origin: `
apiVersion: apps/v1
kind: Deployment # old value`,
`
update: `
apiVersion: apps/v1
kind: StatefulSet # new value`,
`
local: `
apiVersion: apps/v1
kind: Service # conflicting value`,
`
expected: `
apiVersion: apps/v1
kind: StatefulSet # new value`, nil},
kind: StatefulSet # new value`},
{`Ignore a field`,
`
{description: `Ignore a field`,
origin: `
apiVersion: apps/v1
kind: Deployment # ignore this field`,
`
update: `
apiVersion: apps/v1
kind: Deployment # ignore this field`,
`
local: `
apiVersion: apps/v1`,
`
apiVersion: apps/v1`, nil},
expected: `
apiVersion: apps/v1`},
{`Explicitly clear a field`,
`
{description: `Explicitly clear a field`,
origin: `
apiVersion: apps/v1`,
`
update: `
apiVersion: apps/v1
kind: null # clear this value`,
`
local: `
apiVersion: apps/v1
kind: Deployment # value to be cleared`,
`
apiVersion: apps/v1`, nil},
expected: `
apiVersion: apps/v1`},
{`Implicitly clear a field`,
`
{description: `Implicitly clear a field`,
origin: `
apiVersion: apps/v1
kind: Deployment # clear this field`,
`
update: `
apiVersion: apps/v1`,
`
local: `
apiVersion: apps/v1
kind: Deployment # clear this field`,
`
apiVersion: apps/v1`, nil},
expected: `
apiVersion: apps/v1`},
//
// Test Case
//
// TODO(#36): consider making this an error
{`Implicitly clear a changed field`,
`
{description: `Implicitly clear a changed field`,
origin: `
apiVersion: apps/v1
kind: Deployment`,
`
update: `
apiVersion: apps/v1`,
`
local: `
apiVersion: apps/v1
kind: StatefulSet`,
`
apiVersion: apps/v1`, nil},
expected: `
apiVersion: apps/v1`},
//
// Test Case
//
{`Merge an empty scalar value`,
`
{description: `Merge an empty scalar value`,
origin: `
apiVersion: apps/v1
`,
`
update: `
apiVersion: apps/v1
kind: {}
`,
`
local: `
apiVersion: apps/v1
`,
`
expected: `
apiVersion: apps/v1
kind: {}
`, nil},
`},
}

View File

@@ -4,6 +4,7 @@
package merge3
import (
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/walk"
)
@@ -17,7 +18,7 @@ const (
type Visitor struct{}
func (m Visitor) VisitMap(nodes walk.Sources) (*yaml.RNode, error) {
func (m Visitor) VisitMap(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) {
if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) {
// explicitly cleared from either dest or update
return walk.ClearNode, nil
@@ -36,7 +37,7 @@ func (m Visitor) VisitMap(nodes walk.Sources) (*yaml.RNode, error) {
return nodes.Dest(), nil
}
func (m Visitor) visitAList(nodes walk.Sources) (*yaml.RNode, error) {
func (m Visitor) visitAList(nodes walk.Sources, _ *openapi.ResourceSchema) (*yaml.RNode, error) {
if yaml.IsEmpty(nodes.Updated()) && !yaml.IsEmpty(nodes.Origin()) {
// implicitly cleared from update -- element was deleted
return walk.ClearNode, nil
@@ -51,7 +52,7 @@ func (m Visitor) visitAList(nodes walk.Sources) (*yaml.RNode, error) {
return nodes.Dest(), nil
}
func (m Visitor) VisitScalar(nodes walk.Sources) (*yaml.RNode, error) {
func (m Visitor) VisitScalar(nodes walk.Sources, s *openapi.ResourceSchema) (*yaml.RNode, error) {
if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) {
// explicitly cleared from either dest or update
return nil, nil
@@ -103,9 +104,9 @@ func (m Visitor) visitNAList(nodes walk.Sources) (*yaml.RNode, error) {
return nodes.Dest(), nil
}
func (m Visitor) VisitList(nodes walk.Sources, kind walk.ListKind) (*yaml.RNode, error) {
func (m Visitor) VisitList(nodes walk.Sources, s *openapi.ResourceSchema, kind walk.ListKind) (*yaml.RNode, error) {
if kind == walk.AssociativeList {
return m.visitAList(nodes)
return m.visitAList(nodes, s)
}
// non-associative list
return m.visitNAList(nodes)