Skip to content

Commit

Permalink
provide utility helpers for preserving internal annotations
Browse files Browse the repository at this point in the history
  • Loading branch information
natasha41575 committed Oct 28, 2021
1 parent 5765ab4 commit 52f0361
Show file tree
Hide file tree
Showing 3 changed files with 346 additions and 7 deletions.
1 change: 1 addition & 0 deletions api/resource/resource.go
Expand Up @@ -26,6 +26,7 @@ type Resource struct {
refVarNames []string
}

// nolint
var BuildAnnotations = []string{
utils.BuildAnnotationPreviousKinds,
utils.BuildAnnotationPreviousNames,
Expand Down
102 changes: 95 additions & 7 deletions kyaml/kio/kioutil/kioutil.go
Expand Up @@ -17,25 +17,29 @@ import (
type AnnotationKey = string

const (
// internalPrefix is the prefix given to internal annotations that are used
// internally by the orchestrator
internalPrefix string = "internal.config.kubernetes.io/"

// IndexAnnotation records the index of a specific resource in a file or input stream.
IndexAnnotation AnnotationKey = "internal.config.kubernetes.io/index"
IndexAnnotation AnnotationKey = internalPrefix + "index"

// PathAnnotation records the path to the file the Resource was read from
PathAnnotation AnnotationKey = "internal.config.kubernetes.io/path"
PathAnnotation AnnotationKey = internalPrefix + "path"

// SeqIndentAnnotation records the sequence nodes indentation of the input resource
SeqIndentAnnotation AnnotationKey = "internal.config.kubernetes.io/seqindent"
SeqIndentAnnotation AnnotationKey = internalPrefix + "seqindent"

// IdAnnotation records the id of the resource to map inputs to outputs
IdAnnotation = "internal.config.kubernetes.io/id"
IdAnnotation AnnotationKey = internalPrefix + "id"

// LegacyIndexAnnotation is the deprecated annotation key for resource index
// Deprecated: LegacyIndexAnnotation is deprecated.
LegacyIndexAnnotation AnnotationKey = "config.kubernetes.io/index"

// LegacyPathAnnotation is the deprecated annotation key for resource path
// Deprecated: LegacyPathAnnotation is deprecated.
LegacyPathAnnotation AnnotationKey = "config.kubernetes.io/path"

// LegacyIdAnnotation is the deprecated annotation key for resource ids
// Deprecated: LegacyIdAnnotation is deprecated.
LegacyIdAnnotation = "config.k8s.io/id"
)

Expand Down Expand Up @@ -311,3 +315,87 @@ func SortNodes(nodes []*yaml.RNode) error {
})
return errors.Wrap(err)
}

// CopyInternalAnnotations copies the annotations that begin with the prefix
// `internal.config.kubernetes.io` from the source RNode to the destination RNode.
// It takes a parameter exclusions, which is a list of annotation keys to ignore.
func CopyInternalAnnotations(src *yaml.RNode, dst *yaml.RNode, exclusions ...AnnotationKey) error {
srcAnnotations := GetInternalAnnotations(src)
for k, v := range srcAnnotations {
if stringSliceContains(exclusions, k) {
continue
}
if err := dst.PipeE(yaml.SetAnnotation(k, v)); err != nil {
return err
}
}
return nil
}

// ConfirmInternalAnnotationUnchanged compares the annotations of the RNodes that begin with the prefix
// `internal.config.kubernetes.io`, throwing an error if they differ. // It takes a parameter exclusions,
// which is a list of annotation keys to ignore.
func ConfirmInternalAnnotationUnchanged(r1 *yaml.RNode, r2 *yaml.RNode, exclusions ...AnnotationKey) error {
r1Annotations := GetInternalAnnotations(r1)
r2Annotations := GetInternalAnnotations(r2)

// this is a map to prevent duplicates
diffAnnos := make(map[string]bool)

for k, v1 := range r1Annotations {
if stringSliceContains(exclusions, k) {
continue
}
if v2, ok := r2Annotations[k]; !ok || v1 != v2 {
diffAnnos[k] = true
}
}

for k, v2 := range r2Annotations {
if stringSliceContains(exclusions, k) {
continue
}
if v1, ok := r1Annotations[k]; !ok || v2 != v1 {
diffAnnos[k] = true
}
}

if len(diffAnnos) > 0 {
keys := make([]string, 0, len(diffAnnos))
for k := range diffAnnos {
keys = append(keys, k)
}
sort.Strings(keys)

errorString := "internal annotations differ: "
for _, key := range keys {
errorString = errorString + key + ", "
}
return errors.Errorf(errorString[0 : len(errorString)-2])
}

return nil
}

// GetInternalAnnotations returns a map of all the annotations of the provided RNode that begin
// with the prefix `internal.config.kubernetes.io`
func GetInternalAnnotations(rn *yaml.RNode) map[string]string {
annotations := rn.GetAnnotations()
result := make(map[string]string)
for k, v := range annotations {
if strings.HasPrefix(k, internalPrefix) {
result[k] = v
}
}
return result
}

// stringSliceContains returns true if the slice has the string.
func stringSliceContains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}
250 changes: 250 additions & 0 deletions kyaml/kio/kioutil/kioutil_test.go
Expand Up @@ -451,3 +451,253 @@ metadata:
assert.Equal(t, tc.expected, nodes[0].MustString())
}
}

func TestCopyInternalAnnotations(t *testing.T) {
var tests = []struct {
input string
exclusions []kioutil.AnnotationKey
expected string
}{
{
input: `apiVersion: v1
kind: Foo
metadata:
name: src
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
internal.config.kubernetes.io/foo: 'bar'
---
apiVersion: v1
kind: Foo
metadata:
name: dst
annotations:
internal.config.kubernetes.io/path: 'c/d.yaml'
internal.config.kubernetes.io/index: '10'
`,
expected: `apiVersion: v1
kind: Foo
metadata:
name: dst
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
internal.config.kubernetes.io/foo: 'bar'
`,
},
{
input: `apiVersion: v1
kind: Foo
metadata:
name: src
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
internal.config.kubernetes.io/foo: 'bar-src'
---
apiVersion: v1
kind: Foo
metadata:
name: dst
annotations:
internal.config.kubernetes.io/path: 'c/d.yaml'
internal.config.kubernetes.io/index: '10'
internal.config.kubernetes.io/foo: 'bar-dst'
`,
exclusions: []kioutil.AnnotationKey{
kioutil.PathAnnotation,
kioutil.IndexAnnotation,
},
expected: `apiVersion: v1
kind: Foo
metadata:
name: dst
annotations:
internal.config.kubernetes.io/path: 'c/d.yaml'
internal.config.kubernetes.io/index: '10'
internal.config.kubernetes.io/foo: 'bar-src'
`,
},
}

for _, tc := range tests {
rw := kio.ByteReadWriter{
Reader: bytes.NewBufferString(tc.input),
OmitReaderAnnotations: true,
}
nodes, err := rw.Read()
assert.NoError(t, err)
assert.NoError(t, kioutil.CopyInternalAnnotations(nodes[0], nodes[1], tc.exclusions...))
assert.Equal(t, tc.expected, nodes[1].MustString())
}
}

func TestConfirmInternalAnnotationUnchanged(t *testing.T) {
var tests = []struct {
input string
exclusions []kioutil.AnnotationKey
expectedErr string
}{
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo-1
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
---
apiVersion: v1
kind: Foo
metadata:
name: foo-2
annotations:
internal.config.kubernetes.io/path: 'c/d.yaml'
internal.config.kubernetes.io/index: '10'
`,
expectedErr: `internal annotations differ: internal.config.kubernetes.io/index, internal.config.kubernetes.io/path`,
},
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo-1
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
---
apiVersion: v1
kind: Foo
metadata:
name: foo-2
annotations:
internal.config.kubernetes.io/path: 'c/d.yaml'
internal.config.kubernetes.io/index: '10'
`,
exclusions: []kioutil.AnnotationKey{
kioutil.PathAnnotation,
kioutil.IndexAnnotation,
},
expectedErr: ``,
},
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo-1
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
internal.config.kubernetes.io/foo: 'bar-1'
---
apiVersion: v1
kind: Foo
metadata:
name: foo-2
annotations:
internal.config.kubernetes.io/path: 'c/d.yaml'
internal.config.kubernetes.io/index: '10'
internal.config.kubernetes.io/foo: 'bar-2'
`,
exclusions: []kioutil.AnnotationKey{
kioutil.PathAnnotation,
kioutil.IndexAnnotation,
},
expectedErr: `internal annotations differ: internal.config.kubernetes.io/foo`,
},
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo-1
annotations:
internal.config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
internal.config.kubernetes.io/foo: 'bar-1'
---
apiVersion: v1
kind: Foo
metadata:
name: foo-2
annotations:
internal.config.kubernetes.io/path: 'c/d.yaml'
internal.config.kubernetes.io/index: '10'
internal.config.kubernetes.io/foo: 'bar-1'
`,
expectedErr: `internal annotations differ: internal.config.kubernetes.io/index, internal.config.kubernetes.io/path`,
},
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foo-1
annotations:
internal.config.kubernetes.io/a: 'b'
internal.config.kubernetes.io/c: 'd'
---
apiVersion: v1
kind: Foo
metadata:
name: foo-2
annotations:
internal.config.kubernetes.io/e: 'f'
internal.config.kubernetes.io/g: 'h'
`,
expectedErr: `internal annotations differ: internal.config.kubernetes.io/a, internal.config.kubernetes.io/c, internal.config.kubernetes.io/e, internal.config.kubernetes.io/g`,
},
}

for _, tc := range tests {
rw := kio.ByteReadWriter{
Reader: bytes.NewBufferString(tc.input),
OmitReaderAnnotations: true,
}
nodes, err := rw.Read()
assert.NoError(t, err)
err = kioutil.ConfirmInternalAnnotationUnchanged(nodes[0], nodes[1], tc.exclusions...)
if tc.expectedErr == "" {
assert.NoError(t, err)
} else {
if err == nil {
t.Fatalf("expected error: %s\n", tc.expectedErr)
}
assert.Equal(t, tc.expectedErr, err.Error())
}
}
}

func TestGetInternalAnnotations(t *testing.T) {
var tests = []struct {
input string
expected map[string]string
}{
{
input: `apiVersion: v1
kind: Foo
metadata:
name: foobar
annotations:
foo: bar
internal.config.kubernetes.io/path: 'a/b.yaml'
internal.config.kubernetes.io/index: '5'
internal.config.kubernetes.io/foo: 'bar'
`,
expected: map[string]string{
"internal.config.kubernetes.io/path": "a/b.yaml",
"internal.config.kubernetes.io/index": "5",
"internal.config.kubernetes.io/foo": "bar",
},
},
}

for _, tc := range tests {
rw := kio.ByteReadWriter{
Reader: bytes.NewBufferString(tc.input),
OmitReaderAnnotations: true,
}
nodes, err := rw.Read()
assert.NoError(t, err)
assert.Equal(t, tc.expected, kioutil.GetInternalAnnotations(nodes[0]))
}
}

0 comments on commit 52f0361

Please sign in to comment.