Manage name changes (prefix/suffix) via YAML annotations rather than via in-memory-only fields.

This commit is contained in:
Natasha Sarkar
2021-01-11 13:06:21 -08:00
parent ea5d08bac5
commit bd4580d73a
34 changed files with 539 additions and 61 deletions

View File

@@ -35,17 +35,17 @@ func (rf *Factory) FromMap(m map[string]interface{}) *Resource {
// FromMapWithName returns a new instance with the given "original" name.
func (rf *Factory) FromMapWithName(n string, m map[string]interface{}) *Resource {
return rf.makeOne(rf.kf.FromMap(m), nil).setOriginalName(n)
return rf.makeOne(rf.kf.FromMap(m), nil).SetOriginalName(n, true)
}
// FromMapWithNamespace returns a new instance with the given "original" namespace.
func (rf *Factory) FromMapWithNamespace(n string, m map[string]interface{}) *Resource {
return rf.makeOne(rf.kf.FromMap(m), nil).setOriginalNs(n)
return rf.makeOne(rf.kf.FromMap(m), nil).SetOriginalNs(n, true)
}
// FromMapWithNamespaceAndName returns a new instance with the given "original" namespace.
func (rf *Factory) FromMapWithNamespaceAndName(ns string, n string, m map[string]interface{}) *Resource {
return rf.makeOne(rf.kf.FromMap(m), nil).setOriginalNs(ns).setOriginalName(n)
return rf.makeOne(rf.kf.FromMap(m), nil).SetOriginalNs(ns, true).SetOriginalName(n, true)
}
// FromMapAndOption returns a new instance of Resource with given options.
@@ -72,7 +72,7 @@ func (rf *Factory) makeOne(
kunStr: u,
options: o,
}
return r.setOriginalName(r.kunStr.GetName()).setOriginalNs(r.GetNamespace())
return r
}
// SliceFromPatches returns a slice of resources given a patch path
@@ -157,7 +157,7 @@ func (rf *Factory) SliceFromBytesWithNames(names []string, in []byte) ([]*Resour
return nil, fmt.Errorf("number of names doesn't match number of resources")
}
for i, res := range result {
res.originalName = names[i]
res.SetOriginalName(names[i], true)
}
return result, nil
}

View File

@@ -23,16 +23,19 @@ import (
// paired with metadata used by kustomize.
// For more history, see sigs.k8s.io/kustomize/api/ifc.Unstructured
type Resource struct {
kunStr ifc.Kunstructured
originalName string
originalNs string
options *types.GenArgs
refBy []resid.ResId
refVarNames []string
namePrefixes []string
nameSuffixes []string
kunStr ifc.Kunstructured
options *types.GenArgs
refBy []resid.ResId
refVarNames []string
}
const (
nameAnnotation = "config.kubernetes.io/originalName"
prefixAnnotation = "config.kubernetes.io/prefixes"
suffixAnnotation = "config.kubernetes.io/suffixes"
namespaceAnnotation = "config.kubernetes.io/originalNs"
)
func (r *Resource) ResetPrimaryData(incoming *Resource) {
r.kunStr = incoming.Copy()
}
@@ -172,13 +175,9 @@ func (r *Resource) CopyMergeMetaDataFieldsFrom(other *Resource) {
}
func (r *Resource) copyOtherFields(other *Resource) {
r.originalName = other.originalName
r.originalNs = other.originalNs
r.options = other.options
r.refBy = other.copyRefBy()
r.refVarNames = copyStringSlice(other.refVarNames)
r.namePrefixes = copyStringSlice(other.namePrefixes)
r.nameSuffixes = copyStringSlice(other.nameSuffixes)
}
func (r *Resource) MergeDataMapFrom(o *Resource) {
@@ -245,28 +244,48 @@ func copyStringSlice(s []string) []string {
// Implements ResCtx AddNamePrefix
func (r *Resource) AddNamePrefix(p string) {
r.namePrefixes = append(r.namePrefixes, p)
annotations := r.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
if _, ok := annotations[prefixAnnotation]; !ok {
annotations[prefixAnnotation] = p
} else {
annotations[prefixAnnotation] = annotations[prefixAnnotation] + "," + p
}
r.SetAnnotations(annotations)
}
// Implements ResCtx AddNameSuffix
func (r *Resource) AddNameSuffix(s string) {
r.nameSuffixes = append(r.nameSuffixes, s)
annotations := r.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
if _, ok := annotations[suffixAnnotation]; !ok {
annotations[suffixAnnotation] = s
} else {
annotations[suffixAnnotation] = annotations[suffixAnnotation] + "," + s
}
r.SetAnnotations(annotations)
}
// Implements ResCtx GetOutermostNamePrefix
func (r *Resource) GetOutermostNamePrefix() string {
if len(r.namePrefixes) == 0 {
namePrefixes := r.GetNamePrefixes()
if len(namePrefixes) == 0 {
return ""
}
return r.namePrefixes[len(r.namePrefixes)-1]
return namePrefixes[len(namePrefixes)-1]
}
// Implements ResCtx GetOutermostNameSuffix
func (r *Resource) GetOutermostNameSuffix() string {
if len(r.nameSuffixes) == 0 {
nameSuffixes := r.GetNameSuffixes()
if len(nameSuffixes) == 0 {
return ""
}
return r.nameSuffixes[len(r.nameSuffixes)-1]
return nameSuffixes[len(nameSuffixes)-1]
}
func sameEndingSubarray(a, b []string) bool {
@@ -291,12 +310,20 @@ func sameEndingSubarray(a, b []string) bool {
// Implements ResCtx GetNamePrefixes
func (r *Resource) GetNamePrefixes() []string {
return r.namePrefixes
annotations := r.GetAnnotations()
if _, ok := annotations[prefixAnnotation]; !ok {
return nil
}
return strings.Split(annotations[prefixAnnotation], ",")
}
// Implements ResCtx GetNameSuffixes
func (r *Resource) GetNameSuffixes() []string {
return r.nameSuffixes
annotations := r.GetAnnotations()
if _, ok := annotations[suffixAnnotation]; !ok {
return nil
}
return strings.Split(annotations[suffixAnnotation], ",")
}
// OutermostPrefixSuffixEquals returns true if both resources
@@ -320,23 +347,96 @@ func (r *Resource) PrefixesSuffixesEquals(o ResCtx) bool {
return sameEndingSubarray(r.GetNamePrefixes(), o.GetNamePrefixes()) && sameEndingSubarray(r.GetNameSuffixes(), o.GetNameSuffixes())
}
func (r *Resource) GetOriginalName() string {
return r.originalName
func (r *Resource) RemoveIdAnnotations() error {
annotations := r.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
delete(annotations, nameAnnotation)
delete(annotations, prefixAnnotation)
delete(annotations, suffixAnnotation)
delete(annotations, namespaceAnnotation)
if len(annotations) == 0 {
json, err := r.MarshalJSON()
if err != nil {
return err
}
yml, err := yaml.JSONToYAML(json)
if err != nil {
return err
}
rnode, err := kyaml.Parse(string(yml))
if err != nil {
return err
}
err = rnode.SetAnnotations(nil)
if err != nil {
return err
}
b, err := rnode.MarshalJSON()
if err != nil {
return err
}
err = r.UnmarshalJSON(b)
if err != nil {
return err
}
return nil
}
r.SetAnnotations(annotations)
return nil
}
// Making this public would be bad.
func (r *Resource) setOriginalName(n string) *Resource {
r.originalName = n
func (r *Resource) GetOriginalName() string {
annotations := r.GetAnnotations()
if name, ok := annotations[nameAnnotation]; ok {
return name
}
return r.kunStr.GetName()
}
func (r *Resource) SetOriginalName(n string, overwrite bool) *Resource {
annotations := r.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
if _, ok := annotations[nameAnnotation]; !ok || overwrite {
annotations[nameAnnotation] = n
}
r.kunStr.SetAnnotations(annotations)
return r
}
func (r *Resource) GetOriginalNs() string {
return r.originalNs
annotations := r.GetAnnotations()
if ns, ok := annotations[namespaceAnnotation]; ok {
return ns
}
ns := r.GetNamespace()
if ns == "default" {
return ""
}
return ns
}
// Making this public would be bad.
func (r *Resource) setOriginalNs(n string) *Resource {
r.originalNs = n
func (r *Resource) SetOriginalNs(n string, overwrite bool) *Resource {
if n == "" {
n = "default"
}
annotations := r.GetAnnotations()
if annotations == nil {
annotations = make(map[string]string)
}
if _, ok := annotations[namespaceAnnotation]; !ok || overwrite {
annotations[namespaceAnnotation] = n
}
r.SetAnnotations(annotations)
return r
}

View File

@@ -695,6 +695,325 @@ spec:
}
}
func TestSetOriginalNameAndNs(t *testing.T) {
input := `apiVersion: apps/v1
kind: Secret
metadata:
name: newName`
factory := provider.NewDefaultDepProvider().GetResourceFactory()
resources, err := factory.SliceFromBytes([]byte(input))
if err != nil {
t.Fatal(err)
}
res := resources[0]
res.SetOriginalName("oldName", false)
res.SetOriginalNs("default", false)
expected := `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalName: oldName
config.kubernetes.io/originalNs: default
name: newName
`
bytes, err := res.AsYAML()
if err != nil {
t.Fatal(err)
}
assert.Equal(t, expected, string(bytes))
}
func TestGetOriginalName(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
// no name annotation, return the name
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: mySecret`,
expected: "mySecret",
},
{
// return name from name annotation
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalName: oldName
name: newName`,
expected: "oldName",
},
}
for _, test := range tests {
factory := provider.NewDefaultDepProvider().GetResourceFactory()
resources, err := factory.SliceFromBytes([]byte(test.input))
if err != nil {
t.Fatal(err)
}
assert.Equal(t, test.expected, resources[0].GetOriginalName())
}
}
func TestSetOriginalName(t *testing.T) {
tests := []struct {
input string
originalName string
overwrite bool
expected string
}{
{
// no original name set, overwrite is false
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: newName`,
originalName: "oldName",
overwrite: false,
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalName: oldName
name: newName
`,
},
{
// no original name set, overwrite is true
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: newName`,
originalName: "oldName",
overwrite: true,
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalName: oldName
name: newName
`,
},
{
// original name is set, overwrite is false, resource shouldn't change
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalName: oldName
name: newName`,
originalName: "newOriginalName",
overwrite: false,
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalName: oldName
name: newName
`,
},
{
// original name is set, overwrite is true, resource should change
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalName: oldName
name: newName`,
originalName: "newOriginalName",
overwrite: true,
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalName: newOriginalName
name: newName
`,
},
}
for _, test := range tests {
factory := provider.NewDefaultDepProvider().GetResourceFactory()
resources, err := factory.SliceFromBytes([]byte(test.input))
if err != nil {
t.Fatal(err)
}
res := resources[0]
res.SetOriginalName(test.originalName, test.overwrite)
bytes, err := res.AsYAML()
if err != nil {
t.Fatal(err)
}
assert.Equal(t, test.expected, string(bytes))
}
}
func TestGetOriginalNs(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
// no namespace, return default
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: mySecret`,
expected: "",
},
{
// return old namespace
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalNs: oldNamespace
name: mySecret
namespace: myNamespace`,
expected: "oldNamespace",
},
{
// return namespace
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: mySecret
namespace: myNamespace`,
expected: "myNamespace",
},
}
for _, test := range tests {
factory := provider.NewDefaultDepProvider().GetResourceFactory()
resources, err := factory.SliceFromBytes([]byte(test.input))
if err != nil {
t.Fatal(err)
}
assert.Equal(t, test.expected, resources[0].GetOriginalNs())
}
}
func TestSetOriginalNs(t *testing.T) {
tests := []struct {
input string
originalNs string
overwrite bool
expected string
}{
{
// no original namespace set, overwrite is false
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: newName
namespace: newNamespace`,
originalNs: "oldNamespace",
overwrite: false,
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalNs: oldNamespace
name: newName
namespace: newNamespace
`,
},
{
// no original name set, overwrite is true
input: `apiVersion: apps/v1
kind: Secret
metadata:
name: newName
namespace: newNamespace`,
originalNs: "oldNamespace",
overwrite: true,
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalNs: oldNamespace
name: newName
namespace: newNamespace
`,
},
{
// original name is set, overwrite is false, resource shouldn't change
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalNs: oldNamespace
name: newName
namespace: newNamespace`,
originalNs: "newOriginalNamespace",
overwrite: false,
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalNs: oldNamespace
name: newName
namespace: newNamespace
`,
},
{
// original name is set, overwrite is true, resource should change
input: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalNs: oldNamespace
name: newName
namespace: newNamespace`,
originalNs: "newOriginalNamespace",
overwrite: true,
expected: `apiVersion: apps/v1
kind: Secret
metadata:
annotations:
config.kubernetes.io/originalNs: newOriginalNamespace
name: newName
namespace: newNamespace
`,
},
}
for _, test := range tests {
factory := provider.NewDefaultDepProvider().GetResourceFactory()
resources, err := factory.SliceFromBytes([]byte(test.input))
if err != nil {
t.Fatal(err)
}
res := resources[0]
res.SetOriginalNs(test.originalNs, test.overwrite)
bytes, err := res.AsYAML()
if err != nil {
t.Fatal(err)
}
assert.Equal(t, test.expected, string(bytes))
}
}
// baseResource produces a base object which used to test
// patch transformation
// Also the structure is matching the Deployment syntax