mirror of
https://github.com/kubernetes-sigs/kustomize.git
synced 2026-05-17 18:25:26 +00:00
Split reswrangler.go and test from resmap.go
This commit is contained in:
@@ -38,8 +38,7 @@ type Loader interface {
|
||||
Cleanup() error
|
||||
}
|
||||
|
||||
// Kunstructured allows manipulation of k8s objects
|
||||
// that do not have Golang structs.
|
||||
// Kunstructured represents a Kubernetes Resource Model object.
|
||||
type Kunstructured interface {
|
||||
// Several uses.
|
||||
Copy() Kunstructured
|
||||
|
||||
@@ -11,9 +11,18 @@ import (
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
// Merginator merges resources.
|
||||
type Merginator interface {
|
||||
// Merge creates a new ResMap by merging incoming resources.
|
||||
// Error if conflict found.
|
||||
Merge([]*resource.Resource) (ResMap, error)
|
||||
}
|
||||
|
||||
// Factory makes instances of ResMap.
|
||||
type Factory struct {
|
||||
// Makes resources.
|
||||
resF *resource.Factory
|
||||
// Makes ResMaps via merging.
|
||||
pm Merginator
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resmap
|
||||
|
||||
import "sigs.k8s.io/kustomize/api/resource"
|
||||
|
||||
// Merginator merges resources.
|
||||
type Merginator interface {
|
||||
// Merge creates a new ResMap by merging incoming resources.
|
||||
// Error if conflict found.
|
||||
Merge([]*resource.Resource) (ResMap, error)
|
||||
}
|
||||
@@ -6,16 +6,10 @@
|
||||
package resmap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/ifc"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// A Transformer modifies an instance of ResMap.
|
||||
@@ -242,569 +236,3 @@ type ResMap interface {
|
||||
// are selected by a Selector
|
||||
Select(types.Selector) ([]*resource.Resource, error)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func newOne() *resWrangler {
|
||||
result := &resWrangler{}
|
||||
result.Clear()
|
||||
return result
|
||||
}
|
||||
|
||||
// Clear implements ResMap.
|
||||
func (m *resWrangler) Clear() {
|
||||
m.rList = nil
|
||||
}
|
||||
|
||||
// Size implements ResMap.
|
||||
func (m *resWrangler) Size() int {
|
||||
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
|
||||
}
|
||||
|
||||
// Append implements ResMap.
|
||||
func (m *resWrangler) Append(res *resource.Resource) error {
|
||||
id := res.CurId()
|
||||
if r := m.GetMatchingResourcesByCurrentId(id.Equals); len(r) > 0 {
|
||||
return fmt.Errorf(
|
||||
"may not add resource with an already registered id: %s", id)
|
||||
}
|
||||
m.rList = append(m.rList, res)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove implements ResMap.
|
||||
func (m *resWrangler) Remove(adios resid.ResId) error {
|
||||
tmp := newOne()
|
||||
for _, r := range m.rList {
|
||||
if r.CurId() != adios {
|
||||
tmp.Append(r)
|
||||
}
|
||||
}
|
||||
if tmp.Size() != m.Size()-1 {
|
||||
return fmt.Errorf("id %s not found in removal", adios)
|
||||
}
|
||||
m.rList = tmp.rList
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace implements ResMap.
|
||||
func (m *resWrangler) Replace(res *resource.Resource) (int, error) {
|
||||
id := res.CurId()
|
||||
i, err := m.GetIndexOfCurrentId(id)
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err, "in Replace")
|
||||
}
|
||||
if i < 0 {
|
||||
return -1, fmt.Errorf("cannot find resource with id %s to replace", id)
|
||||
}
|
||||
m.rList[i] = res
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// AllIds implements ResMap.
|
||||
func (m *resWrangler) AllIds() (ids []resid.ResId) {
|
||||
ids = make([]resid.ResId, m.Size())
|
||||
for i, r := range m.rList {
|
||||
ids[i] = r.CurId()
|
||||
}
|
||||
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, r.OrgId())
|
||||
blob, err := yaml.Marshal(r.Map())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(blob))
|
||||
}
|
||||
}
|
||||
|
||||
type IdMatcher func(resid.ResId) bool
|
||||
|
||||
// GetByIndex implements ResMap.
|
||||
func (m *resWrangler) GetByIndex(i int) *resource.Resource {
|
||||
if i < 0 || i >= m.Size() {
|
||||
return nil
|
||||
}
|
||||
return m.rList[i]
|
||||
}
|
||||
|
||||
// GetIndexOfCurrentId implements ResMap.
|
||||
func (m *resWrangler) GetIndexOfCurrentId(id resid.ResId) (int, error) {
|
||||
count := 0
|
||||
result := -1
|
||||
for i, r := range m.rList {
|
||||
if id.Equals(r.CurId()) {
|
||||
count++
|
||||
result = i
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
return -1, fmt.Errorf("id matched %d resources", count)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type IdFromResource func(r *resource.Resource) resid.ResId
|
||||
|
||||
func GetOriginalId(r *resource.Resource) resid.ResId { return r.OrgId() }
|
||||
func GetCurrentId(r *resource.Resource) resid.ResId { return r.CurId() }
|
||||
|
||||
// GetMatchingResourcesByCurrentId implements ResMap.
|
||||
func (m *resWrangler) GetMatchingResourcesByCurrentId(
|
||||
matches IdMatcher) []*resource.Resource {
|
||||
return m.filteredById(matches, GetCurrentId)
|
||||
}
|
||||
|
||||
// GetMatchingResourcesByOriginalId implements ResMap.
|
||||
func (m *resWrangler) GetMatchingResourcesByOriginalId(
|
||||
matches IdMatcher) []*resource.Resource {
|
||||
return m.filteredById(matches, GetOriginalId)
|
||||
}
|
||||
|
||||
func (m *resWrangler) filteredById(
|
||||
matches IdMatcher, idGetter IdFromResource) []*resource.Resource {
|
||||
var result []*resource.Resource
|
||||
for _, r := range m.rList {
|
||||
if matches(idGetter(r)) {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetByCurrentId implements ResMap.
|
||||
func (m *resWrangler) GetByCurrentId(
|
||||
id resid.ResId) (*resource.Resource, error) {
|
||||
return demandOneMatch(m.GetMatchingResourcesByCurrentId, id, "Current")
|
||||
}
|
||||
|
||||
// GetByOriginalId implements ResMap.
|
||||
func (m *resWrangler) GetByOriginalId(
|
||||
id resid.ResId) (*resource.Resource, error) {
|
||||
return demandOneMatch(m.GetMatchingResourcesByOriginalId, id, "Original")
|
||||
}
|
||||
|
||||
// GetById implements ResMap.
|
||||
func (m *resWrangler) GetById(
|
||||
id resid.ResId) (*resource.Resource, error) {
|
||||
match, err1 := m.GetByOriginalId(id)
|
||||
if err1 == nil {
|
||||
return match, nil
|
||||
}
|
||||
match, err2 := m.GetByCurrentId(id)
|
||||
if err2 == nil {
|
||||
return match, nil
|
||||
}
|
||||
return nil, fmt.Errorf(
|
||||
"%s; %s; failed to find unique target for patch %s",
|
||||
err1.Error(), err2.Error(), id.GvknString())
|
||||
}
|
||||
|
||||
type resFinder func(IdMatcher) []*resource.Resource
|
||||
|
||||
func demandOneMatch(
|
||||
f resFinder, id resid.ResId, s string) (*resource.Resource, error) {
|
||||
r := f(id.Equals)
|
||||
if len(r) == 1 {
|
||||
return r[0], nil
|
||||
}
|
||||
if len(r) > 1 {
|
||||
return nil, fmt.Errorf("multiple matches for %sId %s", s, id)
|
||||
}
|
||||
return nil, fmt.Errorf("no matches for %sId %s", s, id)
|
||||
}
|
||||
|
||||
// GroupedByCurrentNamespace implements ResMap.GroupByCurrentNamespace
|
||||
func (m *resWrangler) GroupedByCurrentNamespace() map[string][]*resource.Resource {
|
||||
items := m.groupedByCurrentNamespace()
|
||||
delete(items, resid.TotallyNotANamespace)
|
||||
return items
|
||||
}
|
||||
|
||||
// NonNamespaceable implements ResMap.NonNamespaceable
|
||||
func (m *resWrangler) NonNamespaceable() []*resource.Resource {
|
||||
return m.groupedByCurrentNamespace()[resid.TotallyNotANamespace]
|
||||
}
|
||||
|
||||
func (m *resWrangler) groupedByCurrentNamespace() map[string][]*resource.Resource {
|
||||
byNamespace := make(map[string][]*resource.Resource)
|
||||
for _, res := range m.rList {
|
||||
namespace := res.CurId().EffectiveNamespace()
|
||||
if _, found := byNamespace[namespace]; !found {
|
||||
byNamespace[namespace] = []*resource.Resource{}
|
||||
}
|
||||
byNamespace[namespace] = append(byNamespace[namespace], res)
|
||||
}
|
||||
return byNamespace
|
||||
}
|
||||
|
||||
// GroupedByNamespace implements ResMap.GroupByOrginalNamespace
|
||||
func (m *resWrangler) GroupedByOriginalNamespace() map[string][]*resource.Resource {
|
||||
items := m.groupedByOriginalNamespace()
|
||||
delete(items, resid.TotallyNotANamespace)
|
||||
return items
|
||||
}
|
||||
|
||||
func (m *resWrangler) groupedByOriginalNamespace() map[string][]*resource.Resource {
|
||||
byNamespace := make(map[string][]*resource.Resource)
|
||||
for _, res := range m.rList {
|
||||
namespace := res.OrgId().EffectiveNamespace()
|
||||
if _, found := byNamespace[namespace]; !found {
|
||||
byNamespace[namespace] = []*resource.Resource{}
|
||||
}
|
||||
byNamespace[namespace] = append(byNamespace[namespace], res)
|
||||
}
|
||||
return byNamespace
|
||||
}
|
||||
|
||||
// AsYaml implements ResMap.
|
||||
func (m *resWrangler) AsYaml() ([]byte, error) {
|
||||
firstObj := true
|
||||
var b []byte
|
||||
buf := bytes.NewBuffer(b)
|
||||
for _, res := range m.Resources() {
|
||||
out, err := yaml.Marshal(res.Map())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if firstObj {
|
||||
firstObj = false
|
||||
} else {
|
||||
if _, err = buf.WriteString("---\n"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err = buf.Write(out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ErrorIfNotEqualSets implements ResMap.
|
||||
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
||||
m2, ok := other.(*resWrangler)
|
||||
if !ok {
|
||||
panic("bad cast")
|
||||
}
|
||||
if m.Size() != m2.Size() {
|
||||
return fmt.Errorf(
|
||||
"lists have different number of entries: %#v doesn't equal %#v",
|
||||
m.rList, m2.rList)
|
||||
}
|
||||
seen := make(map[int]bool)
|
||||
for _, r1 := range m.rList {
|
||||
id := r1.CurId()
|
||||
others := m2.GetMatchingResourcesByCurrentId(id.Equals)
|
||||
if len(others) == 0 {
|
||||
return fmt.Errorf(
|
||||
"id in self missing from other; id: %s", id)
|
||||
}
|
||||
if len(others) > 1 {
|
||||
return fmt.Errorf(
|
||||
"id in self matches %d in other; id: %s", len(others), id)
|
||||
}
|
||||
r2 := others[0]
|
||||
if !r1.KunstructEqual(r2) {
|
||||
return fmt.Errorf(
|
||||
"kunstruct not equal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
||||
r1, r2, r1, r2)
|
||||
}
|
||||
seen[m2.indexOfResource(r2)] = true
|
||||
}
|
||||
if len(seen) != m.Size() {
|
||||
return fmt.Errorf("counting problem %d != %d", len(seen), m.Size())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrorIfNotEqualList implements ResMap.
|
||||
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
||||
m2, ok := other.(*resWrangler)
|
||||
if !ok {
|
||||
panic("bad cast")
|
||||
}
|
||||
if m.Size() != m2.Size() {
|
||||
return fmt.Errorf(
|
||||
"lists have different number of entries: %#v doesn't equal %#v",
|
||||
m.rList, m2.rList)
|
||||
}
|
||||
for i, r1 := range m.rList {
|
||||
r2 := m2.rList[i]
|
||||
if !r1.Equals(r2) {
|
||||
return fmt.Errorf(
|
||||
"Item i=%d differs:\n n1 = %s\n n2 = %s\n o1 = %s\n o2 = %s\n",
|
||||
i, r1.OrgId(), r2.OrgId(), r1, r2)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// 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.rList = make([]*resource.Resource, m.Size())
|
||||
for i, r := range m.rList {
|
||||
result.rList[i] = copier(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SubsetThatCouldBeReferencedByResource implements ResMap.
|
||||
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
||||
inputRes *resource.Resource) ResMap {
|
||||
result := newOne()
|
||||
inputId := inputRes.CurId()
|
||||
isInputIdNamespaceable := inputId.IsNamespaceableKind()
|
||||
rctxm := inputRes.PrefixesSuffixesEquals
|
||||
subjectNamespaces := getNamespacesForRoleBinding(inputRes)
|
||||
for _, r := range m.Resources() {
|
||||
// Need to match more accuratly both at the time of selection and transformation.
|
||||
// OutmostPrefixSuffixEquals is not accurate enough since it is only using
|
||||
// the outer most suffix and the last prefix. Use PrefixedSuffixesEquals instead.
|
||||
resId := r.CurId()
|
||||
if (!isInputIdNamespaceable || !resId.IsNamespaceableKind() || resId.IsNsEquals(inputId) ||
|
||||
isRoleBindingNamespace(&subjectNamespaces, r.GetNamespace())) && r.InSameKustomizeCtx(rctxm) {
|
||||
result.append(r)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// isRoleBindingNamespace returns true is the namespace `ns` is in role binding
|
||||
// namespaces `m`
|
||||
func isRoleBindingNamespace(m *map[string]bool, ns string) bool {
|
||||
return (*m)[ns]
|
||||
}
|
||||
|
||||
// getNamespacesForRoleBinding returns referenced ServiceAccount namespaces if the inputRes is
|
||||
// a RoleBinding
|
||||
func getNamespacesForRoleBinding(inputRes *resource.Resource) map[string]bool {
|
||||
res := make(map[string]bool)
|
||||
if inputRes.GetKind() != "RoleBinding" {
|
||||
return res
|
||||
}
|
||||
subjects, err := inputRes.GetSlice("subjects")
|
||||
if err != nil || subjects == nil {
|
||||
return res
|
||||
}
|
||||
|
||||
for _, s := range subjects {
|
||||
subject := s.(map[string]interface{})
|
||||
if subject["namespace"] == nil || subject["kind"] == nil ||
|
||||
subject["kind"].(string) != "ServiceAccount" {
|
||||
continue
|
||||
}
|
||||
res[subject["namespace"].(string)] = true
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *resWrangler) append(res *resource.Resource) {
|
||||
m.rList = append(m.rList, res)
|
||||
}
|
||||
|
||||
// AppendAll implements ResMap.
|
||||
func (m *resWrangler) AppendAll(other ResMap) error {
|
||||
if other == nil {
|
||||
return nil
|
||||
}
|
||||
for _, res := range other.Resources() {
|
||||
if err := m.Append(res); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AbsorbAll implements ResMap.
|
||||
func (m *resWrangler) AbsorbAll(other ResMap) error {
|
||||
if other == nil {
|
||||
return nil
|
||||
}
|
||||
for _, r := range other.Resources() {
|
||||
err := m.appendReplaceOrMerge(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *resWrangler) appendReplaceOrMerge(
|
||||
res *resource.Resource) error {
|
||||
id := res.CurId()
|
||||
matches := m.GetMatchingResourcesByOriginalId(id.Equals)
|
||||
if len(matches) == 0 {
|
||||
matches = m.GetMatchingResourcesByCurrentId(id.Equals)
|
||||
}
|
||||
switch len(matches) {
|
||||
case 0:
|
||||
switch res.Behavior() {
|
||||
case types.BehaviorMerge, types.BehaviorReplace:
|
||||
return fmt.Errorf(
|
||||
"id %#v does not exist; cannot merge or replace", id)
|
||||
default:
|
||||
// presumably types.BehaviorCreate
|
||||
err := m.Append(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
old := matches[0]
|
||||
if old == nil {
|
||||
return fmt.Errorf("id lookup failure")
|
||||
}
|
||||
index := m.indexOfResource(old)
|
||||
if index < 0 {
|
||||
return fmt.Errorf("indexing problem")
|
||||
}
|
||||
switch res.Behavior() {
|
||||
case types.BehaviorReplace:
|
||||
res.Replace(old)
|
||||
case types.BehaviorMerge:
|
||||
res.Merge(old)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"id %#v exists; behavior must be merge or replace", id)
|
||||
}
|
||||
i, err := m.Replace(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i != index {
|
||||
return fmt.Errorf("unexpected index in replacement")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"found multiple objects %v that could accept merge of %v",
|
||||
matches, id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func anchorRegex(pattern string) string {
|
||||
if pattern == "" {
|
||||
return pattern
|
||||
}
|
||||
return "^" + pattern + "$"
|
||||
}
|
||||
|
||||
// Select returns a list of resources that
|
||||
// are selected by a Selector
|
||||
func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
||||
ns := regexp.MustCompile(anchorRegex(s.Namespace))
|
||||
nm := regexp.MustCompile(anchorRegex(s.Name))
|
||||
var result []*resource.Resource
|
||||
for _, r := range m.Resources() {
|
||||
curId := r.CurId()
|
||||
orgId := r.OrgId()
|
||||
|
||||
// matches the namespace when namespace is not empty in the selector
|
||||
// It first tries to match with the original namespace
|
||||
// then matches with the current namespace
|
||||
if r.GetNamespace() != "" {
|
||||
matched := ns.MatchString(orgId.EffectiveNamespace())
|
||||
if !matched {
|
||||
matched = ns.MatchString(curId.EffectiveNamespace())
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// matches the name when name is not empty in the selector
|
||||
// It first tries to match with the original name
|
||||
// then matches with the current name
|
||||
if r.GetName() != "" {
|
||||
matched := nm.MatchString(orgId.Name)
|
||||
if !matched {
|
||||
matched = nm.MatchString(curId.Name)
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// matches the GVK
|
||||
if !r.GetGvk().IsSelected(&s.Gvk) {
|
||||
continue
|
||||
}
|
||||
|
||||
// matches the label selector
|
||||
matched, err := r.MatchesLabelSelector(s.LabelSelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
// matches the annotation selector
|
||||
matched, err = r.MatchesAnnotationSelector(s.AnnotationSelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
result = append(result, r)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -3,899 +3,4 @@
|
||||
|
||||
package resmap_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
. "sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
var rf = resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
var rmF = NewFactory(rf, nil)
|
||||
|
||||
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),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Maintain the class invariant that no two
|
||||
// resources can have the same CurId().
|
||||
func TestAppendRejectsDuplicateResId(t *testing.T) {
|
||||
w := New()
|
||||
if err := w.Append(makeCm(1)); err != nil {
|
||||
t.Fatalf("append error: %v", err)
|
||||
}
|
||||
err := w.Append(makeCm(1))
|
||||
if err == nil {
|
||||
t.Fatalf("expected append error")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(),
|
||||
"may not add resource with an already registered id") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
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).OrgId())
|
||||
doRemove(t, w1, makeCm(3).OrgId())
|
||||
doRemove(t, w1, makeCm(5).OrgId())
|
||||
doRemove(t, w1, makeCm(7).OrgId())
|
||||
|
||||
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.OrgId())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
err = w.Append(r)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = w.Remove(r.OrgId())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = w.Remove(r.OrgId())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
cm5 := makeCm(5)
|
||||
cm700 := makeCm(700)
|
||||
otherCm5 := makeCm(5)
|
||||
|
||||
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.Replace(otherCm5)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if w.Size() != oldSize {
|
||||
t.Fatalf("unexpected size %d", w.Size())
|
||||
}
|
||||
if r, err := w.GetByCurrentId(cm5.OrgId()); err != nil || r != otherCm5 {
|
||||
t.Fatalf("unexpected result r=%s, err=%v", r.CurId(), err)
|
||||
}
|
||||
if err := w.Append(cm5); err == nil {
|
||||
t.Fatalf("expected id already there error")
|
||||
}
|
||||
if err := w.Remove(cm5.OrgId()); 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("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeAsYaml(t *testing.T) {
|
||||
encoded := []byte(`apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm2
|
||||
`)
|
||||
input := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
}).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
}).ResMap()
|
||||
out, err := input.AsYaml()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(out, encoded) {
|
||||
t.Fatalf("%s doesn't match expected %s", out, encoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMatchingResourcesByCurrentId(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "alice",
|
||||
},
|
||||
})
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bob",
|
||||
},
|
||||
})
|
||||
r3 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bob",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r4 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r5 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).ResMap()
|
||||
|
||||
result := m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResId(cmap, "alice").GvknEquals)
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("Expected single map entry but got %v", result)
|
||||
}
|
||||
result = m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResId(cmap, "bob").GvknEquals)
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("Expected two, got %v", result)
|
||||
}
|
||||
result = m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResIdWithNamespace(cmap, "bob", "system").GvknEquals)
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("Expected two but got %v", result)
|
||||
}
|
||||
result = m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResIdWithNamespace(cmap, "bob", "happy").Equals)
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("Expected single map entry but got %v", result)
|
||||
}
|
||||
result = m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResId(cmap, "charlie").GvknEquals)
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("Expected single map entry but got %v", result)
|
||||
}
|
||||
|
||||
// nolint:goconst
|
||||
tests := []struct {
|
||||
name string
|
||||
matcher IdMatcher
|
||||
count int
|
||||
}{
|
||||
{
|
||||
"match everything",
|
||||
func(resid.ResId) bool { return true },
|
||||
5,
|
||||
},
|
||||
{
|
||||
"match nothing",
|
||||
func(resid.ResId) bool { return false },
|
||||
0,
|
||||
},
|
||||
{
|
||||
"name is alice",
|
||||
func(x resid.ResId) bool { return x.Name == "alice" },
|
||||
1,
|
||||
},
|
||||
{
|
||||
"name is charlie",
|
||||
func(x resid.ResId) bool { return x.Name == "charlie" },
|
||||
2,
|
||||
},
|
||||
{
|
||||
"name is bob",
|
||||
func(x resid.ResId) bool { return x.Name == "bob" },
|
||||
2,
|
||||
},
|
||||
{
|
||||
"happy namespace",
|
||||
func(x resid.ResId) bool {
|
||||
return x.Namespace == "happy"
|
||||
},
|
||||
3,
|
||||
},
|
||||
{
|
||||
"happy deployment",
|
||||
func(x resid.ResId) bool {
|
||||
return x.Namespace == "happy" &&
|
||||
x.Gvk.Kind == "Deployment"
|
||||
},
|
||||
1,
|
||||
},
|
||||
{
|
||||
"happy ConfigMap",
|
||||
func(x resid.ResId) bool {
|
||||
return x.Namespace == "happy" &&
|
||||
x.Gvk.Kind == "ConfigMap"
|
||||
},
|
||||
2,
|
||||
},
|
||||
}
|
||||
for _, tst := range tests {
|
||||
result := m.GetMatchingResourcesByCurrentId(tst.matcher)
|
||||
if len(result) != tst.count {
|
||||
t.Fatalf("test '%s'; actual: %d, expected: %d",
|
||||
tst.name, len(result), tst.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "alice",
|
||||
},
|
||||
})
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bob",
|
||||
},
|
||||
})
|
||||
r3 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bob",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r4 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r5 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r5.AddNamePrefix("little-")
|
||||
r6 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "domino",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r6.AddNamePrefix("little-")
|
||||
r7 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "meh",
|
||||
},
|
||||
})
|
||||
|
||||
tests := map[string]struct {
|
||||
filter *resource.Resource
|
||||
expected ResMap
|
||||
}{
|
||||
"default namespace 1": {
|
||||
filter: r2,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
||||
},
|
||||
"default namespace 2": {
|
||||
filter: r1,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
||||
},
|
||||
"happy namespace no prefix": {
|
||||
filter: r3,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||
},
|
||||
"happy namespace with prefix": {
|
||||
filter: r5,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||
},
|
||||
"cluster level": {
|
||||
filter: r7,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||
},
|
||||
}
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap()
|
||||
for name, test := range tests {
|
||||
test := test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := m.SubsetThatCouldBeReferencedByResource(test.filter)
|
||||
err := test.expected.ErrorIfNotEqualLists(got)
|
||||
if err != nil {
|
||||
test.expected.Debug("expected")
|
||||
got.Debug("actual")
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func addPfxSfx(r *resource.Resource, prefixes []string, suffixes []string) {
|
||||
for _, pfx := range prefixes {
|
||||
r.AddNamePrefix(pfx)
|
||||
}
|
||||
|
||||
for _, sfx := range suffixes {
|
||||
r.AddNameSuffix(sfx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubsetThatCouldBeReferencedByResourceMultiLevel(t *testing.T) {
|
||||
// Simulates ConfigMap and Deployment defined at level 1
|
||||
// No prefix nor suffix added at that level
|
||||
cm1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level1",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm1, []string{""}, []string{""})
|
||||
dep1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level1",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep1, []string{""}, []string{""})
|
||||
|
||||
// Simulates ConfigMap and Deployment defined at level 1
|
||||
// and prefix added in level 2 of kustomization
|
||||
cm2p := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level2p",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm2p, []string{"", "level2p-"}, []string{"", ""})
|
||||
|
||||
dep2p := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level2p",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep2p, []string{"", "level2p-"}, []string{"", ""})
|
||||
|
||||
// Simulates ConfigMap and Deployment defined at level 1
|
||||
// and suffix added in level 2 of kustomization
|
||||
cm2s := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level2s",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm2s, []string{"", ""}, []string{"", "-level2s"})
|
||||
|
||||
dep2s := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level2s",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep2s, []string{"", ""}, []string{"", "-level2s"})
|
||||
|
||||
// Simulates ConfigMap and Deployment defined at level 1,
|
||||
// prefix added in levels 2 and 3 of kustomization.
|
||||
cm3e := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level3e",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm3e, []string{"", "level2p-", "level3e-"}, []string{"", "", ""})
|
||||
|
||||
dep3e := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level3e",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep3e, []string{"", "level2p-", "level3e-"}, []string{"", "", ""})
|
||||
|
||||
// Simulates Deployment defined at level 1, ConfigMap defined at level 2,
|
||||
// prefix added in levels 2 and 3 of kustomization.
|
||||
// This reproduce issue 1440.
|
||||
cm3i := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level3i",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm3i, []string{"level2p-", "level3i-"}, []string{"", ""})
|
||||
|
||||
dep3i := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level3i",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep3i, []string{"", "level2p-", "level3i-"}, []string{"", "", ""})
|
||||
|
||||
tests := map[string]struct {
|
||||
filter *resource.Resource
|
||||
expected ResMap
|
||||
}{
|
||||
"level1": {
|
||||
filter: dep1,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm1).AddR(dep1).ResMap(),
|
||||
},
|
||||
"level2p": {
|
||||
filter: dep2p,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm2p).AddR(dep2p).ResMap(),
|
||||
},
|
||||
"level2s": {
|
||||
filter: dep2s,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm2s).AddR(dep2s).ResMap(),
|
||||
},
|
||||
"level3p": {
|
||||
filter: dep3e,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm3e).AddR(dep3e).ResMap(),
|
||||
},
|
||||
"level3i": {
|
||||
filter: dep3i,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm3i).AddR(dep3i).ResMap(),
|
||||
},
|
||||
}
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm1).AddR(dep1).AddR(cm2s).AddR(dep2s).AddR(cm2p).AddR(dep2p).AddR(cm3e).AddR(dep3e).AddR(cm3i).AddR(dep3i).ResMap()
|
||||
for name, test := range tests {
|
||||
test := test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := m.SubsetThatCouldBeReferencedByResource(test.filter)
|
||||
err := test.expected.ErrorIfNotEqualLists(got)
|
||||
if err != nil {
|
||||
test.expected.Debug("expected")
|
||||
got.Debug("actual")
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeepCopy(t *testing.T) {
|
||||
rm1 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
}).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
}).ResMap()
|
||||
|
||||
rm2 := rm1.DeepCopy()
|
||||
|
||||
if &rm1 == &rm2 {
|
||||
t.Fatal("DeepCopy returned a reference to itself instead of a copy")
|
||||
}
|
||||
err := rm1.ErrorIfNotEqualLists(rm1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorIfNotEqualSets(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
})
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
})
|
||||
r3 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
"namespace": "system",
|
||||
},
|
||||
})
|
||||
|
||||
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||
if err := m1.ErrorIfNotEqualSets(m1); err != nil {
|
||||
t.Fatalf("object should equal itself %v", err)
|
||||
}
|
||||
|
||||
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
||||
if err := m1.ErrorIfNotEqualSets(m2); err == nil {
|
||||
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
||||
}
|
||||
|
||||
m3 := resmaptest_test.NewRmBuilder(t, rf).AddR(r2).ResMap()
|
||||
if err := m2.ErrorIfNotEqualSets(m3); err == nil {
|
||||
t.Fatalf("%v should not equal %v %v", m2, m3, err)
|
||||
}
|
||||
|
||||
m3 = resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
}}).ResMap()
|
||||
if err := m2.ErrorIfNotEqualSets(m3); err != nil {
|
||||
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
||||
}
|
||||
|
||||
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
|
||||
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
|
||||
m4 = m1.ShallowCopy()
|
||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
m4 = m1.DeepCopy()
|
||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorIfNotEqualLists(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
})
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
})
|
||||
r3 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
"namespace": "system",
|
||||
},
|
||||
})
|
||||
|
||||
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||
if err := m1.ErrorIfNotEqualLists(m1); err != nil {
|
||||
t.Fatalf("object should equal itself %v", err)
|
||||
}
|
||||
|
||||
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
||||
if err := m1.ErrorIfNotEqualLists(m2); err == nil {
|
||||
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
||||
}
|
||||
|
||||
m3 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
}}).ResMap()
|
||||
if err := m2.ErrorIfNotEqualLists(m3); err != nil {
|
||||
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
||||
}
|
||||
|
||||
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
|
||||
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
||||
if err := m1.ErrorIfNotEqualLists(m4); err == nil {
|
||||
t.Fatalf("expected inequality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
|
||||
m4 = m1.ShallowCopy()
|
||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
m4 = m1.DeepCopy()
|
||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendAll(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-deploy1",
|
||||
},
|
||||
})
|
||||
input1 := rmF.FromResource(r1)
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "StatefulSet",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bar-stateful",
|
||||
},
|
||||
})
|
||||
input2 := rmF.FromResource(r2)
|
||||
|
||||
expected := New()
|
||||
if err := expected.Append(r1); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := expected.Append(r2); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := input1.AppendAll(input2); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
||||
input1.Debug("1")
|
||||
expected.Debug("ex")
|
||||
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
||||
}
|
||||
if err := input1.AppendAll(nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func makeMap1() ResMap {
|
||||
return rmF.FromResource(rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cmap",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"a": "x",
|
||||
"b": "y",
|
||||
},
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: "create",
|
||||
}))
|
||||
}
|
||||
|
||||
func makeMap2(b types.GenerationBehavior) ResMap {
|
||||
return rmF.FromResource(rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cmap",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"a": "u",
|
||||
"b": "v",
|
||||
"c": "w",
|
||||
},
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: b.String(),
|
||||
}))
|
||||
}
|
||||
|
||||
func TestAbsorbAll(t *testing.T) {
|
||||
expected := rmF.FromResource(rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"annotations": map[string]interface{}{},
|
||||
"labels": map[string]interface{}{},
|
||||
"name": "cmap",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"a": "u",
|
||||
"b": "v",
|
||||
"c": "w",
|
||||
},
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: "create",
|
||||
}))
|
||||
w := makeMap1()
|
||||
if err := w.AbsorbAll(makeMap2(types.BehaviorMerge)); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := expected.ErrorIfNotEqualLists(w); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w = makeMap1()
|
||||
if err := w.AbsorbAll(nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := w.ErrorIfNotEqualLists(makeMap1()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w = makeMap1()
|
||||
w2 := makeMap2(types.BehaviorReplace)
|
||||
if err := w.AbsorbAll(w2); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := w2.ErrorIfNotEqualLists(w); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w = makeMap1()
|
||||
w2 = makeMap2(types.BehaviorUnspecified)
|
||||
err := w.AbsorbAll(w2)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error with unspecified behavior")
|
||||
}
|
||||
}
|
||||
// See reswrangler_test.go
|
||||
|
||||
582
api/resmap/reswrangler.go
Normal file
582
api/resmap/reswrangler.go
Normal file
@@ -0,0 +1,582 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resmap
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// resWrangler implements ResMap.
|
||||
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
|
||||
}
|
||||
|
||||
func newOne() *resWrangler {
|
||||
result := &resWrangler{}
|
||||
result.Clear()
|
||||
return result
|
||||
}
|
||||
|
||||
// Clear implements ResMap.
|
||||
func (m *resWrangler) Clear() {
|
||||
m.rList = nil
|
||||
}
|
||||
|
||||
// Size implements ResMap.
|
||||
func (m *resWrangler) Size() int {
|
||||
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
|
||||
}
|
||||
|
||||
// Append implements ResMap.
|
||||
func (m *resWrangler) Append(res *resource.Resource) error {
|
||||
id := res.CurId()
|
||||
if r := m.GetMatchingResourcesByCurrentId(id.Equals); len(r) > 0 {
|
||||
return fmt.Errorf(
|
||||
"may not add resource with an already registered id: %s", id)
|
||||
}
|
||||
m.rList = append(m.rList, res)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove implements ResMap.
|
||||
func (m *resWrangler) Remove(adios resid.ResId) error {
|
||||
tmp := newOne()
|
||||
for _, r := range m.rList {
|
||||
if r.CurId() != adios {
|
||||
tmp.Append(r)
|
||||
}
|
||||
}
|
||||
if tmp.Size() != m.Size()-1 {
|
||||
return fmt.Errorf("id %s not found in removal", adios)
|
||||
}
|
||||
m.rList = tmp.rList
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace implements ResMap.
|
||||
func (m *resWrangler) Replace(res *resource.Resource) (int, error) {
|
||||
id := res.CurId()
|
||||
i, err := m.GetIndexOfCurrentId(id)
|
||||
if err != nil {
|
||||
return -1, errors.Wrap(err, "in Replace")
|
||||
}
|
||||
if i < 0 {
|
||||
return -1, fmt.Errorf("cannot find resource with id %s to replace", id)
|
||||
}
|
||||
m.rList[i] = res
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// AllIds implements ResMap.
|
||||
func (m *resWrangler) AllIds() (ids []resid.ResId) {
|
||||
ids = make([]resid.ResId, m.Size())
|
||||
for i, r := range m.rList {
|
||||
ids[i] = r.CurId()
|
||||
}
|
||||
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, r.OrgId())
|
||||
blob, err := yaml.Marshal(r.Map())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(blob))
|
||||
}
|
||||
}
|
||||
|
||||
type IdMatcher func(resid.ResId) bool
|
||||
|
||||
// GetByIndex implements ResMap.
|
||||
func (m *resWrangler) GetByIndex(i int) *resource.Resource {
|
||||
if i < 0 || i >= m.Size() {
|
||||
return nil
|
||||
}
|
||||
return m.rList[i]
|
||||
}
|
||||
|
||||
// GetIndexOfCurrentId implements ResMap.
|
||||
func (m *resWrangler) GetIndexOfCurrentId(id resid.ResId) (int, error) {
|
||||
count := 0
|
||||
result := -1
|
||||
for i, r := range m.rList {
|
||||
if id.Equals(r.CurId()) {
|
||||
count++
|
||||
result = i
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
return -1, fmt.Errorf("id matched %d resources", count)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type IdFromResource func(r *resource.Resource) resid.ResId
|
||||
|
||||
func GetOriginalId(r *resource.Resource) resid.ResId { return r.OrgId() }
|
||||
func GetCurrentId(r *resource.Resource) resid.ResId { return r.CurId() }
|
||||
|
||||
// GetMatchingResourcesByCurrentId implements ResMap.
|
||||
func (m *resWrangler) GetMatchingResourcesByCurrentId(
|
||||
matches IdMatcher) []*resource.Resource {
|
||||
return m.filteredById(matches, GetCurrentId)
|
||||
}
|
||||
|
||||
// GetMatchingResourcesByOriginalId implements ResMap.
|
||||
func (m *resWrangler) GetMatchingResourcesByOriginalId(
|
||||
matches IdMatcher) []*resource.Resource {
|
||||
return m.filteredById(matches, GetOriginalId)
|
||||
}
|
||||
|
||||
func (m *resWrangler) filteredById(
|
||||
matches IdMatcher, idGetter IdFromResource) []*resource.Resource {
|
||||
var result []*resource.Resource
|
||||
for _, r := range m.rList {
|
||||
if matches(idGetter(r)) {
|
||||
result = append(result, r)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetByCurrentId implements ResMap.
|
||||
func (m *resWrangler) GetByCurrentId(
|
||||
id resid.ResId) (*resource.Resource, error) {
|
||||
return demandOneMatch(m.GetMatchingResourcesByCurrentId, id, "Current")
|
||||
}
|
||||
|
||||
// GetByOriginalId implements ResMap.
|
||||
func (m *resWrangler) GetByOriginalId(
|
||||
id resid.ResId) (*resource.Resource, error) {
|
||||
return demandOneMatch(m.GetMatchingResourcesByOriginalId, id, "Original")
|
||||
}
|
||||
|
||||
// GetById implements ResMap.
|
||||
func (m *resWrangler) GetById(
|
||||
id resid.ResId) (*resource.Resource, error) {
|
||||
match, err1 := m.GetByOriginalId(id)
|
||||
if err1 == nil {
|
||||
return match, nil
|
||||
}
|
||||
match, err2 := m.GetByCurrentId(id)
|
||||
if err2 == nil {
|
||||
return match, nil
|
||||
}
|
||||
return nil, fmt.Errorf(
|
||||
"%s; %s; failed to find unique target for patch %s",
|
||||
err1.Error(), err2.Error(), id.GvknString())
|
||||
}
|
||||
|
||||
type resFinder func(IdMatcher) []*resource.Resource
|
||||
|
||||
func demandOneMatch(
|
||||
f resFinder, id resid.ResId, s string) (*resource.Resource, error) {
|
||||
r := f(id.Equals)
|
||||
if len(r) == 1 {
|
||||
return r[0], nil
|
||||
}
|
||||
if len(r) > 1 {
|
||||
return nil, fmt.Errorf("multiple matches for %sId %s", s, id)
|
||||
}
|
||||
return nil, fmt.Errorf("no matches for %sId %s", s, id)
|
||||
}
|
||||
|
||||
// GroupedByCurrentNamespace implements ResMap.GroupByCurrentNamespace
|
||||
func (m *resWrangler) GroupedByCurrentNamespace() map[string][]*resource.Resource {
|
||||
items := m.groupedByCurrentNamespace()
|
||||
delete(items, resid.TotallyNotANamespace)
|
||||
return items
|
||||
}
|
||||
|
||||
// NonNamespaceable implements ResMap.NonNamespaceable
|
||||
func (m *resWrangler) NonNamespaceable() []*resource.Resource {
|
||||
return m.groupedByCurrentNamespace()[resid.TotallyNotANamespace]
|
||||
}
|
||||
|
||||
func (m *resWrangler) groupedByCurrentNamespace() map[string][]*resource.Resource {
|
||||
byNamespace := make(map[string][]*resource.Resource)
|
||||
for _, res := range m.rList {
|
||||
namespace := res.CurId().EffectiveNamespace()
|
||||
if _, found := byNamespace[namespace]; !found {
|
||||
byNamespace[namespace] = []*resource.Resource{}
|
||||
}
|
||||
byNamespace[namespace] = append(byNamespace[namespace], res)
|
||||
}
|
||||
return byNamespace
|
||||
}
|
||||
|
||||
// GroupedByNamespace implements ResMap.GroupByOrginalNamespace
|
||||
func (m *resWrangler) GroupedByOriginalNamespace() map[string][]*resource.Resource {
|
||||
items := m.groupedByOriginalNamespace()
|
||||
delete(items, resid.TotallyNotANamespace)
|
||||
return items
|
||||
}
|
||||
|
||||
func (m *resWrangler) groupedByOriginalNamespace() map[string][]*resource.Resource {
|
||||
byNamespace := make(map[string][]*resource.Resource)
|
||||
for _, res := range m.rList {
|
||||
namespace := res.OrgId().EffectiveNamespace()
|
||||
if _, found := byNamespace[namespace]; !found {
|
||||
byNamespace[namespace] = []*resource.Resource{}
|
||||
}
|
||||
byNamespace[namespace] = append(byNamespace[namespace], res)
|
||||
}
|
||||
return byNamespace
|
||||
}
|
||||
|
||||
// AsYaml implements ResMap.
|
||||
func (m *resWrangler) AsYaml() ([]byte, error) {
|
||||
firstObj := true
|
||||
var b []byte
|
||||
buf := bytes.NewBuffer(b)
|
||||
for _, res := range m.Resources() {
|
||||
out, err := yaml.Marshal(res.Map())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if firstObj {
|
||||
firstObj = false
|
||||
} else {
|
||||
if _, err = buf.WriteString("---\n"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if _, err = buf.Write(out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
// ErrorIfNotEqualSets implements ResMap.
|
||||
func (m *resWrangler) ErrorIfNotEqualSets(other ResMap) error {
|
||||
m2, ok := other.(*resWrangler)
|
||||
if !ok {
|
||||
panic("bad cast")
|
||||
}
|
||||
if m.Size() != m2.Size() {
|
||||
return fmt.Errorf(
|
||||
"lists have different number of entries: %#v doesn't equal %#v",
|
||||
m.rList, m2.rList)
|
||||
}
|
||||
seen := make(map[int]bool)
|
||||
for _, r1 := range m.rList {
|
||||
id := r1.CurId()
|
||||
others := m2.GetMatchingResourcesByCurrentId(id.Equals)
|
||||
if len(others) == 0 {
|
||||
return fmt.Errorf(
|
||||
"id in self missing from other; id: %s", id)
|
||||
}
|
||||
if len(others) > 1 {
|
||||
return fmt.Errorf(
|
||||
"id in self matches %d in other; id: %s", len(others), id)
|
||||
}
|
||||
r2 := others[0]
|
||||
if !r1.KunstructEqual(r2) {
|
||||
return fmt.Errorf(
|
||||
"kunstruct not equal: \n -- %s,\n -- %s\n\n--\n%#v\n------\n%#v\n",
|
||||
r1, r2, r1, r2)
|
||||
}
|
||||
seen[m2.indexOfResource(r2)] = true
|
||||
}
|
||||
if len(seen) != m.Size() {
|
||||
return fmt.Errorf("counting problem %d != %d", len(seen), m.Size())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ErrorIfNotEqualList implements ResMap.
|
||||
func (m *resWrangler) ErrorIfNotEqualLists(other ResMap) error {
|
||||
m2, ok := other.(*resWrangler)
|
||||
if !ok {
|
||||
panic("bad cast")
|
||||
}
|
||||
if m.Size() != m2.Size() {
|
||||
return fmt.Errorf(
|
||||
"lists have different number of entries: %#v doesn't equal %#v",
|
||||
m.rList, m2.rList)
|
||||
}
|
||||
for i, r1 := range m.rList {
|
||||
r2 := m2.rList[i]
|
||||
if !r1.Equals(r2) {
|
||||
return fmt.Errorf(
|
||||
"Item i=%d differs:\n n1 = %s\n n2 = %s\n o1 = %s\n o2 = %s\n",
|
||||
i, r1.OrgId(), r2.OrgId(), r1, r2)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// 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.rList = make([]*resource.Resource, m.Size())
|
||||
for i, r := range m.rList {
|
||||
result.rList[i] = copier(r)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// SubsetThatCouldBeReferencedByResource implements ResMap.
|
||||
func (m *resWrangler) SubsetThatCouldBeReferencedByResource(
|
||||
inputRes *resource.Resource) ResMap {
|
||||
result := newOne()
|
||||
inputId := inputRes.CurId()
|
||||
isInputIdNamespaceable := inputId.IsNamespaceableKind()
|
||||
rctxm := inputRes.PrefixesSuffixesEquals
|
||||
subjectNamespaces := getNamespacesForRoleBinding(inputRes)
|
||||
for _, r := range m.Resources() {
|
||||
// Need to match more accuratly both at the time of selection and transformation.
|
||||
// OutmostPrefixSuffixEquals is not accurate enough since it is only using
|
||||
// the outer most suffix and the last prefix. Use PrefixedSuffixesEquals instead.
|
||||
resId := r.CurId()
|
||||
if (!isInputIdNamespaceable || !resId.IsNamespaceableKind() || resId.IsNsEquals(inputId) ||
|
||||
isRoleBindingNamespace(&subjectNamespaces, r.GetNamespace())) && r.InSameKustomizeCtx(rctxm) {
|
||||
result.append(r)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// isRoleBindingNamespace returns true is the namespace `ns` is in role binding
|
||||
// namespaces `m`
|
||||
func isRoleBindingNamespace(m *map[string]bool, ns string) bool {
|
||||
return (*m)[ns]
|
||||
}
|
||||
|
||||
// getNamespacesForRoleBinding returns referenced ServiceAccount namespaces if the inputRes is
|
||||
// a RoleBinding
|
||||
func getNamespacesForRoleBinding(inputRes *resource.Resource) map[string]bool {
|
||||
res := make(map[string]bool)
|
||||
if inputRes.GetKind() != "RoleBinding" {
|
||||
return res
|
||||
}
|
||||
subjects, err := inputRes.GetSlice("subjects")
|
||||
if err != nil || subjects == nil {
|
||||
return res
|
||||
}
|
||||
|
||||
for _, s := range subjects {
|
||||
subject := s.(map[string]interface{})
|
||||
if subject["namespace"] == nil || subject["kind"] == nil ||
|
||||
subject["kind"].(string) != "ServiceAccount" {
|
||||
continue
|
||||
}
|
||||
res[subject["namespace"].(string)] = true
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *resWrangler) append(res *resource.Resource) {
|
||||
m.rList = append(m.rList, res)
|
||||
}
|
||||
|
||||
// AppendAll implements ResMap.
|
||||
func (m *resWrangler) AppendAll(other ResMap) error {
|
||||
if other == nil {
|
||||
return nil
|
||||
}
|
||||
for _, res := range other.Resources() {
|
||||
if err := m.Append(res); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AbsorbAll implements ResMap.
|
||||
func (m *resWrangler) AbsorbAll(other ResMap) error {
|
||||
if other == nil {
|
||||
return nil
|
||||
}
|
||||
for _, r := range other.Resources() {
|
||||
err := m.appendReplaceOrMerge(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *resWrangler) appendReplaceOrMerge(
|
||||
res *resource.Resource) error {
|
||||
id := res.CurId()
|
||||
matches := m.GetMatchingResourcesByOriginalId(id.Equals)
|
||||
if len(matches) == 0 {
|
||||
matches = m.GetMatchingResourcesByCurrentId(id.Equals)
|
||||
}
|
||||
switch len(matches) {
|
||||
case 0:
|
||||
switch res.Behavior() {
|
||||
case types.BehaviorMerge, types.BehaviorReplace:
|
||||
return fmt.Errorf(
|
||||
"id %#v does not exist; cannot merge or replace", id)
|
||||
default:
|
||||
// presumably types.BehaviorCreate
|
||||
err := m.Append(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
old := matches[0]
|
||||
if old == nil {
|
||||
return fmt.Errorf("id lookup failure")
|
||||
}
|
||||
index := m.indexOfResource(old)
|
||||
if index < 0 {
|
||||
return fmt.Errorf("indexing problem")
|
||||
}
|
||||
switch res.Behavior() {
|
||||
case types.BehaviorReplace:
|
||||
res.Replace(old)
|
||||
case types.BehaviorMerge:
|
||||
res.Merge(old)
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"id %#v exists; behavior must be merge or replace", id)
|
||||
}
|
||||
i, err := m.Replace(res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if i != index {
|
||||
return fmt.Errorf("unexpected index in replacement")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf(
|
||||
"found multiple objects %v that could accept merge of %v",
|
||||
matches, id)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func anchorRegex(pattern string) string {
|
||||
if pattern == "" {
|
||||
return pattern
|
||||
}
|
||||
return "^" + pattern + "$"
|
||||
}
|
||||
|
||||
// Select returns a list of resources that
|
||||
// are selected by a Selector
|
||||
func (m *resWrangler) Select(s types.Selector) ([]*resource.Resource, error) {
|
||||
ns := regexp.MustCompile(anchorRegex(s.Namespace))
|
||||
nm := regexp.MustCompile(anchorRegex(s.Name))
|
||||
var result []*resource.Resource
|
||||
for _, r := range m.Resources() {
|
||||
curId := r.CurId()
|
||||
orgId := r.OrgId()
|
||||
|
||||
// matches the namespace when namespace is not empty in the selector
|
||||
// It first tries to match with the original namespace
|
||||
// then matches with the current namespace
|
||||
if r.GetNamespace() != "" {
|
||||
matched := ns.MatchString(orgId.EffectiveNamespace())
|
||||
if !matched {
|
||||
matched = ns.MatchString(curId.EffectiveNamespace())
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// matches the name when name is not empty in the selector
|
||||
// It first tries to match with the original name
|
||||
// then matches with the current name
|
||||
if r.GetName() != "" {
|
||||
matched := nm.MatchString(orgId.Name)
|
||||
if !matched {
|
||||
matched = nm.MatchString(curId.Name)
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// matches the GVK
|
||||
if !r.GetGvk().IsSelected(&s.Gvk) {
|
||||
continue
|
||||
}
|
||||
|
||||
// matches the label selector
|
||||
matched, err := r.MatchesLabelSelector(s.LabelSelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
|
||||
// matches the annotation selector
|
||||
matched, err = r.MatchesAnnotationSelector(s.AnnotationSelector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !matched {
|
||||
continue
|
||||
}
|
||||
result = append(result, r)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
901
api/resmap/reswrangler_test.go
Normal file
901
api/resmap/reswrangler_test.go
Normal file
@@ -0,0 +1,901 @@
|
||||
// Copyright 2019 The Kubernetes Authors.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package resmap_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"sigs.k8s.io/kustomize/api/k8sdeps/kunstruct"
|
||||
"sigs.k8s.io/kustomize/api/resid"
|
||||
. "sigs.k8s.io/kustomize/api/resmap"
|
||||
"sigs.k8s.io/kustomize/api/resource"
|
||||
resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
|
||||
"sigs.k8s.io/kustomize/api/types"
|
||||
)
|
||||
|
||||
var rf = resource.NewFactory(
|
||||
kunstruct.NewKunstructuredFactoryImpl())
|
||||
var rmF = NewFactory(rf, nil)
|
||||
|
||||
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),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Maintain the class invariant that no two
|
||||
// resources can have the same CurId().
|
||||
func TestAppendRejectsDuplicateResId(t *testing.T) {
|
||||
w := New()
|
||||
if err := w.Append(makeCm(1)); err != nil {
|
||||
t.Fatalf("append error: %v", err)
|
||||
}
|
||||
err := w.Append(makeCm(1))
|
||||
if err == nil {
|
||||
t.Fatalf("expected append error")
|
||||
}
|
||||
if !strings.Contains(
|
||||
err.Error(),
|
||||
"may not add resource with an already registered id") {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
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).OrgId())
|
||||
doRemove(t, w1, makeCm(3).OrgId())
|
||||
doRemove(t, w1, makeCm(5).OrgId())
|
||||
doRemove(t, w1, makeCm(7).OrgId())
|
||||
|
||||
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.OrgId())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
err = w.Append(r)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = w.Remove(r.OrgId())
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
err = w.Remove(r.OrgId())
|
||||
if err == nil {
|
||||
t.Fatalf("expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReplace(t *testing.T) {
|
||||
cm5 := makeCm(5)
|
||||
cm700 := makeCm(700)
|
||||
otherCm5 := makeCm(5)
|
||||
|
||||
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.Replace(otherCm5)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if w.Size() != oldSize {
|
||||
t.Fatalf("unexpected size %d", w.Size())
|
||||
}
|
||||
if r, err := w.GetByCurrentId(cm5.OrgId()); err != nil || r != otherCm5 {
|
||||
t.Fatalf("unexpected result r=%s, err=%v", r.CurId(), err)
|
||||
}
|
||||
if err := w.Append(cm5); err == nil {
|
||||
t.Fatalf("expected id already there error")
|
||||
}
|
||||
if err := w.Remove(cm5.OrgId()); 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("unexpected err: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeAsYaml(t *testing.T) {
|
||||
encoded := []byte(`apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm1
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: cm2
|
||||
`)
|
||||
input := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
}).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
}).ResMap()
|
||||
out, err := input.AsYaml()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(out, encoded) {
|
||||
t.Fatalf("%s doesn't match expected %s", out, encoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMatchingResourcesByCurrentId(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "alice",
|
||||
},
|
||||
})
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bob",
|
||||
},
|
||||
})
|
||||
r3 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bob",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r4 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r5 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).ResMap()
|
||||
|
||||
result := m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResId(cmap, "alice").GvknEquals)
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("Expected single map entry but got %v", result)
|
||||
}
|
||||
result = m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResId(cmap, "bob").GvknEquals)
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("Expected two, got %v", result)
|
||||
}
|
||||
result = m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResIdWithNamespace(cmap, "bob", "system").GvknEquals)
|
||||
if len(result) != 2 {
|
||||
t.Fatalf("Expected two but got %v", result)
|
||||
}
|
||||
result = m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResIdWithNamespace(cmap, "bob", "happy").Equals)
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("Expected single map entry but got %v", result)
|
||||
}
|
||||
result = m.GetMatchingResourcesByCurrentId(
|
||||
resid.NewResId(cmap, "charlie").GvknEquals)
|
||||
if len(result) != 1 {
|
||||
t.Fatalf("Expected single map entry but got %v", result)
|
||||
}
|
||||
|
||||
// nolint:goconst
|
||||
tests := []struct {
|
||||
name string
|
||||
matcher IdMatcher
|
||||
count int
|
||||
}{
|
||||
{
|
||||
"match everything",
|
||||
func(resid.ResId) bool { return true },
|
||||
5,
|
||||
},
|
||||
{
|
||||
"match nothing",
|
||||
func(resid.ResId) bool { return false },
|
||||
0,
|
||||
},
|
||||
{
|
||||
"name is alice",
|
||||
func(x resid.ResId) bool { return x.Name == "alice" },
|
||||
1,
|
||||
},
|
||||
{
|
||||
"name is charlie",
|
||||
func(x resid.ResId) bool { return x.Name == "charlie" },
|
||||
2,
|
||||
},
|
||||
{
|
||||
"name is bob",
|
||||
func(x resid.ResId) bool { return x.Name == "bob" },
|
||||
2,
|
||||
},
|
||||
{
|
||||
"happy namespace",
|
||||
func(x resid.ResId) bool {
|
||||
return x.Namespace == "happy"
|
||||
},
|
||||
3,
|
||||
},
|
||||
{
|
||||
"happy deployment",
|
||||
func(x resid.ResId) bool {
|
||||
return x.Namespace == "happy" &&
|
||||
x.Gvk.Kind == "Deployment"
|
||||
},
|
||||
1,
|
||||
},
|
||||
{
|
||||
"happy ConfigMap",
|
||||
func(x resid.ResId) bool {
|
||||
return x.Namespace == "happy" &&
|
||||
x.Gvk.Kind == "ConfigMap"
|
||||
},
|
||||
2,
|
||||
},
|
||||
}
|
||||
for _, tst := range tests {
|
||||
result := m.GetMatchingResourcesByCurrentId(tst.matcher)
|
||||
if len(result) != tst.count {
|
||||
t.Fatalf("test '%s'; actual: %d, expected: %d",
|
||||
tst.name, len(result), tst.count)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubsetThatCouldBeReferencedByResource(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "alice",
|
||||
},
|
||||
})
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bob",
|
||||
},
|
||||
})
|
||||
r3 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bob",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r4 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r5 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "charlie",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r5.AddNamePrefix("little-")
|
||||
r6 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "domino",
|
||||
"namespace": "happy",
|
||||
},
|
||||
})
|
||||
r6.AddNamePrefix("little-")
|
||||
r7 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ClusterRoleBinding",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "meh",
|
||||
},
|
||||
})
|
||||
|
||||
tests := map[string]struct {
|
||||
filter *resource.Resource
|
||||
expected ResMap
|
||||
}{
|
||||
"default namespace 1": {
|
||||
filter: r2,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
||||
},
|
||||
"default namespace 2": {
|
||||
filter: r1,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r7).ResMap(),
|
||||
},
|
||||
"happy namespace no prefix": {
|
||||
filter: r3,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||
},
|
||||
"happy namespace with prefix": {
|
||||
filter: r5,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||
},
|
||||
"cluster level": {
|
||||
filter: r7,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap(),
|
||||
},
|
||||
}
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(r1).AddR(r2).AddR(r3).AddR(r4).AddR(r5).AddR(r6).AddR(r7).ResMap()
|
||||
for name, test := range tests {
|
||||
test := test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := m.SubsetThatCouldBeReferencedByResource(test.filter)
|
||||
err := test.expected.ErrorIfNotEqualLists(got)
|
||||
if err != nil {
|
||||
test.expected.Debug("expected")
|
||||
got.Debug("actual")
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func addPfxSfx(r *resource.Resource, prefixes []string, suffixes []string) {
|
||||
for _, pfx := range prefixes {
|
||||
r.AddNamePrefix(pfx)
|
||||
}
|
||||
|
||||
for _, sfx := range suffixes {
|
||||
r.AddNameSuffix(sfx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSubsetThatCouldBeReferencedByResourceMultiLevel(t *testing.T) {
|
||||
// Simulates ConfigMap and Deployment defined at level 1
|
||||
// No prefix nor suffix added at that level
|
||||
cm1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level1",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm1, []string{""}, []string{""})
|
||||
dep1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level1",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep1, []string{""}, []string{""})
|
||||
|
||||
// Simulates ConfigMap and Deployment defined at level 1
|
||||
// and prefix added in level 2 of kustomization
|
||||
cm2p := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level2p",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm2p, []string{"", "level2p-"}, []string{"", ""})
|
||||
|
||||
dep2p := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level2p",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep2p, []string{"", "level2p-"}, []string{"", ""})
|
||||
|
||||
// Simulates ConfigMap and Deployment defined at level 1
|
||||
// and suffix added in level 2 of kustomization
|
||||
cm2s := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level2s",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm2s, []string{"", ""}, []string{"", "-level2s"})
|
||||
|
||||
dep2s := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level2s",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep2s, []string{"", ""}, []string{"", "-level2s"})
|
||||
|
||||
// Simulates ConfigMap and Deployment defined at level 1,
|
||||
// prefix added in levels 2 and 3 of kustomization.
|
||||
cm3e := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level3e",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm3e, []string{"", "level2p-", "level3e-"}, []string{"", "", ""})
|
||||
|
||||
dep3e := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level3e",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep3e, []string{"", "level2p-", "level3e-"}, []string{"", "", ""})
|
||||
|
||||
// Simulates Deployment defined at level 1, ConfigMap defined at level 2,
|
||||
// prefix added in levels 2 and 3 of kustomization.
|
||||
// This reproduce issue 1440.
|
||||
cm3i := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level3i",
|
||||
},
|
||||
})
|
||||
addPfxSfx(cm3i, []string{"level2p-", "level3i-"}, []string{"", ""})
|
||||
|
||||
dep3i := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "level3i",
|
||||
},
|
||||
})
|
||||
addPfxSfx(dep3i, []string{"", "level2p-", "level3i-"}, []string{"", "", ""})
|
||||
|
||||
tests := map[string]struct {
|
||||
filter *resource.Resource
|
||||
expected ResMap
|
||||
}{
|
||||
"level1": {
|
||||
filter: dep1,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm1).AddR(dep1).ResMap(),
|
||||
},
|
||||
"level2p": {
|
||||
filter: dep2p,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm2p).AddR(dep2p).ResMap(),
|
||||
},
|
||||
"level2s": {
|
||||
filter: dep2s,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm2s).AddR(dep2s).ResMap(),
|
||||
},
|
||||
"level3p": {
|
||||
filter: dep3e,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm3e).AddR(dep3e).ResMap(),
|
||||
},
|
||||
"level3i": {
|
||||
filter: dep3i,
|
||||
expected: resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm3i).AddR(dep3i).ResMap(),
|
||||
},
|
||||
}
|
||||
m := resmaptest_test.NewRmBuilder(t, rf).
|
||||
AddR(cm1).AddR(dep1).AddR(cm2s).AddR(dep2s).AddR(cm2p).AddR(dep2p).AddR(cm3e).AddR(dep3e).AddR(cm3i).AddR(dep3i).ResMap()
|
||||
for name, test := range tests {
|
||||
test := test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got := m.SubsetThatCouldBeReferencedByResource(test.filter)
|
||||
err := test.expected.ErrorIfNotEqualLists(got)
|
||||
if err != nil {
|
||||
test.expected.Debug("expected")
|
||||
got.Debug("actual")
|
||||
t.Fatalf("Expected match")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeepCopy(t *testing.T) {
|
||||
rm1 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
}).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
}).ResMap()
|
||||
|
||||
rm2 := rm1.DeepCopy()
|
||||
|
||||
if &rm1 == &rm2 {
|
||||
t.Fatal("DeepCopy returned a reference to itself instead of a copy")
|
||||
}
|
||||
err := rm1.ErrorIfNotEqualLists(rm1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorIfNotEqualSets(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
})
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
})
|
||||
r3 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
"namespace": "system",
|
||||
},
|
||||
})
|
||||
|
||||
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||
if err := m1.ErrorIfNotEqualSets(m1); err != nil {
|
||||
t.Fatalf("object should equal itself %v", err)
|
||||
}
|
||||
|
||||
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
||||
if err := m1.ErrorIfNotEqualSets(m2); err == nil {
|
||||
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
||||
}
|
||||
|
||||
m3 := resmaptest_test.NewRmBuilder(t, rf).AddR(r2).ResMap()
|
||||
if err := m2.ErrorIfNotEqualSets(m3); err == nil {
|
||||
t.Fatalf("%v should not equal %v %v", m2, m3, err)
|
||||
}
|
||||
|
||||
m3 = resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
}}).ResMap()
|
||||
if err := m2.ErrorIfNotEqualSets(m3); err != nil {
|
||||
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
||||
}
|
||||
|
||||
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
|
||||
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
|
||||
m4 = m1.ShallowCopy()
|
||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
m4 = m1.DeepCopy()
|
||||
if err := m1.ErrorIfNotEqualSets(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorIfNotEqualLists(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
},
|
||||
})
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
},
|
||||
})
|
||||
r3 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm2",
|
||||
"namespace": "system",
|
||||
},
|
||||
})
|
||||
|
||||
m1 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||
if err := m1.ErrorIfNotEqualLists(m1); err != nil {
|
||||
t.Fatalf("object should equal itself %v", err)
|
||||
}
|
||||
|
||||
m2 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).ResMap()
|
||||
if err := m1.ErrorIfNotEqualLists(m2); err == nil {
|
||||
t.Fatalf("%v should not equal %v %v", m1, m2, err)
|
||||
}
|
||||
|
||||
m3 := resmaptest_test.NewRmBuilder(t, rf).Add(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cm1",
|
||||
}}).ResMap()
|
||||
if err := m2.ErrorIfNotEqualLists(m3); err != nil {
|
||||
t.Fatalf("%v should equal %v %v", m2, m3, err)
|
||||
}
|
||||
|
||||
m4 := resmaptest_test.NewRmBuilder(t, rf).AddR(r1).AddR(r2).AddR(r3).ResMap()
|
||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
|
||||
m4 = resmaptest_test.NewRmBuilder(t, rf).AddR(r3).AddR(r1).AddR(r2).ResMap()
|
||||
if err := m1.ErrorIfNotEqualLists(m4); err == nil {
|
||||
t.Fatalf("expected inequality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
|
||||
m4 = m1.ShallowCopy()
|
||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
m4 = m1.DeepCopy()
|
||||
if err := m1.ErrorIfNotEqualLists(m4); err != nil {
|
||||
t.Fatalf("expected equality between %v and %v, %v", m1, m4, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendAll(t *testing.T) {
|
||||
r1 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "foo-deploy1",
|
||||
},
|
||||
})
|
||||
input1 := rmF.FromResource(r1)
|
||||
r2 := rf.FromMap(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "StatefulSet",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "bar-stateful",
|
||||
},
|
||||
})
|
||||
input2 := rmF.FromResource(r2)
|
||||
|
||||
expected := New()
|
||||
if err := expected.Append(r1); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := expected.Append(r2); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if err := input1.AppendAll(input2); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
||||
input1.Debug("1")
|
||||
expected.Debug("ex")
|
||||
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
||||
}
|
||||
if err := input1.AppendAll(nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := expected.ErrorIfNotEqualLists(input1); err != nil {
|
||||
t.Fatalf("%#v doesn't equal expected %#v", input1, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func makeMap1() ResMap {
|
||||
return rmF.FromResource(rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cmap",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"a": "x",
|
||||
"b": "y",
|
||||
},
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: "create",
|
||||
}))
|
||||
}
|
||||
|
||||
func makeMap2(b types.GenerationBehavior) ResMap {
|
||||
return rmF.FromResource(rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "cmap",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"a": "u",
|
||||
"b": "v",
|
||||
"c": "w",
|
||||
},
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: b.String(),
|
||||
}))
|
||||
}
|
||||
|
||||
func TestAbsorbAll(t *testing.T) {
|
||||
expected := rmF.FromResource(rf.FromMapAndOption(
|
||||
map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "ConfigMap",
|
||||
"metadata": map[string]interface{}{
|
||||
"annotations": map[string]interface{}{},
|
||||
"labels": map[string]interface{}{},
|
||||
"name": "cmap",
|
||||
},
|
||||
"data": map[string]interface{}{
|
||||
"a": "u",
|
||||
"b": "v",
|
||||
"c": "w",
|
||||
},
|
||||
}, &types.GeneratorArgs{
|
||||
Behavior: "create",
|
||||
}))
|
||||
w := makeMap1()
|
||||
if err := w.AbsorbAll(makeMap2(types.BehaviorMerge)); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := expected.ErrorIfNotEqualLists(w); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w = makeMap1()
|
||||
if err := w.AbsorbAll(nil); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := w.ErrorIfNotEqualLists(makeMap1()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w = makeMap1()
|
||||
w2 := makeMap2(types.BehaviorReplace)
|
||||
if err := w.AbsorbAll(w2); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if err := w2.ErrorIfNotEqualLists(w); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
w = makeMap1()
|
||||
w2 = makeMap2(types.BehaviorUnspecified)
|
||||
err := w.AbsorbAll(w2)
|
||||
if err == nil {
|
||||
t.Fatalf("expected error with unspecified behavior")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user