mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-07-17 01:39:06 +00:00
Maintain resources in order loaded.
This commit is contained in:
@@ -1,27 +1,13 @@
|
||||
/*
|
||||
Copyright 2018 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.
|
||||
*/
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/internal/kusterr"
|
||||
"sigs.k8s.io/kustomize/pkg/ifc"
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
)
|
||||
@@ -41,11 +27,17 @@ func (rmF *Factory) RF() *resource.Factory {
|
||||
return rmF.resF
|
||||
}
|
||||
|
||||
func New() ResMap {
|
||||
return newOne()
|
||||
}
|
||||
|
||||
// FromResource returns a ResMap with one entry.
|
||||
func (rmF *Factory) FromResource(res *resource.Resource) ResMap {
|
||||
result := ResMap{}
|
||||
result[res.Id()] = res
|
||||
return result
|
||||
m, err := newResMapFromResourceSlice([]*resource.Resource{res})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// FromFile returns a ResMap given a resource path.
|
||||
@@ -55,26 +47,41 @@ func (rmF *Factory) FromFile(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res, err := rmF.NewResMapFromBytes(content)
|
||||
m, err := rmF.NewResMapFromBytes(content)
|
||||
if err != nil {
|
||||
return nil, kusterr.Handler(err, path)
|
||||
}
|
||||
return res, nil
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// newResMapFromBytes decodes a list of objects in byte array format.
|
||||
// NewResMapFromBytes decodes a list of objects in byte array format.
|
||||
func (rmF *Factory) NewResMapFromBytes(b []byte) (ResMap, error) {
|
||||
resources, err := rmF.resF.SliceFromBytes(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := ResMap{}
|
||||
for _, res := range resources {
|
||||
id := res.Id()
|
||||
if _, found := result[id]; found {
|
||||
return result, fmt.Errorf("GroupVersionKindName: %#v already exists in the map", id)
|
||||
return newResMapFromResourceSlice(resources)
|
||||
}
|
||||
|
||||
// Deprecated.
|
||||
// FromMap returns a ResMap with arbitrary internal ordering,
|
||||
// panicing on error. For tests only.
|
||||
// See also ErrorIfNotEqual.
|
||||
func FromMap(arg map[resid.ResId]*resource.Resource) ResMap {
|
||||
result, err := fromMap(arg)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func fromMap(arg map[resid.ResId]*resource.Resource) (ResMap, error) {
|
||||
result := New()
|
||||
for id, r := range arg {
|
||||
err := result.AppendWithId(id, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[id] = res
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -136,13 +143,12 @@ func (rmF *Factory) FromSecretArgs(
|
||||
}
|
||||
|
||||
func newResMapFromResourceSlice(resources []*resource.Resource) (ResMap, error) {
|
||||
result := ResMap{}
|
||||
result := New()
|
||||
for _, res := range resources {
|
||||
id := res.Id()
|
||||
if _, found := result[id]; found {
|
||||
return nil, fmt.Errorf("duplicated %#v is not allowed", id)
|
||||
err := result.Append(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[id] = res
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -1,18 +1,5 @@
|
||||
/*
|
||||
Copyright 2018 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.
|
||||
*/
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resmap_test
|
||||
|
||||
@@ -21,6 +8,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
|
||||
"sigs.k8s.io/kustomize/internal/loadertest"
|
||||
"sigs.k8s.io/kustomize/pkg/fs"
|
||||
"sigs.k8s.io/kustomize/pkg/gvk"
|
||||
@@ -57,14 +46,15 @@ metadata:
|
||||
if ferr := l.AddFile("/whatever/project/deployment.yaml", []byte(resourceStr)); ferr != nil {
|
||||
t.Fatalf("Error adding fake file: %v\n", ferr)
|
||||
}
|
||||
expected := ResMap{resid.NewResId(deploy, "dply1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "dply1",
|
||||
},
|
||||
}),
|
||||
expected := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(deploy, "dply1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "dply1",
|
||||
},
|
||||
}),
|
||||
resid.NewResId(deploy, "dply2"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
@@ -82,11 +72,10 @@ metadata:
|
||||
"namespace": "test",
|
||||
},
|
||||
}),
|
||||
}
|
||||
|
||||
})
|
||||
m, _ := rmF.FromFile(l, "deployment.yaml")
|
||||
if len(m) != 3 {
|
||||
t.Fatalf("%#v should contain 3 appResource, but got %d", m, len(m))
|
||||
if m.Size() != 3 {
|
||||
t.Fatalf("result should contain 3, but got %d", m.Size())
|
||||
}
|
||||
if err := expected.ErrorIfNotEqual(m); err != nil {
|
||||
t.Fatalf("actual doesn't match expected: %v", err)
|
||||
@@ -104,24 +93,23 @@ kind: ConfigMap
|
||||
metadata:
|
||||
name: cm2
|
||||
`)
|
||||
expected := ResMap{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
}),
|
||||
resid.NewResId(cmap, "cm2"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
}),
|
||||
}
|
||||
expected := New()
|
||||
expected.Append(rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
}))
|
||||
expected.Append(rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
}))
|
||||
m, err := rmF.NewResMapFromBytes(encoded)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
@@ -158,7 +146,7 @@ func TestNewFromConfigMaps(t *testing.T) {
|
||||
},
|
||||
filepath: "/whatever/project/app.env",
|
||||
content: "DB_USERNAME=admin\nDB_PASSWORD=somepw",
|
||||
expected: ResMap{
|
||||
expected: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "envConfigMap"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -171,7 +159,7 @@ func TestNewFromConfigMaps(t *testing.T) {
|
||||
"DB_PASSWORD": "somepw",
|
||||
},
|
||||
}, &types.GeneratorArgs{}, nil),
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
{
|
||||
@@ -187,7 +175,7 @@ func TestNewFromConfigMaps(t *testing.T) {
|
||||
},
|
||||
filepath: "/whatever/project/app-init.ini",
|
||||
content: "FOO=bar\nBAR=baz\n",
|
||||
expected: ResMap{
|
||||
expected: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "fileConfigMap"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -201,7 +189,7 @@ BAR=baz
|
||||
`,
|
||||
},
|
||||
}, &types.GeneratorArgs{}, nil),
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
description: "construct config map from literal",
|
||||
@@ -215,7 +203,7 @@ BAR=baz
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: ResMap{
|
||||
expected: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "literalConfigMap"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -230,7 +218,7 @@ BAR=baz
|
||||
"d": "false",
|
||||
},
|
||||
}, &types.GeneratorArgs{}, nil),
|
||||
},
|
||||
}),
|
||||
},
|
||||
|
||||
// TODO: add testcase for data coming from multiple sources like
|
||||
@@ -275,7 +263,7 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expected := ResMap{
|
||||
expected := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(secret, "apple"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -289,7 +277,7 @@ func TestNewResMapFromSecretArgs(t *testing.T) {
|
||||
"DB_PASSWORD": base64.StdEncoding.EncodeToString([]byte("somepw")),
|
||||
},
|
||||
}, &types.GeneratorArgs{}, nil),
|
||||
}
|
||||
})
|
||||
if !reflect.DeepEqual(actual, expected) {
|
||||
t.Fatalf("%#v\ndoesn't match expected:\n%#v", actual, expected)
|
||||
}
|
||||
|
||||
@@ -1,44 +1,348 @@
|
||||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
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 resmap implements a map from ResId to Resource that tracks all resources in a kustomization.
|
||||
// Package resmap implements a map from ResId to Resource that
|
||||
// tracks all resources in a kustomization.
|
||||
package resmap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"sigs.k8s.io/kustomize/pkg/resid"
|
||||
"sigs.k8s.io/kustomize/pkg/resource"
|
||||
"sigs.k8s.io/kustomize/pkg/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// ResMap is a map from ResId to Resource.
|
||||
type ResMap map[resid.ResId]*resource.Resource
|
||||
// ResMap is an interface describing operations on the
|
||||
// core kustomize data structure.
|
||||
//
|
||||
// TODO: delete the commentary below when/if the issues
|
||||
// discussed are addressed.
|
||||
//
|
||||
// It's a temporary(?) interface used during a refactoring
|
||||
// from a bare map (map[resid.ResId]*resource.Resource) to a
|
||||
// pointer to struct (currently named *resWrangler).
|
||||
// Replacing it with a ptr to struct will ease click-thrus
|
||||
// to implementation during development.
|
||||
// OTOH, hackery in a PR might be easier to see if the
|
||||
// interface is left in place.
|
||||
//
|
||||
// The old (bare map) ResMap had pervasive problems:
|
||||
//
|
||||
// * It was mutated inside loops over itself.
|
||||
//
|
||||
// Bugs introduced this way were hard to find since the
|
||||
// bare map was recursively passed everywhere, sometimes
|
||||
// mid loop.
|
||||
//
|
||||
// * Its keys (ResId) aren't opaque, and are effectively
|
||||
// mutated (via copy and replace) for data storage reasons
|
||||
// as a hack.
|
||||
//
|
||||
// ResId was modified a long time ago as a hack to
|
||||
// store name transformation data (prefix and suffix),
|
||||
// destabilizing the basic map concept and resulting
|
||||
// in the need for silly ResId functions like
|
||||
// NewResIdWithPrefixSuffixNamespace, NsGvknEquals,
|
||||
// HasSameLeftmostPrefix, CopyWithNewPrefixSuffix, etc.
|
||||
// plus logic to use them, and overly complex tests.
|
||||
//
|
||||
// If this data were stored in the Resource object
|
||||
// (not in Kunstructured, but as a sibling to it next to
|
||||
// GenArgs, references, etc.) then much code could be
|
||||
// deleted and the remainder simplified.
|
||||
//
|
||||
// * It doesn't preserve (by definition) value order.
|
||||
//
|
||||
// Preserving order is now needed to support
|
||||
// transformer plugins (they aren't commutative).
|
||||
//
|
||||
// One way to fix this is deprecate use of ResId as the
|
||||
// key in favor of ItemId. See use of the resmap.Remove
|
||||
// function to spot the places that need fixing to allow
|
||||
// this.
|
||||
type ResMap interface {
|
||||
// Size reports the number of resources.
|
||||
Size() int
|
||||
|
||||
// Resources provides a discardable slice
|
||||
// of resource pointers, returned in the order
|
||||
// as appended.
|
||||
Resources() []*resource.Resource
|
||||
|
||||
// Append adds a Resource, automatically computing its
|
||||
// associated Id.
|
||||
// Error on Id collision.
|
||||
Append(*resource.Resource) error
|
||||
|
||||
// AppendWithId adds a Resource with the given Id.
|
||||
// Error on Id collision.
|
||||
AppendWithId(resid.ResId, *resource.Resource) error
|
||||
|
||||
// AsMap returns ResId, *Resource pairs in
|
||||
// arbitrary order via a map.
|
||||
// The map is discardable, and edits to map structure
|
||||
// have no impact on the ResMap.
|
||||
// The Ids are copies, but the resources are pointers,
|
||||
// so the resources themselves can be modified.
|
||||
AsMap() map[resid.ResId]*resource.Resource
|
||||
|
||||
// EncodeAsYaml emits the resources as YAML in a byte slice.
|
||||
// Resources are separated by `---`.
|
||||
EncodeAsYaml() ([]byte, error)
|
||||
|
||||
// Gets the resource with the given Id, else nil.
|
||||
GetById(resid.ResId) *resource.Resource
|
||||
|
||||
// ReplaceResource associates a new resource with
|
||||
// an _existing_ Id.
|
||||
// Error if Id unknown, or if some other Id points
|
||||
// to the same resource object.
|
||||
ReplaceResource(resid.ResId, *resource.Resource) error
|
||||
|
||||
// AllIds returns all known Ids.
|
||||
// Result order is arbitrary.
|
||||
AllIds() []resid.ResId
|
||||
|
||||
// GetMatchingIds returns a slice of Ids that
|
||||
// satisfy the given matcher function.
|
||||
// Result order is arbitrary.
|
||||
GetMatchingIds(IdMatcher) []resid.ResId
|
||||
|
||||
// Remove removes the Id and the resource it points to.
|
||||
Remove(resid.ResId) error
|
||||
|
||||
// ResourcesThatCouldReference returns a new ResMap with
|
||||
// resources that _might_ reference the resource represented
|
||||
// by the argument Id, excluding resources that should
|
||||
// _never_ reference the Id. E.g., if the Id
|
||||
// refers to a ConfigMap, the returned set may include a
|
||||
// Deployment from the same namespace and exclude Deployments
|
||||
// from other namespaces. Cluster wide objects are
|
||||
// never excluded.
|
||||
ResourcesThatCouldReference(resid.ResId) ResMap
|
||||
|
||||
// DeepCopy copies the ResMap and underlying resources.
|
||||
DeepCopy() ResMap
|
||||
|
||||
// ShallowCopy copies the ResMap but
|
||||
// not the underlying resources.
|
||||
ShallowCopy() ResMap
|
||||
|
||||
// ErrorIfNotEqual returns an error if the
|
||||
// argument doesn't have the same Ids and resource
|
||||
// data as self. Ordering is _not_ taken into account,
|
||||
// as this function was solely used in tests written
|
||||
// before internal resource order was maintained,
|
||||
// and those tests are initialized with maps which
|
||||
// by definition have random ordering, and will
|
||||
// fail spuriously.
|
||||
// TODO: rename to ErrorIfNotEqualSets
|
||||
// TODO: modify tests to not use resmap.FromMap,
|
||||
// TODO: - and replace this with a stricter equals.
|
||||
ErrorIfNotEqual(ResMap) error
|
||||
|
||||
// Debug prints the ResMap.
|
||||
Debug(title string)
|
||||
}
|
||||
|
||||
// resWrangler holds the content manipulated by kustomize.
|
||||
type resWrangler struct {
|
||||
// Resource list maintained in load (append) order.
|
||||
// This is important for transformers, which must
|
||||
// be performed in a specific order, and for users
|
||||
// who for whatever reasons wish the order they
|
||||
// specify in kustomizations to be maintained and
|
||||
// available as an option for final YAML rendering.
|
||||
rList []*resource.Resource
|
||||
|
||||
// A map from id to an index into rList.
|
||||
// At the time of writing, the ids used as keys in
|
||||
// this map cannot be assumed to match the id
|
||||
// generated from the resource.Id() method pointed
|
||||
// to by the map's value (via rList). These keys
|
||||
// have been hacked to store prefix/suffix data.
|
||||
rIndex map[resid.ResId]int
|
||||
}
|
||||
|
||||
func newOne() *resWrangler {
|
||||
result := &resWrangler{}
|
||||
result.rIndex = make(map[resid.ResId]int)
|
||||
return result
|
||||
}
|
||||
|
||||
// Size implements ResMap.
|
||||
func (m *resWrangler) Size() int {
|
||||
if len(m.rList) != len(m.rIndex) {
|
||||
panic(errors.New("class invariant violation"))
|
||||
}
|
||||
return len(m.rList)
|
||||
}
|
||||
|
||||
func (m *resWrangler) indexOfResource(other *resource.Resource) int {
|
||||
for i, r := range m.rList {
|
||||
if r == other {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Resources implements ResMap.
|
||||
func (m *resWrangler) Resources() []*resource.Resource {
|
||||
tmp := make([]*resource.Resource, len(m.rList))
|
||||
copy(tmp, m.rList)
|
||||
return tmp
|
||||
}
|
||||
|
||||
// GetById implements ResMap.
|
||||
func (m *resWrangler) GetById(id resid.ResId) *resource.Resource {
|
||||
if i, ok := m.rIndex[id]; ok {
|
||||
return m.rList[i]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append implements ResMap.
|
||||
func (m *resWrangler) Append(res *resource.Resource) error {
|
||||
return m.AppendWithId(res.Id(), res)
|
||||
}
|
||||
|
||||
// Remove implements ResMap.
|
||||
func (m *resWrangler) Remove(adios resid.ResId) error {
|
||||
tmp := newOne()
|
||||
for i, r := range m.rList {
|
||||
id, err := m.idMappingToIndex(i)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "assumption failure in remove")
|
||||
}
|
||||
if id != adios {
|
||||
tmp.AppendWithId(id, r)
|
||||
}
|
||||
}
|
||||
if tmp.Size() != m.Size()-1 {
|
||||
return fmt.Errorf("id %s not found in removal", adios)
|
||||
}
|
||||
m.rIndex = tmp.rIndex
|
||||
m.rList = tmp.rList
|
||||
return nil
|
||||
}
|
||||
|
||||
// AppendWithId implements ResMap.
|
||||
func (m *resWrangler) AppendWithId(id resid.ResId, res *resource.Resource) error {
|
||||
if already, ok := m.rIndex[id]; ok {
|
||||
return fmt.Errorf(
|
||||
"attempt to add res %s at id %s; that id already maps to %d",
|
||||
res, id, already)
|
||||
}
|
||||
i := m.indexOfResource(res)
|
||||
if i >= 0 {
|
||||
return fmt.Errorf(
|
||||
"attempt to add res %s that is already held",
|
||||
res)
|
||||
}
|
||||
m.rList = append(m.rList, res)
|
||||
m.rIndex[id] = len(m.rList) - 1
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReplaceResource implements ResMap.
|
||||
func (m *resWrangler) ReplaceResource(
|
||||
id resid.ResId, newGuy *resource.Resource) error {
|
||||
insertAt, ok := m.rIndex[id]
|
||||
if !ok {
|
||||
return fmt.Errorf(
|
||||
"attempt to reset resource at id %s; that id not used", id)
|
||||
}
|
||||
existingSpot := m.indexOfResource(newGuy)
|
||||
if insertAt == existingSpot {
|
||||
// Be idempotent.
|
||||
return nil
|
||||
}
|
||||
if existingSpot >= 0 {
|
||||
return fmt.Errorf(
|
||||
"the new resource %s is already present", newGuy.Id())
|
||||
}
|
||||
m.rList[insertAt] = newGuy
|
||||
return nil
|
||||
}
|
||||
|
||||
// AsMap implements ResMap.
|
||||
func (m *resWrangler) AsMap() map[resid.ResId]*resource.Resource {
|
||||
result := make(map[resid.ResId]*resource.Resource, m.Size())
|
||||
for id, i := range m.rIndex {
|
||||
result[id] = m.rList[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// AllIds implements ResMap.
|
||||
func (m *resWrangler) AllIds() (ids []resid.ResId) {
|
||||
ids = make([]resid.ResId, m.Size())
|
||||
i := 0
|
||||
for id := range m.rIndex {
|
||||
ids[i] = id
|
||||
i++
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Debug implements ResMap.
|
||||
func (m *resWrangler) Debug(title string) {
|
||||
fmt.Println("--------------------------- " + title)
|
||||
firstObj := true
|
||||
for i, r := range m.rList {
|
||||
if firstObj {
|
||||
firstObj = false
|
||||
} else {
|
||||
fmt.Println("---")
|
||||
}
|
||||
fmt.Printf("# %d %s\n", i, m.debugIdMappingToIndex(i))
|
||||
blob, err := yaml.Marshal(r.Map())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(blob))
|
||||
}
|
||||
}
|
||||
|
||||
func (m *resWrangler) debugIdMappingToIndex(i int) string {
|
||||
id, err := m.idMappingToIndex(i)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return id.String()
|
||||
}
|
||||
|
||||
func (m *resWrangler) idMappingToIndex(i int) (resid.ResId, error) {
|
||||
var foundId resid.ResId
|
||||
found := false
|
||||
for id, index := range m.rIndex {
|
||||
if index == i {
|
||||
if found {
|
||||
return foundId, fmt.Errorf("found multiple")
|
||||
}
|
||||
found = true
|
||||
foundId = id
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return foundId, fmt.Errorf("cannot find index %d", i)
|
||||
}
|
||||
return foundId, nil
|
||||
}
|
||||
|
||||
type IdMatcher func(resid.ResId) bool
|
||||
|
||||
// GetMatchingIds returns a slice of ResId keys from the map
|
||||
// that all satisfy the given matcher function.
|
||||
func (m ResMap) GetMatchingIds(matches IdMatcher) []resid.ResId {
|
||||
// GetMatchingIds implements ResMap.
|
||||
func (m *resWrangler) GetMatchingIds(matches IdMatcher) []resid.ResId {
|
||||
var result []resid.ResId
|
||||
for id := range m {
|
||||
for id := range m.rIndex {
|
||||
if matches(id) {
|
||||
result = append(result, id)
|
||||
}
|
||||
@@ -46,19 +350,19 @@ func (m ResMap) GetMatchingIds(matches IdMatcher) []resid.ResId {
|
||||
return result
|
||||
}
|
||||
|
||||
// EncodeAsYaml encodes a ResMap to YAML; encoded objects separated by `---`.
|
||||
func (m ResMap) EncodeAsYaml() ([]byte, error) {
|
||||
var ids []resid.ResId
|
||||
for id := range m {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
// EncodeAsYaml implements ResMap.
|
||||
func (m *resWrangler) EncodeAsYaml() ([]byte, error) {
|
||||
// TODO: should be able to suppress this sort
|
||||
// and rely on ordering as specified in the ResMap
|
||||
// internal rList.
|
||||
ids := m.AllIds()
|
||||
sort.Sort(IdSlice(ids))
|
||||
|
||||
firstObj := true
|
||||
var b []byte
|
||||
buf := bytes.NewBuffer(b)
|
||||
for _, id := range ids {
|
||||
obj := m[id]
|
||||
obj := m.GetById(id)
|
||||
out, err := yaml.Marshal(obj.Map())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -79,54 +383,80 @@ func (m ResMap) EncodeAsYaml() ([]byte, error) {
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ErrorIfNotEqual returns error if maps are not equal.
|
||||
func (m ResMap) ErrorIfNotEqual(m2 ResMap) error {
|
||||
if len(m) != len(m2) {
|
||||
var keySet1 []resid.ResId
|
||||
var keySet2 []resid.ResId
|
||||
for id := range m {
|
||||
keySet1 = append(keySet1, id)
|
||||
}
|
||||
for id := range m2 {
|
||||
keySet2 = append(keySet2, id)
|
||||
}
|
||||
return fmt.Errorf("maps has different number of entries: %#v doesn't equals %#v", keySet1, keySet2)
|
||||
// ErrorIfNotEqual implements ResMap.
|
||||
func (m *resWrangler) ErrorIfNotEqual(other ResMap) error {
|
||||
m2, ok := other.(*resWrangler)
|
||||
if !ok {
|
||||
panic(fmt.Errorf("bad cast to resmapImpl"))
|
||||
}
|
||||
for id, obj1 := range m {
|
||||
obj2, found := m2[id]
|
||||
if !found {
|
||||
return fmt.Errorf("%#v doesn't exist in %#v", id, m2)
|
||||
if m.Size() != m2.Size() {
|
||||
return fmt.Errorf(
|
||||
"lists have different number of entries: %#v doesn't equal %#v",
|
||||
m.rList, m2.rList)
|
||||
}
|
||||
for id, i := range m.rIndex {
|
||||
r1 := m.rList[i]
|
||||
r2 := m2.GetById(id)
|
||||
if r2 == nil {
|
||||
return fmt.Errorf("id in self missing from other; id: %s", id)
|
||||
}
|
||||
if !reflect.DeepEqual(obj1, obj2) {
|
||||
return fmt.Errorf("%#v doesn't deep equal %#v", obj1, obj2)
|
||||
if !r1.KunstructEqual(r2) {
|
||||
return fmt.Errorf(
|
||||
"kuns equal mismatch: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
||||
r1, r2, r1, r2)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopy clone the resmap into a new one
|
||||
func (m ResMap) DeepCopy(rf *resource.Factory) ResMap {
|
||||
mcopy := make(ResMap)
|
||||
for id, obj := range m {
|
||||
mcopy[id] = obj.DeepCopy()
|
||||
}
|
||||
return mcopy
|
||||
type resCopier func(r *resource.Resource) *resource.Resource
|
||||
|
||||
// ShallowCopy implements ResMap.
|
||||
func (m *resWrangler) ShallowCopy() ResMap {
|
||||
return m.makeCopy(
|
||||
func(r *resource.Resource) *resource.Resource {
|
||||
return r
|
||||
})
|
||||
}
|
||||
|
||||
// FilterBy returns a subset ResMap containing ResIds with
|
||||
// the same namespace and leftmost name prefix and rightmost name
|
||||
// as the inputId. If inputId is a cluster level resource, this
|
||||
// returns the original ResMap.
|
||||
func (m ResMap) FilterBy(inputId resid.ResId) ResMap {
|
||||
// DeepCopy implements ResMap.
|
||||
func (m *resWrangler) DeepCopy() ResMap {
|
||||
return m.makeCopy(
|
||||
func(r *resource.Resource) *resource.Resource {
|
||||
return r.DeepCopy()
|
||||
})
|
||||
}
|
||||
|
||||
// makeCopy copies the ResMap.
|
||||
func (m *resWrangler) makeCopy(copier resCopier) ResMap {
|
||||
result := &resWrangler{}
|
||||
result.rIndex = make(map[resid.ResId]int, m.Size())
|
||||
result.rList = make([]*resource.Resource, m.Size())
|
||||
for i, r := range m.rList {
|
||||
result.rList[i] = copier(r)
|
||||
id, err := m.idMappingToIndex(i)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("corrupt index map"))
|
||||
}
|
||||
result.rIndex[id] = i
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ResourcesThatCouldReference implements ResMap.
|
||||
func (m *resWrangler) ResourcesThatCouldReference(inputId resid.ResId) ResMap {
|
||||
if inputId.Gvk().IsClusterKind() {
|
||||
return m
|
||||
}
|
||||
result := ResMap{}
|
||||
for id, res := range m {
|
||||
result := New()
|
||||
for id, i := range m.rIndex {
|
||||
if id.Gvk().IsClusterKind() || id.Namespace() == inputId.Namespace() &&
|
||||
id.HasSameLeftmostPrefix(inputId) &&
|
||||
id.HasSameRightmostSuffix(inputId) {
|
||||
result[id] = res
|
||||
err := result.AppendWithId(id, m.rList[i])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
@@ -136,16 +466,16 @@ func (m ResMap) FilterBy(inputId resid.ResId) ResMap {
|
||||
// key collision and skipping nil maps.
|
||||
// If all of the maps are nil, an empty ResMap is returned.
|
||||
func MergeWithErrorOnIdCollision(maps ...ResMap) (ResMap, error) {
|
||||
result := ResMap{}
|
||||
result := New()
|
||||
for _, m := range maps {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
for id, res := range m {
|
||||
if _, found := result[id]; found {
|
||||
return nil, fmt.Errorf("id '%q' already used", id)
|
||||
for id, res := range m.AsMap() {
|
||||
err := result.AppendWithId(id, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result[id] = res
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
@@ -161,26 +491,40 @@ func MergeWithErrorOnIdCollision(maps ...ResMap) (ResMap, error) {
|
||||
// resource X is found to be already in the combined map, then the behavior
|
||||
// field for X must be BehaviorMerge or BehaviorReplace. If X is not in the
|
||||
// map, then it's behavior cannot be merge or replace.
|
||||
// nolint: gocyclo
|
||||
func MergeWithOverride(maps ...ResMap) (ResMap, error) {
|
||||
if len(maps) == 0 {
|
||||
return New(), nil
|
||||
}
|
||||
result := maps[0]
|
||||
if result == nil {
|
||||
result = ResMap{}
|
||||
result = New()
|
||||
}
|
||||
for _, m := range maps[1:] {
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
for id, r := range m {
|
||||
for id, r := range m.AsMap() {
|
||||
matchedId := result.GetMatchingIds(id.GvknEquals)
|
||||
if len(matchedId) == 1 {
|
||||
id = matchedId[0]
|
||||
old := result.GetById(id)
|
||||
if old == nil {
|
||||
return nil, fmt.Errorf("id lookup failure")
|
||||
}
|
||||
switch r.Behavior() {
|
||||
case types.BehaviorReplace:
|
||||
r.Replace(result[id])
|
||||
result[id] = r
|
||||
r.Replace(old)
|
||||
err := result.ReplaceResource(id, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case types.BehaviorMerge:
|
||||
r.Merge(result[id])
|
||||
result[id] = r
|
||||
r.Merge(old)
|
||||
err := result.ReplaceResource(id, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("id %#v exists; must merge or replace", id)
|
||||
}
|
||||
@@ -189,7 +533,10 @@ func MergeWithOverride(maps ...ResMap) (ResMap, error) {
|
||||
case types.BehaviorMerge, types.BehaviorReplace:
|
||||
return nil, fmt.Errorf("id %#v does not exist; cannot merge or replace", id)
|
||||
default:
|
||||
result[id] = r
|
||||
err := result.AppendWithId(id, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("merge conflict, found multiple objects %v the Resmap %v can merge into", matchedId, id)
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
/*
|
||||
Copyright 2018 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.
|
||||
*/
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resmap_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -34,6 +22,134 @@ var rf = resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
var rmF = NewFactory(rf)
|
||||
|
||||
func doAppend(t *testing.T, w ResMap, r *resource.Resource) {
|
||||
err := w.Append(r)
|
||||
if err != nil {
|
||||
t.Fatalf("append error: %v", err)
|
||||
}
|
||||
}
|
||||
func doRemove(t *testing.T, w ResMap, id resid.ResId) {
|
||||
err := w.Remove(id)
|
||||
if err != nil {
|
||||
t.Fatalf("remove error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a resource with a predictable name.
|
||||
func makeCm(i int) *resource.Resource {
|
||||
return rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": fmt.Sprintf("cm%03d", i),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAppendRemove(t *testing.T) {
|
||||
w1 := New()
|
||||
doAppend(t, w1, makeCm(1))
|
||||
doAppend(t, w1, makeCm(2))
|
||||
doAppend(t, w1, makeCm(3))
|
||||
doAppend(t, w1, makeCm(4))
|
||||
doAppend(t, w1, makeCm(5))
|
||||
doAppend(t, w1, makeCm(6))
|
||||
doAppend(t, w1, makeCm(7))
|
||||
doRemove(t, w1, makeCm(1).Id())
|
||||
doRemove(t, w1, makeCm(3).Id())
|
||||
doRemove(t, w1, makeCm(5).Id())
|
||||
doRemove(t, w1, makeCm(7).Id())
|
||||
|
||||
w2 := New()
|
||||
doAppend(t, w2, makeCm(2))
|
||||
doAppend(t, w2, makeCm(4))
|
||||
doAppend(t, w2, makeCm(6))
|
||||
if !reflect.DeepEqual(w1, w1) {
|
||||
w1.Debug("w1")
|
||||
w2.Debug("w2")
|
||||
t.Fatalf("mismatch")
|
||||
}
|
||||
|
||||
err := w2.Append(makeCm(6))
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
w := New()
|
||||
r := makeCm(1)
|
||||
err := w.Remove(r.Id())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
err = w.Append(r)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = w.Remove(r.Id())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = w.Remove(r.Id())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplaceResource(t *testing.T) {
|
||||
cm5 := makeCm(5)
|
||||
cm700 := makeCm(700)
|
||||
cm888 := makeCm(888)
|
||||
|
||||
w := New()
|
||||
doAppend(t, w, makeCm(1))
|
||||
doAppend(t, w, makeCm(2))
|
||||
doAppend(t, w, makeCm(3))
|
||||
doAppend(t, w, makeCm(4))
|
||||
doAppend(t, w, cm5)
|
||||
doAppend(t, w, makeCm(6))
|
||||
doAppend(t, w, makeCm(7))
|
||||
|
||||
oldSize := w.Size()
|
||||
err := w.ReplaceResource(cm5.Id(), cm700)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if w.Size() != oldSize {
|
||||
t.Fatalf("unexpected size %d", w.Size())
|
||||
}
|
||||
if w.GetById(cm5.Id()) != cm700 {
|
||||
t.Fatalf("unexpected result")
|
||||
}
|
||||
if err := w.Append(cm5); err == nil {
|
||||
t.Fatalf("expected id already there error")
|
||||
}
|
||||
if err := w.AppendWithId(cm888.Id(), cm5); err != nil {
|
||||
// Okay to add with some unused Id.
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := w.Append(cm700); err == nil {
|
||||
t.Fatalf("expected resource already there error")
|
||||
}
|
||||
if err := w.Remove(cm5.Id()); err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if err := w.Append(cm700); err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if err := w.Append(cm5); err == nil {
|
||||
t.Fatalf("expected err; object is still there under id 888")
|
||||
}
|
||||
if err := w.Remove(cm888.Id()); err != nil {
|
||||
t.Fatalf("unexpected err: %v", err)
|
||||
}
|
||||
if err := w.Append(cm5); err != nil {
|
||||
t.Fatalf("unexpected err; %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeAsYaml(t *testing.T) {
|
||||
encoded := []byte(`apiVersion: v1
|
||||
kind: ConfigMap
|
||||
@@ -45,7 +161,7 @@ kind: ConfigMap
|
||||
metadata:
|
||||
name: cm2
|
||||
`)
|
||||
input := ResMap{
|
||||
input := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -62,7 +178,7 @@ metadata:
|
||||
"name": "cm2",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
out, err := input.EncodeAsYaml()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
@@ -73,7 +189,7 @@ metadata:
|
||||
}
|
||||
|
||||
func TestDemandOneGvknMatchForId(t *testing.T) {
|
||||
rm1 := ResMap{
|
||||
rm1 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixNamespace(cmap, "cm1", "prefix1", "ns1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -90,7 +206,7 @@ func TestDemandOneGvknMatchForId(t *testing.T) {
|
||||
"name": "cm2",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
result := rm1.GetMatchingIds(
|
||||
resid.NewResIdWithPrefixNamespace(cmap, "cm2", "prefix1", "ns1").GvknEquals)
|
||||
@@ -129,135 +245,147 @@ func TestFilterBy(t *testing.T) {
|
||||
expected ResMap
|
||||
}{
|
||||
"different namespace": {
|
||||
resMap: ResMap{resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix", "suffix", "namespace1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
},
|
||||
resMap: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix", "suffix", "namespace1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
filter: resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix", "suffix", "namespace2"),
|
||||
expected: ResMap{},
|
||||
expected: New(),
|
||||
},
|
||||
"different prefix": {
|
||||
resMap: ResMap{resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix1", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
},
|
||||
resMap: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix1", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
filter: resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix2", "suffix", "namespace"),
|
||||
expected: ResMap{},
|
||||
expected: New(),
|
||||
},
|
||||
"different suffix": {
|
||||
resMap: ResMap{resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix", "suffix1", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
},
|
||||
resMap: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix", "suffix1", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
filter: resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map", "prefix", "suffix2", "namespace"),
|
||||
expected: ResMap{},
|
||||
expected: New(),
|
||||
},
|
||||
"same namespace, same prefix": {
|
||||
resMap: ResMap{resid.NewResIdWithPrefixNamespace(cmap, "config-map1", "prefix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map1",
|
||||
},
|
||||
}),
|
||||
},
|
||||
resMap: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixNamespace(cmap, "config-map1", "prefix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map1",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
filter: resid.NewResIdWithPrefixNamespace(cmap, "config-map2", "prefix", "namespace"),
|
||||
expected: ResMap{resid.NewResIdWithPrefixNamespace(cmap, "config-map1", "prefix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map1",
|
||||
},
|
||||
}),
|
||||
},
|
||||
expected: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixNamespace(cmap, "config-map1", "prefix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map1",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
"same namespace, same suffix": {
|
||||
resMap: ResMap{resid.NewResIdWithSuffixNamespace(cmap, "config-map1", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map1",
|
||||
},
|
||||
}),
|
||||
},
|
||||
resMap: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithSuffixNamespace(cmap, "config-map1", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map1",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
filter: resid.NewResIdWithSuffixNamespace(cmap, "config-map2", "suffix", "namespace"),
|
||||
expected: ResMap{resid.NewResIdWithSuffixNamespace(cmap, "config-map1", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map1",
|
||||
},
|
||||
}),
|
||||
},
|
||||
expected: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithSuffixNamespace(cmap, "config-map1", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map1",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
"same namespace, same prefix, same suffix": {
|
||||
resMap: ResMap{resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map1", "prefix", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
},
|
||||
resMap: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map1", "prefix", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
filter: resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map2", "prefix", "suffix", "namespace"),
|
||||
expected: ResMap{resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map1", "prefix", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
},
|
||||
expected: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixSuffixNamespace(cmap, "config-map1", "prefix", "suffix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
"filter by cluster-level Gvk": {
|
||||
resMap: ResMap{resid.NewResIdWithPrefixNamespace(cmap, "config-map", "prefix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
},
|
||||
resMap: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixNamespace(cmap, "config-map", "prefix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
filter: resid.NewResId(gvk.Gvk{Kind: "ClusterRoleBinding"}, "cluster-role-binding"),
|
||||
expected: ResMap{resid.NewResIdWithPrefixNamespace(cmap, "config-map", "prefix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
},
|
||||
expected: FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResIdWithPrefixNamespace(cmap, "config-map", "prefix", "namespace"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "config-map",
|
||||
},
|
||||
}),
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
test := test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := test.resMap.FilterBy(test.filter)
|
||||
if !reflect.DeepEqual(test.expected, got) {
|
||||
got := test.resMap.ResourcesThatCouldReference(test.filter)
|
||||
err := test.expected.ErrorIfNotEqual(got)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected %v but got back %v", test.expected, got)
|
||||
}
|
||||
})
|
||||
@@ -265,7 +393,7 @@ func TestFilterBy(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDeepCopy(t *testing.T) {
|
||||
rm1 := ResMap{
|
||||
rm1 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -282,46 +410,63 @@ func TestDeepCopy(t *testing.T) {
|
||||
"name": "cm2",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
rm2 := rm1.DeepCopy(rf)
|
||||
rm2 := rm1.DeepCopy()
|
||||
|
||||
if &rm1 == &rm2 {
|
||||
t.Fatal("DeepCopy returned a reference to itself instead of a copy")
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(rm1, rm2) {
|
||||
t.Fatalf("%v doesn't equal it's deep copy %v", rm1, rm2)
|
||||
err := rm1.ErrorIfNotEqual(rm1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMatchingIds(t *testing.T) {
|
||||
// These ids used as map keys.
|
||||
// They must be different to avoid overwriting
|
||||
// map entries during construction.
|
||||
ids := []resid.ResId{
|
||||
|
||||
m := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(
|
||||
gvk.Gvk{Kind: "vegetable"},
|
||||
"bedlam"),
|
||||
"bedlam"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "whatever1",
|
||||
},
|
||||
}),
|
||||
resid.NewResId(
|
||||
gvk.Gvk{Group: "g1", Version: "v1", Kind: "vegetable"},
|
||||
"domino"),
|
||||
"domino"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "whatever2",
|
||||
},
|
||||
}),
|
||||
resid.NewResIdWithPrefixNamespace(
|
||||
gvk.Gvk{Kind: "vegetable"},
|
||||
"peter", "p", "happy"),
|
||||
"peter", "p", "happy"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "whatever3",
|
||||
},
|
||||
}),
|
||||
resid.NewResIdWithPrefixNamespace(
|
||||
gvk.Gvk{Version: "v1", Kind: "fruit"},
|
||||
"shatterstar", "p", "happy"),
|
||||
}
|
||||
|
||||
m := ResMap{}
|
||||
for _, id := range ids {
|
||||
// Resources values don't matter in this test.
|
||||
m[id] = nil
|
||||
}
|
||||
if len(m) != len(ids) {
|
||||
t.Fatalf("unexpected map len %d presumably due to duplicate keys", len(m))
|
||||
}
|
||||
"shatterstar", "p", "happy"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "whatever4",
|
||||
},
|
||||
}),
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -362,7 +507,7 @@ func TestGetMatchingIds(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestErrorIfNotEqual(t *testing.T) {
|
||||
rm1 := ResMap{
|
||||
rm1 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -379,14 +524,14 @@ func TestErrorIfNotEqual(t *testing.T) {
|
||||
"name": "cm2",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
err := rm1.ErrorIfNotEqual(rm1)
|
||||
if err != nil {
|
||||
t.Fatalf("%v should equal itself %v", rm1, err)
|
||||
}
|
||||
|
||||
rm2 := ResMap{
|
||||
rm2 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -395,7 +540,7 @@ func TestErrorIfNotEqual(t *testing.T) {
|
||||
"name": "cm1",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
// test the different number of keys path
|
||||
err = rm1.ErrorIfNotEqual(rm2)
|
||||
@@ -403,7 +548,7 @@ func TestErrorIfNotEqual(t *testing.T) {
|
||||
t.Fatalf("%v should not equal %v %v", rm1, rm2, err)
|
||||
}
|
||||
|
||||
rm3 := ResMap{
|
||||
rm3 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cm2"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -412,7 +557,7 @@ func TestErrorIfNotEqual(t *testing.T) {
|
||||
"name": "cm1",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
// test the different key values path
|
||||
err = rm2.ErrorIfNotEqual(rm3)
|
||||
@@ -420,7 +565,7 @@ func TestErrorIfNotEqual(t *testing.T) {
|
||||
t.Fatalf("%v should not equal %v %v", rm1, rm2, err)
|
||||
}
|
||||
|
||||
rm4 := ResMap{
|
||||
rm4 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cm1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
@@ -429,7 +574,7 @@ func TestErrorIfNotEqual(t *testing.T) {
|
||||
"name": "cm3",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
// test the deepcopy path
|
||||
err = rm2.ErrorIfNotEqual(rm4)
|
||||
@@ -440,7 +585,7 @@ func TestErrorIfNotEqual(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMergeWithoutOverride(t *testing.T) {
|
||||
input1 := ResMap{
|
||||
input1 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
@@ -449,8 +594,8 @@ func TestMergeWithoutOverride(t *testing.T) {
|
||||
"name": "foo-deploy1",
|
||||
},
|
||||
}),
|
||||
}
|
||||
input2 := ResMap{
|
||||
})
|
||||
input2 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(statefulset, "stateful1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
@@ -459,9 +604,9 @@ func TestMergeWithoutOverride(t *testing.T) {
|
||||
"name": "bar-stateful",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
input := []ResMap{input1, input2}
|
||||
expected := ResMap{
|
||||
expected := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(deploy, "deploy1"): rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
@@ -478,12 +623,13 @@ func TestMergeWithoutOverride(t *testing.T) {
|
||||
"name": "bar-stateful",
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
merged, err := MergeWithErrorOnIdCollision(input...)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(merged, expected) {
|
||||
err = expected.ErrorIfNotEqual(merged)
|
||||
if err != nil {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", merged, expected)
|
||||
}
|
||||
input3 := []ResMap{merged, nil}
|
||||
@@ -491,21 +637,23 @@ func TestMergeWithoutOverride(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(merged1, expected) {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", merged1, expected)
|
||||
err = expected.ErrorIfNotEqual(merged1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
input4 := []ResMap{nil, merged}
|
||||
merged2, err := MergeWithErrorOnIdCollision(input4...)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(merged2, expected) {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", merged2, expected)
|
||||
err = expected.ErrorIfNotEqual(merged2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func generateMergeFixtures(b types.GenerationBehavior) []ResMap {
|
||||
input1 := ResMap{
|
||||
input1 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cmap"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
@@ -520,8 +668,8 @@ func generateMergeFixtures(b types.GenerationBehavior) []ResMap {
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: "create",
|
||||
}, nil),
|
||||
}
|
||||
input2 := ResMap{
|
||||
})
|
||||
input2 := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cmap"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
@@ -537,12 +685,12 @@ func generateMergeFixtures(b types.GenerationBehavior) []ResMap {
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: b.String(),
|
||||
}, nil),
|
||||
}
|
||||
})
|
||||
return []ResMap{input1, input2}
|
||||
}
|
||||
|
||||
func TestMergeWithOverride(t *testing.T) {
|
||||
expected := ResMap{
|
||||
expected := FromMap(map[resid.ResId]*resource.Resource{
|
||||
resid.NewResId(cmap, "cmap"): rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
@@ -560,41 +708,43 @@ func TestMergeWithOverride(t *testing.T) {
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: "create",
|
||||
}, nil),
|
||||
}
|
||||
})
|
||||
merged, err := MergeWithOverride(generateMergeFixtures(types.BehaviorMerge)...)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(merged, expected) {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", merged, expected)
|
||||
err = expected.ErrorIfNotEqual(merged)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
input3 := []ResMap{merged, nil}
|
||||
merged1, err := MergeWithOverride(input3...)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(merged1, expected) {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", merged1, expected)
|
||||
err = expected.ErrorIfNotEqual(merged1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
input4 := []ResMap{nil, merged}
|
||||
merged2, err := MergeWithOverride(input4...)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(merged2, expected) {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", merged2, expected)
|
||||
err = expected.ErrorIfNotEqual(merged2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
inputs := generateMergeFixtures(types.BehaviorReplace)
|
||||
replaced, err := MergeWithOverride(inputs...)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
expectedReplaced := inputs[1]
|
||||
if !reflect.DeepEqual(replaced, expectedReplaced) {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", replaced, expectedReplaced)
|
||||
err = expectedReplaced.ErrorIfNotEqual(replaced)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = MergeWithOverride(generateMergeFixtures(types.BehaviorUnspecified)...)
|
||||
if err == nil {
|
||||
t.Fatal("Merging with GenerationBehavior BehaviorUnspecified should return an error but does not")
|
||||
|
||||
Reference in New Issue
Block a user