mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-06-12 01:14:22 +00:00
kyaml: initial support for yaml and resource manipulation
This commit is contained in:
422
kyaml/yaml/merge3/element_test.go
Normal file
422
kyaml/yaml/merge3/element_test.go
Normal file
@@ -0,0 +1,422 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package merge3_test
|
||||
|
||||
var elementTestCases = []testCase{
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Add an element to an existing list`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:1
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:1
|
||||
- name: baz
|
||||
image: baz:2
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:1
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:1
|
||||
- image: baz:2
|
||||
name: baz
|
||||
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Add an element to a non-existing list`,
|
||||
`
|
||||
kind: Deployment`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- image: foo:bar
|
||||
name: foo
|
||||
`, nil},
|
||||
|
||||
{`Add an element to a non-existing list, existing in dest`,
|
||||
`
|
||||
kind: Deployment`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: baz
|
||||
image: baz:bar
|
||||
`,
|
||||
`
|
||||
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`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command:
|
||||
- run.sh
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- command:
|
||||
- run.sh
|
||||
name: foo
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Update a field on the elem, element missing from the dest`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command:
|
||||
- run.sh
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command:
|
||||
- run2.sh
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- command:
|
||||
- run2.sh
|
||||
name: foo
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Update a field on the elem, element present in the dest`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run.sh']
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run2.sh']
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run.sh']
|
||||
`,
|
||||
`
|
||||
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`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run2.sh']
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
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`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run2.sh']
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run.sh']
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command:
|
||||
- run2.sh
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Ignore an element`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers: {}
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers: {}
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Leave deleted`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove an element -- matching`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove an element -- field missing from update`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run.sh']
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove an element -- element missing`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
- name: baz
|
||||
image: baz:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run.sh']
|
||||
- name: baz
|
||||
image: baz:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command:
|
||||
- run.sh
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove an element -- empty containers`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers: {}
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run.sh']
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove an element -- missing list field`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
containers:
|
||||
- name: foo
|
||||
image: foo:bar
|
||||
command: ['run.sh']
|
||||
`,
|
||||
`
|
||||
kind: Deployment
|
||||
`, nil},
|
||||
}
|
||||
232
kyaml/yaml/merge3/list_test.go
Normal file
232
kyaml/yaml/merge3/list_test.go
Normal file
@@ -0,0 +1,232 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package merge3_test
|
||||
|
||||
var listTestCases = []testCase{
|
||||
// List Field Test Cases
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Replace list`,
|
||||
`
|
||||
list:
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
list:
|
||||
- 2
|
||||
- 3
|
||||
- 4`,
|
||||
`
|
||||
list:
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
list:
|
||||
- 2
|
||||
- 3
|
||||
- 4`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Add an updated list`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # old value
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # new value
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list:
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Add keep an omitted field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # not present in sources
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
`,
|
||||
`
|
||||
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`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # old value
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # new value
|
||||
- 2
|
||||
- 3
|
||||
- 4`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # conflicting value
|
||||
- a
|
||||
- b
|
||||
- c`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # conflicting value
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Ignore a field -- set`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # ignore value
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # ignore value
|
||||
- 1
|
||||
- 2
|
||||
- 3`, `
|
||||
apiVersion: apps/v1
|
||||
list:
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
`, `
|
||||
apiVersion: apps/v1
|
||||
list:
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Ignore a field -- empty`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # ignore value
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # ignore value
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Explicitly clear a field`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: null # clear`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # value to clear
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
apiVersion: apps/v1`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Implicitly clear a field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # clear value
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # old value
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
apiVersion: apps/v1`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
// TODO(#36): consider making this an error
|
||||
{`Implicitly clear a changed field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # old value
|
||||
- 1
|
||||
- 2
|
||||
- 3`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
list: # old value
|
||||
- a
|
||||
- b
|
||||
- c`,
|
||||
`
|
||||
apiVersion: apps/v1`, nil},
|
||||
}
|
||||
298
kyaml/yaml/merge3/map_test.go
Normal file
298
kyaml/yaml/merge3/map_test.go
Normal file
@@ -0,0 +1,298 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package merge3_test
|
||||
|
||||
var mapTestCases = []testCase{
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Add the annotations map field`,
|
||||
`
|
||||
kind: Deployment`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
d: e # add these annotations
|
||||
`,
|
||||
`
|
||||
kind: Deployment`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
d: e # add these annotations`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Add an annotation to the field`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b
|
||||
d: e # add these annotations`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
g: h # keep these annotations`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
g: h # keep these annotations
|
||||
d: e # add these annotations`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Add an annotation to the field, field missing from dest`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b # ignored because unchanged`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b # ignore because unchanged
|
||||
d: e`,
|
||||
`
|
||||
kind: Deployment`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
d: e`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Update an annotation on the field, field messing rom the dest`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b
|
||||
d: c`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b
|
||||
d: e # set these annotations`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
g: h # keep these annotations`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
g: h # keep these annotations
|
||||
d: e # set these annotations`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Add an annotation to the field, field missing from dest`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b # ignored because unchanged`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b # ignore because unchanged
|
||||
d: e`,
|
||||
`
|
||||
kind: Deployment`,
|
||||
`
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
d: e`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove an annotation`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations: {}`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
c: d
|
||||
a: b`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
c: d`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
// TODO(#36) support ~annotations~: {} deletion
|
||||
{`Specify a field as empty that isn't present in the source`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
annotations: null`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
a: b`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove an annotation`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
c: d
|
||||
a: b`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
c: d`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove annotations field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove annotations field, but keep in dest`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
a: b`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
foo: bar # keep this annotation even though the parent field was removed`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
foo: bar # keep this annotation even though the parent field was removed`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Remove annotations, but they are already empty`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
annotations:
|
||||
a: b
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
annotations: {}
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: foo
|
||||
annotations: {}
|
||||
`, nil},
|
||||
}
|
||||
88
kyaml/yaml/merge3/merge3.go
Normal file
88
kyaml/yaml/merge3/merge3.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Package merge contains libraries for merging fields from one RNode to another
|
||||
// RNode
|
||||
package merge3
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml/walk"
|
||||
)
|
||||
|
||||
const Help = `
|
||||
Description:
|
||||
|
||||
merge3 identifies changes between an original source + updated source and merges the result
|
||||
into a destination, overriding the destination fields where they have changed between
|
||||
original and updated.
|
||||
|
||||
### Resource MergeRules
|
||||
|
||||
- Resources present in the original and deleted from the update are deleted.
|
||||
- Resources missing from the original and added in the update are added.
|
||||
- Resources present only in the dest are kept without changes.
|
||||
- Resources present in both the update and the dest are merged *original + update + dest => dest*.
|
||||
|
||||
### Field Merge Rules
|
||||
|
||||
Fields are recursively merged using the following rules:
|
||||
|
||||
- scalars
|
||||
- if present in either dest or updated and 'null', clear the value
|
||||
- if unchanged between original and updated, keep dest value
|
||||
- if changed between original and updated (added, deleted, changed), take the updated value
|
||||
|
||||
- non-associative lists -- lists without a merge key
|
||||
- if present in either dest or updated and 'null', clear the value
|
||||
- if unchanged between original and updated, keep dest value
|
||||
- if changed between original and updated (added, deleted, changed), take the updated value
|
||||
|
||||
- map keys and fields -- paired by the map-key / field-name
|
||||
- if present in either dest or updated and 'null', clear the value
|
||||
- if present only in the dest, it keeps its value
|
||||
- if not-present in the dest, add the delta between original-updated as a field
|
||||
- otherwise recursively merge the value between original, updated, dest
|
||||
|
||||
- associative list elements -- paired by the associative key
|
||||
- if present only in the dest, it keeps its value
|
||||
- if not-present in the dest, add the delta between original-updated as a field
|
||||
- otherwise recursively merge the value between original, updated, dest
|
||||
|
||||
### Associative Keys
|
||||
|
||||
Associative keys are used to identify "same" elements within 2 different lists, and merge them.
|
||||
The following fields are recognized as associative keys:
|
||||
|
||||
` + "[`mountPath`, `devicePath`, `ip`, `type`, `topologyKey`, `name`, `containerPort`]" + `
|
||||
|
||||
Any lists where all of the elements contain associative keys will be merged as associative lists.
|
||||
`
|
||||
|
||||
func Merge(dest, original, update *yaml.RNode) (*yaml.RNode, error) {
|
||||
// if update == nil && original != nil => declarative deletion
|
||||
|
||||
return walk.Walker{Visitor: Visitor{},
|
||||
Sources: []*yaml.RNode{dest, original, update}}.Walk()
|
||||
}
|
||||
|
||||
func MergeStrings(dest, original, update string) (string, error) {
|
||||
srcOriginal, err := yaml.Parse(original)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
srcUpdated, err := yaml.Parse(update)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
d, err := yaml.Parse(dest)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result, err := Merge(d, srcOriginal, srcUpdated)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
47
kyaml/yaml/merge3/merge3_test.go
Normal file
47
kyaml/yaml/merge3/merge3_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package merge3_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
. "sigs.k8s.io/kustomize/kyaml/yaml/merge3"
|
||||
)
|
||||
|
||||
var testCases = [][]testCase{scalarTestCases, listTestCases, mapTestCases, elementTestCases}
|
||||
|
||||
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()
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
description string
|
||||
origin string
|
||||
update string
|
||||
local string
|
||||
expected string
|
||||
err error
|
||||
}
|
||||
135
kyaml/yaml/merge3/scalar_test.go
Normal file
135
kyaml/yaml/merge3/scalar_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package merge3_test
|
||||
|
||||
var scalarTestCases = []testCase{
|
||||
// Scalar Field Test Cases
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Set and updated a field`,
|
||||
`kind: Deployment`,
|
||||
`kind: StatefulSet`,
|
||||
`kind: Deployment`,
|
||||
`kind: StatefulSet`, nil},
|
||||
|
||||
{`Add an updated field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment # old value`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet # new value`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet # new value`, nil},
|
||||
|
||||
{`Add keep an omitted field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
spec: foo # field not present in source
|
||||
`,
|
||||
`
|
||||
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`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment # old value`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet # new value`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Service # conflicting value`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet # new value`, nil},
|
||||
|
||||
{`Ignore a field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment # ignore this field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment # ignore this field`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1`, nil},
|
||||
|
||||
{`Explicitly clear a field`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: null # clear this value`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment # value to be cleared`,
|
||||
`
|
||||
apiVersion: apps/v1`, nil},
|
||||
|
||||
{`Implicitly clear a field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment # clear this field`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment # clear this field`,
|
||||
`
|
||||
apiVersion: apps/v1`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
// TODO(#36): consider making this an error
|
||||
{`Implicitly clear a changed field`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment`,
|
||||
`
|
||||
apiVersion: apps/v1`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet`,
|
||||
`
|
||||
apiVersion: apps/v1`, nil},
|
||||
|
||||
//
|
||||
// Test Case
|
||||
//
|
||||
{`Merge an empty scalar value`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: {}
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
`,
|
||||
`
|
||||
apiVersion: apps/v1
|
||||
kind: {}
|
||||
`, nil},
|
||||
}
|
||||
161
kyaml/yaml/merge3/visitor.go
Normal file
161
kyaml/yaml/merge3/visitor.go
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package merge3
|
||||
|
||||
import (
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
"sigs.k8s.io/kustomize/kyaml/yaml/walk"
|
||||
)
|
||||
|
||||
type ConflictStrategy uint
|
||||
|
||||
const (
|
||||
// TODO: Support more strategies
|
||||
TakeUpdate ConflictStrategy = 1 + iota
|
||||
)
|
||||
|
||||
type Visitor struct{}
|
||||
|
||||
func (m Visitor) VisitMap(nodes walk.Sources) (*yaml.RNode, error) {
|
||||
if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) {
|
||||
// explicitly cleared from either dest or update
|
||||
return walk.ClearNode, nil
|
||||
}
|
||||
if nodes.Dest() == nil && nodes.Updated() == nil {
|
||||
// implicitly cleared missing from both dest and update
|
||||
return walk.ClearNode, nil
|
||||
}
|
||||
|
||||
if nodes.Dest() == nil {
|
||||
// not cleared, but missing from the dest
|
||||
// initialize a new value that can be recursively merged
|
||||
return yaml.NewRNode(&yaml.Node{Kind: yaml.MappingNode}), nil
|
||||
}
|
||||
// recursively merge the dest with the original and updated
|
||||
return nodes.Dest(), nil
|
||||
}
|
||||
|
||||
func (m Visitor) visitAList(nodes walk.Sources) (*yaml.RNode, error) {
|
||||
if yaml.IsEmpty(nodes.Updated()) && !yaml.IsEmpty(nodes.Origin()) {
|
||||
// implicitly cleared from update -- element was deleted
|
||||
return walk.ClearNode, nil
|
||||
}
|
||||
if yaml.IsEmpty(nodes.Dest()) {
|
||||
// not cleared, but missing from the dest
|
||||
// initialize a new value that can be recursively merged
|
||||
return yaml.NewRNode(&yaml.Node{Kind: yaml.SequenceNode}), nil
|
||||
}
|
||||
|
||||
// recursively merge the dest with the original and updated
|
||||
return nodes.Dest(), nil
|
||||
}
|
||||
|
||||
func (m Visitor) VisitScalar(nodes walk.Sources) (*yaml.RNode, error) {
|
||||
if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) {
|
||||
// explicitly cleared from either dest or update
|
||||
return nil, nil
|
||||
}
|
||||
if yaml.IsEmpty(nodes.Updated()) != yaml.IsEmpty(nodes.Origin()) {
|
||||
// value added or removed in update
|
||||
return nodes.Updated(), nil
|
||||
}
|
||||
if yaml.IsEmpty(nodes.Updated()) && yaml.IsEmpty(nodes.Origin()) {
|
||||
// value added or removed in update
|
||||
return nodes.Dest(), nil
|
||||
}
|
||||
|
||||
if nodes.Updated().YNode().Value != nodes.Origin().YNode().Value {
|
||||
// value changed in update
|
||||
return nodes.Updated(), nil
|
||||
}
|
||||
|
||||
// unchanged between origin and update, keep the dest
|
||||
return nodes.Dest(), nil
|
||||
}
|
||||
|
||||
func (m Visitor) visitNAList(nodes walk.Sources) (*yaml.RNode, error) {
|
||||
if yaml.IsNull(nodes.Updated()) || yaml.IsNull(nodes.Dest()) {
|
||||
// explicitly cleared from either dest or update
|
||||
return walk.ClearNode, nil
|
||||
}
|
||||
|
||||
if yaml.IsEmpty(nodes.Updated()) != yaml.IsEmpty(nodes.Origin()) {
|
||||
// value added or removed in update
|
||||
return nodes.Updated(), nil
|
||||
}
|
||||
if yaml.IsEmpty(nodes.Updated()) && yaml.IsEmpty(nodes.Origin()) {
|
||||
// value not present in source or dest
|
||||
return nodes.Dest(), nil
|
||||
}
|
||||
|
||||
// compare origin and update values to see if they have changed
|
||||
values, err := m.getStrValues(nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if values.Update != values.Origin {
|
||||
// value changed in update
|
||||
return nodes.Updated(), nil
|
||||
}
|
||||
|
||||
// unchanged between origin and update, keep the dest
|
||||
return nodes.Dest(), nil
|
||||
}
|
||||
|
||||
func (m Visitor) VisitList(nodes walk.Sources, kind walk.ListKind) (*yaml.RNode, error) {
|
||||
if kind == walk.AssociativeList {
|
||||
return m.visitAList(nodes)
|
||||
}
|
||||
// non-associative list
|
||||
return m.visitNAList(nodes)
|
||||
}
|
||||
|
||||
func (m Visitor) getStrValues(nodes walk.Sources) (strValues, error) {
|
||||
var uStr, oStr, dStr string
|
||||
var err error
|
||||
if nodes.Updated() != nil && nodes.Updated().YNode() != nil {
|
||||
s := nodes.Updated().YNode().Style
|
||||
defer func() {
|
||||
nodes.Updated().YNode().Style = s
|
||||
}()
|
||||
nodes.Updated().YNode().Style = yaml.FlowStyle | yaml.SingleQuotedStyle
|
||||
uStr, err = nodes.Updated().String()
|
||||
if err != nil {
|
||||
return strValues{}, err
|
||||
}
|
||||
}
|
||||
if nodes.Origin() != nil && nodes.Origin().YNode() != nil {
|
||||
s := nodes.Origin().YNode().Style
|
||||
defer func() {
|
||||
nodes.Origin().YNode().Style = s
|
||||
}()
|
||||
nodes.Origin().YNode().Style = yaml.FlowStyle | yaml.SingleQuotedStyle
|
||||
oStr, err = nodes.Origin().String()
|
||||
if err != nil {
|
||||
return strValues{}, err
|
||||
}
|
||||
|
||||
}
|
||||
if nodes.Dest() != nil && nodes.Dest().YNode() != nil {
|
||||
s := nodes.Dest().YNode().Style
|
||||
defer func() {
|
||||
nodes.Dest().YNode().Style = s
|
||||
}()
|
||||
nodes.Dest().YNode().Style = yaml.FlowStyle | yaml.SingleQuotedStyle
|
||||
dStr, err = nodes.Dest().String()
|
||||
if err != nil {
|
||||
return strValues{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return strValues{Origin: oStr, Update: uStr, Dest: dStr}, nil
|
||||
}
|
||||
|
||||
type strValues struct {
|
||||
Origin string
|
||||
Update string
|
||||
Dest string
|
||||
}
|
||||
|
||||
var _ walk.Visitor = Visitor{}
|
||||
Reference in New Issue
Block a user