Parse metadata directly instead of struct serialization hack

This commit is contained in:
Phillip Wittrock
2020-01-07 12:53:23 -08:00
parent e619cec090
commit 4628705494
2 changed files with 125 additions and 13 deletions

View File

@@ -6,7 +6,6 @@ package yaml
import (
"bytes"
"fmt"
"reflect"
"strings"
"gopkg.in/yaml.v3"
@@ -70,6 +69,14 @@ func IsFieldEmpty(node *MapNode) bool {
return false
}
// GetValue returns underlying yaml.Node Value field
func GetValue(node *RNode) string {
if IsEmpty(node) {
return ""
}
return node.YNode().Value
}
func IsFieldNull(node *MapNode) bool {
return node != nil && node.Value != nil && node.Value.YNode() != nil &&
node.Value.YNode().Tag == NullNodeTag
@@ -280,23 +287,79 @@ func (r *ResourceIdentifier) GetKind() string {
var ErrMissingMetadata = fmt.Errorf("missing Resource metadata")
// GetMeta returns the ResourceMeta for a RNode
// Field names
const (
AnnotationsField = "annotations"
APIVersionField = "apiVersion"
KindField = "kind"
MetadataField = "metadata"
NameField = "name"
NamespaceField = "namespace"
LabelsField = "labels"
)
// GetMeta returns the ResourceMeta for an RNode
func (rn *RNode) GetMeta() (ResourceMeta, error) {
if IsEmpty(rn) {
return ResourceMeta{}, nil
}
missingMeta := true
n := rn
if n.YNode().Kind == DocumentNode {
// get the content is this is the document node
n = NewRNode(n.Content()[0])
}
// don't decode into the struct directly or it will fail on UTF-8 issues
// which appear in comments
m := ResourceMeta{}
b := &bytes.Buffer{}
e := NewEncoder(b)
if err := e.Encode(rn.YNode()); err != nil {
return m, errors.Wrap(err)
// TODO: consider optimizing this parsing
if f := n.Field(APIVersionField); !IsFieldEmpty(f) {
m.APIVersion = GetValue(f.Value)
missingMeta = false
}
if err := e.Close(); err != nil {
return m, errors.Wrap(err)
if f := n.Field(KindField); !IsFieldEmpty(f) {
m.Kind = GetValue(f.Value)
missingMeta = false
}
d := yaml.NewDecoder(b)
d.KnownFields(false) // only want to parse the metadata
if err := d.Decode(&m); err != nil {
return m, errors.Wrap(err)
mf := n.Field(MetadataField)
if IsFieldEmpty(mf) {
if missingMeta {
return m, ErrMissingMetadata
}
return m, nil
}
if reflect.DeepEqual(m, ResourceMeta{}) {
meta := mf.Value
if f := meta.Field(NameField); !IsFieldEmpty(f) {
m.Name = f.Value.YNode().Value
missingMeta = false
}
if f := meta.Field(NamespaceField); !IsFieldEmpty(f) {
m.Namespace = GetValue(f.Value)
missingMeta = false
}
if f := meta.Field(LabelsField); !IsFieldEmpty(f) {
m.Labels = map[string]string{}
_ = f.Value.VisitFields(func(node *MapNode) error {
m.Labels[GetValue(node.Key)] = GetValue(node.Value)
return nil
})
missingMeta = false
}
if f := meta.Field(AnnotationsField); !IsFieldEmpty(f) {
m.Annotations = map[string]string{}
_ = f.Value.VisitFields(func(node *MapNode) error {
m.Annotations[GetValue(node.Key)] = GetValue(node.Value)
return nil
})
missingMeta = false
}
if missingMeta {
return m, ErrMissingMetadata
}
return m, nil

49
kyaml/yaml/types_test.go Normal file
View File

@@ -0,0 +1,49 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"testing"
"github.com/stretchr/testify/assert"
)
// Test that non-UTF8 characters in comments don't cause failures
func TestRNode_GetMeta_UTF16(t *testing.T) {
sr, err := Parse(`apiVersion: rbac.istio.io/v1alpha1
kind: ServiceRole
metadata:
name: wildcard
namespace: default
# If set to [“*”], it refers to all services in the namespace
annotations:
foo: bar
spec:
rules:
# There is one service in default namespace, should not result in a validation error
# If set to [“*”], it refers to all services in the namespace
- services: ["*"]
methods: ["GET", "HEAD"]
`)
if !assert.NoError(t, err) {
t.FailNow()
}
actual, err := sr.GetMeta()
if !assert.NoError(t, err) {
t.FailNow()
}
expected := ResourceMeta{
APIVersion: "rbac.istio.io/v1alpha1",
Kind: "ServiceRole",
ObjectMeta: ObjectMeta{
Name: "wildcard",
Namespace: "default",
Annotations: map[string]string{"foo": "bar"},
},
}
if !assert.Equal(t, expected, actual) {
t.FailNow()
}
}