blob: 3c47b204bf4b182077abcdf842c13793adf75576 [file] [log] [blame]
/*
Copyright 2019 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package merge_test
import (
"fmt"
"strconv"
"strings"
"testing"
"gopkg.in/yaml.v2"
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
. "sigs.k8s.io/structured-merge-diff/v4/internal/fixture"
"sigs.k8s.io/structured-merge-diff/v4/merge"
"sigs.k8s.io/structured-merge-diff/v4/typed"
"sigs.k8s.io/structured-merge-diff/v4/value"
)
func TestMultipleAppliersSet(t *testing.T) {
tests := map[string]TestCase{
"remove_one": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
list:
- name: a
- name: b
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v2",
Object: `
list:
- name: c
`,
},
Apply{
Manager: "apply-one",
APIVersion: "v3",
Object: `
list:
- name: a
`,
},
},
Object: `
list:
- name: a
- name: c
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", "a")),
_P("list", _KBF("name", "a"), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", "c")),
_P("list", _KBF("name", "c"), "name"),
),
"v2",
false,
),
},
},
"same_value_no_conflict": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
list:
- name: a
value: 0
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v2",
Object: `
list:
- name: a
value: 0
`,
},
},
Object: `
list:
- name: a
value: 0
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", "a")),
_P("list", _KBF("name", "a"), "name"),
_P("list", _KBF("name", "a"), "value"),
),
"v1",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", "a")),
_P("list", _KBF("name", "a"), "name"),
_P("list", _KBF("name", "a"), "value"),
),
"v2",
false,
),
},
},
"change_value_yes_conflict": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
list:
- name: a
value: 0
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v2",
Object: `
list:
- name: a
value: 1
`,
Conflicts: merge.Conflicts{
merge.Conflict{Manager: "apply-one", Path: _P("list", _KBF("name", "a"), "value")},
},
},
},
Object: `
list:
- name: a
value: 0
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", "a")),
_P("list", _KBF("name", "a"), "name"),
_P("list", _KBF("name", "a"), "value"),
),
"v1",
false,
),
},
},
"remove_one_keep_one": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
list:
- name: a
- name: b
- name: c
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v2",
Object: `
list:
- name: c
- name: d
`,
},
Apply{
Manager: "apply-one",
APIVersion: "v3",
Object: `
list:
- name: a
`,
},
},
Object: `
list:
- name: a
- name: c
- name: d
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", "a")),
_P("list", _KBF("name", "a"), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("list", _KBF("name", "c")),
_P("list", _KBF("name", "d")),
_P("list", _KBF("name", "c"), "name"),
_P("list", _KBF("name", "d"), "name"),
),
"v2",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.Test(associativeListParser); err != nil {
t.Fatal(err)
}
})
}
}
var structMultiversionParser = func() Parser {
parser, err := typed.NewParser(`types:
- name: v1
map:
fields:
- name: struct
type:
namedType: struct
- name: version
type:
scalar: string
- name: struct
map:
fields:
- name: name
type:
scalar: string
- name: scalarField_v1
type:
scalar: string
- name: complexField_v1
type:
namedType: complex
- name: complex
map:
fields:
- name: name
type:
scalar: string
- name: v2
map:
fields:
- name: struct
type:
namedType: struct_v2
- name: version
type:
scalar: string
- name: struct_v2
map:
fields:
- name: name
type:
scalar: string
- name: scalarField_v2
type:
scalar: string
- name: complexField_v2
type:
namedType: complex_v2
- name: complex_v2
map:
fields:
- name: name
type:
scalar: string
- name: v3
map:
fields:
- name: struct
type:
namedType: struct_v3
- name: version
type:
scalar: string
- name: struct_v3
map:
fields:
- name: name
type:
scalar: string
- name: scalarField_v3
type:
scalar: string
- name: complexField_v3
type:
namedType: complex_v3
- name: complex_v3
map:
fields:
- name: name
type:
scalar: string
`)
if err != nil {
panic(err)
}
return parser
}()
func TestMultipleAppliersFieldUnsetting(t *testing.T) {
versions := []fieldpath.APIVersion{"v1", "v2", "v3"}
for _, v1 := range versions {
for _, v2 := range versions {
for _, v3 := range versions {
t.Run(fmt.Sprintf("%s-%s-%s", v1, v2, v3), func(t *testing.T) {
testMultipleAppliersFieldUnsetting(t, v1, v2, v3)
})
}
}
}
}
func testMultipleAppliersFieldUnsetting(t *testing.T, v1, v2, v3 fieldpath.APIVersion) {
tests := map[string]TestCase{
"unset_scalar_sole_owner": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: v1,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: a
`, v1)),
},
Apply{
Manager: "apply-one",
APIVersion: v2,
Object: `
struct:
name: a
`,
},
},
Object: `
struct:
name: a
`,
APIVersion: v3,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("struct", "name"),
),
v2,
false,
),
},
},
"unset_scalar_shared_with_applier": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: v1,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: a
`, v1)),
},
Apply{
Manager: "apply-two",
APIVersion: v2,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
scalarField_%s: a
`, v2)),
},
Apply{
Manager: "apply-one",
APIVersion: v3,
Object: `
struct:
name: a
`,
},
},
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: a
`, v3)),
APIVersion: v3,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("struct", "name"),
),
v3,
true,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("struct", fmt.Sprintf("scalarField_%s", v2)),
),
v2,
false,
),
},
},
"unset_scalar_shared_with_updater": {
Ops: []Operation{
Update{
Manager: "updater",
APIVersion: v1,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: a
`, v1)),
},
Apply{
Manager: "applier",
APIVersion: v2,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: a
`, v2)),
},
Apply{
Manager: "applier",
APIVersion: v3,
Object: `
struct:
name: a
`,
},
},
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: a
`, v3)),
APIVersion: v3,
Managed: fieldpath.ManagedFields{
"updater": fieldpath.NewVersionedSet(
_NS(
_P("struct"),
_P("struct", "name"),
_P("struct", fmt.Sprintf("scalarField_%s", v1)),
),
v1,
false,
),
"applier": fieldpath.NewVersionedSet(
_NS(
_P("struct", "name"),
),
v3,
true,
),
},
},
"updater_claims_field": {
Ops: []Operation{
Apply{
Manager: "applier",
APIVersion: v1,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: a
`, v1)),
},
Update{
Manager: "updater",
APIVersion: v2,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: b
`, v2)),
},
},
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: b
`, v3)),
APIVersion: v3,
Managed: fieldpath.ManagedFields{
"updater": fieldpath.NewVersionedSet(
_NS(
_P("struct", fmt.Sprintf("scalarField_%s", v2)),
),
v2,
false,
),
"applier": fieldpath.NewVersionedSet(
_NS(
_P("struct", "name"),
),
v1,
true,
),
},
},
"unset_complex_sole_owner": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: v1,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
complexField_%s:
name: b
`, v1)),
},
Apply{
Manager: "apply-one",
APIVersion: v2,
Object: `
struct:
name: a
`,
},
},
Object: typed.YAMLObject(`
struct:
name: a
`),
APIVersion: v3,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("struct", "name"),
),
v2,
false,
),
},
},
"unset_complex_shared_with_applier": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: v1,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
complexField_%s:
name: b
`, v1)),
},
Apply{
Manager: "apply-two",
APIVersion: v2,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
complexField_%s:
name: b
`, v2)),
},
Apply{
Manager: "apply-one",
APIVersion: v3,
Object: `
struct:
name: a
`,
},
},
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
complexField_%s:
name: b
`, v3)),
APIVersion: v3,
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("struct", "name"),
),
v3,
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("struct", fmt.Sprintf("complexField_%s", v2), "name"),
),
v2,
false,
),
},
},
}
converter := renamingConverter{structMultiversionParser}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.TestWithConverter(structMultiversionParser, converter); err != nil {
t.Fatal(err)
}
})
}
}
func TestMultipleAppliersNestedType(t *testing.T) {
tests := map[string]TestCase{
"remove_one_keep_one_with_two_sub_items": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: b
value:
- d
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
- name: b
value:
- d
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "a")),
_P("listOfLists", _KBF("name", "a"), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "b")),
_P("listOfLists", _KBF("name", "b"), "name"),
_P("listOfLists", _KBF("name", "b"), "value", _V("d")),
),
"v2",
false,
),
},
},
"remove_one_keep_one_with_dangling_subitem": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: b
value:
- d
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
- d
- e
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
- name: b
value:
- d
- e
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "a")),
_P("listOfLists", _KBF("name", "a"), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "b")),
_P("listOfLists", _KBF("name", "b"), "name"),
_P("listOfLists", _KBF("name", "b"), "value", _V("d")),
),
"v2",
false,
),
"controller": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "b"), "value", _V("e")),
),
"v2",
false,
),
},
},
"remove_one_with_dangling_subitem_keep_one": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: a
value:
- b
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
listOfLists:
- name: a
value:
- b
- name: b
value:
- c
- d
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
value:
- b
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "a")),
_P("listOfLists", _KBF("name", "a"), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "a")),
_P("listOfLists", _KBF("name", "a"), "name"),
_P("listOfLists", _KBF("name", "a"), "value", _V("b")),
),
"v2",
false,
),
},
},
"remove_one_with_managed_subitem_keep_one": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: a
value:
- b
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
listOfLists:
- name: a
value:
- b
- name: b
value:
- c
- d
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
value:
- b
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "a")),
_P("listOfLists", _KBF("name", "a"), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "a")),
_P("listOfLists", _KBF("name", "a"), "name"),
_P("listOfLists", _KBF("name", "a"), "value", _V("b")),
),
"v2",
false,
),
},
},
"remove_one_keep_one_with_sub_item": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
- name: b
value:
- c
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
listOfLists:
- name: b
value:
- d
`,
APIVersion: "v2",
},
Apply{
Manager: "apply-one",
Object: `
listOfLists:
- name: a
`,
APIVersion: "v3",
},
},
Object: `
listOfLists:
- name: a
- name: b
value:
- d
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "a")),
_P("listOfLists", _KBF("name", "a"), "name"),
),
"v3",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("listOfLists", _KBF("name", "b")),
_P("listOfLists", _KBF("name", "b"), "name"),
_P("listOfLists", _KBF("name", "b"), "value", _V("d")),
),
"v2",
false,
),
},
},
"multiple_appliers_recursive_map": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
mapOfMapsRecursive:
a:
c:
d:
`,
APIVersion: "v2",
},
Update{
Manager: "controller-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
c:
d:
e:
`,
APIVersion: "v3",
},
Update{
Manager: "controller-two",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
c:
d:
e:
f:
`,
APIVersion: "v2",
},
Update{
Manager: "controller-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
e:
c:
d:
e:
f:
g:
`,
APIVersion: "v3",
},
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
`,
APIVersion: "v4",
},
},
Object: `
mapOfMapsRecursive:
a:
c:
d:
e:
f:
g:
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive"),
),
"v4",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "a"),
_P("mapOfMapsRecursive", "c"),
_P("mapOfMapsRecursive", "c", "d"),
),
"v2",
false,
),
"controller-one": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "c", "d", "e"),
_P("mapOfMapsRecursive", "c", "d", "e", "f", "g"),
),
"v3",
false,
),
"controller-two": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "c", "d", "e", "f"),
),
"v2",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.Test(nestedTypeParser); err != nil {
t.Fatal(err)
}
})
}
}
func TestMultipleAppliersDeducedType(t *testing.T) {
tests := map[string]TestCase{
"multiple_appliers_recursive_map_deduced": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
a:
b:
c:
d:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
a:
c:
d:
`,
APIVersion: "v2",
},
Update{
Manager: "controller-one",
Object: `
a:
b:
c:
c:
d:
e:
`,
APIVersion: "v3",
},
Update{
Manager: "controller-two",
Object: `
a:
b:
c:
d:
c:
d:
e:
f:
`,
APIVersion: "v2",
},
Update{
Manager: "controller-one",
Object: `
a:
b:
c:
d:
e:
c:
d:
e:
f:
g:
`,
APIVersion: "v3",
},
Apply{
Manager: "apply-one",
Object: ``,
APIVersion: "v4",
},
},
Object: `
a:
c:
d:
e:
f:
g:
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("a"),
_P("c"),
_P("c", "d"),
),
"v2",
false,
),
"controller-one": fieldpath.NewVersionedSet(
_NS(
_P("c", "d", "e"),
_P("c", "d", "e", "f", "g"),
),
"v3",
false,
),
"controller-two": fieldpath.NewVersionedSet(
_NS(
_P("c", "d", "e", "f"),
),
"v2",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.Test(DeducedParser); err != nil {
t.Fatal(err)
}
})
}
}
func TestMultipleAppliersRealConversion(t *testing.T) {
tests := map[string]TestCase{
"multiple_appliers_recursive_map_real_conversion": {
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
mapOfMapsRecursive:
aa:
cc:
dd:
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
mapOfMapsRecursive:
aaa:
bbb:
ccc:
ddd:
ccc:
ddd:
eee:
fff:
`,
APIVersion: "v3",
},
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
`,
APIVersion: "v4",
},
},
Object: `
mapOfMapsRecursive:
aaaa:
cccc:
dddd:
eeee:
ffff:
`,
APIVersion: "v4",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive"),
),
"v4",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "aa"),
_P("mapOfMapsRecursive", "cc"),
_P("mapOfMapsRecursive", "cc", "dd"),
),
"v2",
false,
),
"controller": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "ccc", "ddd", "eee"),
_P("mapOfMapsRecursive", "ccc", "ddd", "eee", "fff"),
),
"v3",
false,
),
},
},
"appliers_remove_from_controller_real_conversion": {
// Ensures that an applier can delete associative map items it created after a controller
// modifies them.
Ops: []Operation{
Apply{
Manager: "apply",
Object: `
mapOfMapsRecursive:
aaa:
bbb:
`,
APIVersion: "v3",
},
Update{
Manager: "controller",
Object: `
mapOfMapsRecursive:
a:
b:
c:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply",
Object: `
mapOfMapsRecursive:
aa:
bb:
cc:
dd:
`,
APIVersion: "v2",
},
Apply{
Manager: "apply",
Object: `
mapOfMapsRecursive:
aaa:
ccc:
`,
APIVersion: "v3",
},
},
Object: `
mapOfMapsRecursive:
aaa:
ccc:
`,
APIVersion: "v3",
Managed: fieldpath.ManagedFields{
"apply": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "aaa"),
_P("mapOfMapsRecursive", "ccc"),
),
"v3",
false,
),
},
},
"applier_updater_shared_ownership_real_conversion": {
// Ensures that when an updater creates maps that they are not deleted when
// an applier shares ownership in them and then later removes them from its applied
// configuration
Ops: []Operation{
Update{
Manager: "updater",
Object: `
mapOfMapsRecursive:
a:
b:
c:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply",
Object: `
mapOfMapsRecursive:
aa:
bb:
cc:
dd:
`,
APIVersion: "v2",
},
Apply{
Manager: "apply",
Object: `
mapOfMapsRecursive:
aaa:
ccc:
`,
APIVersion: "v3",
},
},
Object: `
mapOfMapsRecursive:
aaa:
bbb:
ccc:
ccc:
`,
APIVersion: "v3",
Managed: fieldpath.ManagedFields{
"updater": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive"),
_P("mapOfMapsRecursive", "a"),
_P("mapOfMapsRecursive", "a", "b"),
_P("mapOfMapsRecursive", "a", "b", "c"),
),
"v1",
false,
),
"apply": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "aaa"),
_P("mapOfMapsRecursive", "ccc"),
),
"v3",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.TestWithConverter(nestedTypeParser, repeatingConverter{nestedTypeParser}); err != nil {
t.Fatal(err)
}
})
}
}
func TestMultipleAppliersFieldRenameConversions(t *testing.T) {
versions := []fieldpath.APIVersion{"v1", "v2", "v3"}
for _, v1 := range versions {
for _, v2 := range versions {
for _, v3 := range versions {
t.Run(fmt.Sprintf("%s-%s-%s", v1, v2, v3), func(t *testing.T) {
testMultipleAppliersFieldRenameConversions(t, v1, v2, v3)
})
}
}
}
}
func testMultipleAppliersFieldRenameConversions(t *testing.T, v1, v2, v3 fieldpath.APIVersion) {
tests := map[string]TestCase{
"updater_claims_field": {
Ops: []Operation{
Apply{
Manager: "applier",
APIVersion: v1,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: a
`, v1)),
},
Update{
Manager: "updater",
APIVersion: v2,
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: b
`, v2)),
},
},
Object: typed.YAMLObject(fmt.Sprintf(`
struct:
name: a
scalarField_%s: b
`, v3)),
APIVersion: v3,
Managed: fieldpath.ManagedFields{
"updater": fieldpath.NewVersionedSet(
_NS(
_P("struct", fmt.Sprintf("scalarField_%s", v2)),
),
v2,
false,
),
"applier": fieldpath.NewVersionedSet(
_NS(
_P("struct", "name"),
),
v1,
true,
),
},
},
}
converter := renamingConverter{structMultiversionParser}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.TestWithConverter(structMultiversionParser, converter); err != nil {
t.Fatal(err)
}
})
}
}
// repeatingConverter repeats a single letterkey v times, where v is the version.
type repeatingConverter struct {
parser Parser
}
var _ merge.Converter = repeatingConverter{}
var missingVersionError error = fmt.Errorf("cannot convert to invalid version")
// Convert implements merge.Converter
func (r repeatingConverter) Convert(v *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
if len(version) < 2 || string(version)[0] != 'v' {
return nil, missingVersionError
}
versionNumber, err := strconv.Atoi(string(version)[1:len(version)])
if err != nil {
return nil, missingVersionError
}
y, err := yaml.Marshal(v.AsValue().Unstructured())
if err != nil {
return nil, err
}
str := string(y)
var str2 string
for i, line := range strings.Split(str, "\n") {
if i == 0 {
str2 = line
} else {
spaces := strings.Repeat(" ", countLeadingSpace(line))
if len(spaces) == 0 {
break
}
c := line[len(spaces) : len(spaces)+1]
c = strings.Repeat(c, versionNumber)
str2 = fmt.Sprintf("%v\n%v%v:", str2, spaces, c)
}
}
v2, err := r.parser.Type(string(version)).FromYAML(typed.YAMLObject(str2))
if err != nil {
return nil, err
}
return v2, nil
}
func countLeadingSpace(line string) int {
spaces := 0
for _, letter := range line {
if letter == ' ' {
spaces++
} else {
break
}
}
return spaces
}
// Convert implements merge.Converter
func (r repeatingConverter) IsMissingVersionError(err error) bool {
return err == missingVersionError
}
// renamingConverter renames fields by substituting the version suffix of the field name. E.g.
// converting a map with a field named "name_v1" from v1 to v2 renames the field to "name_v2".
// Fields without a version suffix are not converted; they are the same in all versions.
// When parsing, this converter will look for the type by using the APIVersion of the
// object it's trying to parse. If trying to parse a "v1" object, a corresponding "v1" type
// should exist in the schema of the provided parser.
type renamingConverter struct {
parser Parser
}
// Convert implements merge.Converter
func (r renamingConverter) Convert(v *typed.TypedValue, version fieldpath.APIVersion) (*typed.TypedValue, error) {
inVersion := fieldpath.APIVersion(*v.TypeRef().NamedType)
outType := r.parser.Type(string(version))
return outType.FromUnstructured(renameFields(v.AsValue(), string(inVersion), string(version)))
}
func renameFields(v value.Value, oldSuffix, newSuffix string) interface{} {
if v.IsMap() {
out := map[string]interface{}{}
v.AsMap().Iterate(func(key string, value value.Value) bool {
if strings.HasSuffix(key, oldSuffix) {
out[strings.TrimSuffix(key, oldSuffix)+newSuffix] = renameFields(value, oldSuffix, newSuffix)
} else {
out[key] = renameFields(value, oldSuffix, newSuffix)
}
return true
})
return out
}
if v.IsList() {
var out []interface{}
ri := v.AsList().Range()
for ri.Next() {
_, v := ri.Item()
out = append(out, renameFields(v, oldSuffix, newSuffix))
}
return out
}
return v.Unstructured()
}
// Convert implements merge.Converter
func (r renamingConverter) IsMissingVersionError(err error) bool {
return err == missingVersionError
}
var atomicMapParser = func() Parser {
parser, err := typed.NewParser(`types:
- name: v1
map:
fields:
- name: atomicMap
type:
namedType: atomicMap
- name: atomicMap
map:
fields:
- name: field1
type:
scalar: string
- name: field2
type:
scalar: string
elementRelationship: atomic
`)
if err != nil {
panic(err)
}
return parser
}()
func TestMultipleApplierAtomicMaps(t *testing.T) {
tests := map[string]TestCase{
"force": {
Ops: []Operation{
Apply{
Manager: "apply-one",
APIVersion: "v1",
Object: `
atomicMap:
field1: a
`,
},
Apply{
Manager: "apply-two",
APIVersion: "v1",
Object: `
atomicMap:
field2: b
`,
Conflicts: merge.Conflicts{
merge.Conflict{Manager: "apply-one", Path: _P("atomicMap")},
},
},
ForceApply{
Manager: "apply-two",
APIVersion: "v1",
Object: `
atomicMap:
field2: b
`,
},
},
Object: `
atomicMap:
field2: b
`,
APIVersion: "v1",
Managed: fieldpath.ManagedFields{
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("atomicMap"),
),
"v1",
false,
),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
if err := test.Test(atomicMapParser); err != nil {
t.Fatal(err)
}
})
}
}
func BenchmarkMultipleApplierRecursiveRealConversion(b *testing.B) {
test := TestCase{
Ops: []Operation{
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
a:
b:
c:
d:
`,
APIVersion: "v1",
},
Apply{
Manager: "apply-two",
Object: `
mapOfMapsRecursive:
aa:
cc:
dd:
`,
APIVersion: "v2",
},
Update{
Manager: "controller",
Object: `
mapOfMapsRecursive:
aaa:
bbb:
ccc:
ddd:
ccc:
ddd:
eee:
fff:
`,
APIVersion: "v3",
},
Apply{
Manager: "apply-one",
Object: `
mapOfMapsRecursive:
`,
APIVersion: "v4",
},
},
Object: `
mapOfMapsRecursive:
aaaa:
cccc:
dddd:
eeee:
ffff:
`,
APIVersion: "v4",
Managed: fieldpath.ManagedFields{
"apply-one": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive"),
),
"v4",
false,
),
"apply-two": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "aa"),
_P("mapOfMapsRecursive", "cc"),
_P("mapOfMapsRecursive", "cc", "dd"),
),
"v2",
false,
),
"controller": fieldpath.NewVersionedSet(
_NS(
_P("mapOfMapsRecursive", "ccc", "ddd", "eee"),
_P("mapOfMapsRecursive", "ccc", "ddd", "eee", "fff"),
),
"v3",
false,
),
},
}
// Make sure this passes...
if err := test.TestWithConverter(nestedTypeParser, repeatingConverter{nestedTypeParser}); err != nil {
b.Fatal(err)
}
test.PreprocessOperations(nestedTypeParser)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
if err := test.BenchWithConverter(nestedTypeParser, repeatingConverter{nestedTypeParser}); err != nil {
b.Fatal(err)
}
}
}