mirror of
https://github.com/k3s-io/k3s.git
synced 2024-06-07 19:41:36 +00:00
e204d863a5
* Update Kubernetes to v1.22.1 * Update dependent modules to track with upstream Signed-off-by: Brad Davidson <brad.davidson@rancher.com>
1098 lines
28 KiB
Go
1098 lines
28 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package yaml
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"sigs.k8s.io/kustomize/kyaml/errors"
|
|
"sigs.k8s.io/kustomize/kyaml/internal/forked/github.com/go-yaml/yaml"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/labels"
|
|
)
|
|
|
|
// MakeNullNode returns an RNode that represents an empty document.
|
|
func MakeNullNode() *RNode {
|
|
return NewRNode(&Node{Tag: NodeTagNull})
|
|
}
|
|
|
|
// IsMissingOrNull is true if the RNode is nil or explicitly tagged null.
|
|
// TODO: make this a method on RNode.
|
|
func IsMissingOrNull(node *RNode) bool {
|
|
return node.IsNil() || node.YNode().Tag == NodeTagNull
|
|
}
|
|
|
|
// IsEmptyMap returns true if the RNode is an empty node or an empty map.
|
|
// TODO: make this a method on RNode.
|
|
func IsEmptyMap(node *RNode) bool {
|
|
return IsMissingOrNull(node) || IsYNodeEmptyMap(node.YNode())
|
|
}
|
|
|
|
// GetValue returns underlying yaml.Node Value field
|
|
func GetValue(node *RNode) string {
|
|
if IsMissingOrNull(node) {
|
|
return ""
|
|
}
|
|
return node.YNode().Value
|
|
}
|
|
|
|
// Parse parses a yaml string into an *RNode.
|
|
// To parse multiple resources, consider a kio.ByteReader
|
|
func Parse(value string) (*RNode, error) {
|
|
return Parser{Value: value}.Filter(nil)
|
|
}
|
|
|
|
// ReadFile parses a single Resource from a yaml file.
|
|
// To parse multiple resources, consider a kio.ByteReader
|
|
func ReadFile(path string) (*RNode, error) {
|
|
b, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Parse(string(b))
|
|
}
|
|
|
|
// WriteFile writes a single Resource to a yaml file
|
|
func WriteFile(node *RNode, path string) error {
|
|
out, err := node.String()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ioutil.WriteFile(path, []byte(out), 0600)
|
|
}
|
|
|
|
// UpdateFile reads the file at path, applies the filter to it, and write the result back.
|
|
// path must contain a exactly 1 resource (YAML).
|
|
func UpdateFile(filter Filter, path string) error {
|
|
// Read the yaml
|
|
y, err := ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the yaml
|
|
if err := y.PipeE(filter); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write the yaml
|
|
return WriteFile(y, path)
|
|
}
|
|
|
|
// MustParse parses a yaml string into an *RNode and panics if there is an error
|
|
func MustParse(value string) *RNode {
|
|
v, err := Parser{Value: value}.Filter(nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
// NewScalarRNode returns a new Scalar *RNode containing the provided scalar value.
|
|
func NewScalarRNode(value string) *RNode {
|
|
return &RNode{
|
|
value: &yaml.Node{
|
|
Kind: yaml.ScalarNode,
|
|
Value: value,
|
|
}}
|
|
}
|
|
|
|
// NewStringRNode returns a new Scalar *RNode containing the provided string.
|
|
// If the string is non-utf8, it will be base64 encoded, and the tag
|
|
// will indicate binary data.
|
|
func NewStringRNode(value string) *RNode {
|
|
n := yaml.Node{Kind: yaml.ScalarNode}
|
|
n.SetString(value)
|
|
return NewRNode(&n)
|
|
}
|
|
|
|
// NewListRNode returns a new List *RNode containing the provided scalar values.
|
|
func NewListRNode(values ...string) *RNode {
|
|
seq := &RNode{value: &yaml.Node{Kind: yaml.SequenceNode}}
|
|
for _, v := range values {
|
|
seq.value.Content = append(seq.value.Content, &yaml.Node{
|
|
Kind: yaml.ScalarNode,
|
|
Value: v,
|
|
})
|
|
}
|
|
return seq
|
|
}
|
|
|
|
// NewMapRNode returns a new Map *RNode containing the provided values
|
|
func NewMapRNode(values *map[string]string) *RNode {
|
|
m := &RNode{value: &yaml.Node{
|
|
Kind: yaml.MappingNode,
|
|
}}
|
|
if values == nil {
|
|
return m
|
|
}
|
|
|
|
for k, v := range *values {
|
|
m.value.Content = append(m.value.Content, &yaml.Node{
|
|
Kind: yaml.ScalarNode,
|
|
Value: k,
|
|
}, &yaml.Node{
|
|
Kind: yaml.ScalarNode,
|
|
Value: v,
|
|
})
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
// NewRNode returns a new RNode pointer containing the provided Node.
|
|
func NewRNode(value *yaml.Node) *RNode {
|
|
return &RNode{value: value}
|
|
}
|
|
|
|
// RNode provides functions for manipulating Kubernetes Resources
|
|
// Objects unmarshalled into *yaml.Nodes
|
|
type RNode struct {
|
|
// fieldPath contains the path from the root of the KubernetesObject to
|
|
// this field.
|
|
// Only field names are captured in the path.
|
|
// e.g. a image field in a Deployment would be
|
|
// 'spec.template.spec.containers.image'
|
|
fieldPath []string
|
|
|
|
// FieldValue contains the value.
|
|
// FieldValue is always set:
|
|
// field: field value
|
|
// list entry: list entry value
|
|
// object root: object root
|
|
value *yaml.Node
|
|
|
|
Match []string
|
|
}
|
|
|
|
// Copy returns a distinct copy.
|
|
func (rn *RNode) Copy() *RNode {
|
|
if rn == nil {
|
|
return nil
|
|
}
|
|
result := *rn
|
|
result.value = CopyYNode(rn.value)
|
|
return &result
|
|
}
|
|
|
|
var ErrMissingMetadata = fmt.Errorf("missing Resource metadata")
|
|
|
|
// IsNil is true if the node is nil, or its underlying YNode is nil.
|
|
func (rn *RNode) IsNil() bool {
|
|
return rn == nil || rn.YNode() == nil
|
|
}
|
|
|
|
// IsTaggedNull is true if a non-nil node is explicitly tagged Null.
|
|
func (rn *RNode) IsTaggedNull() bool {
|
|
return !rn.IsNil() && IsYNodeTaggedNull(rn.YNode())
|
|
}
|
|
|
|
// IsNilOrEmpty is true if the node is nil,
|
|
// has no YNode, or has YNode that appears empty.
|
|
func (rn *RNode) IsNilOrEmpty() bool {
|
|
return rn.IsNil() ||
|
|
IsYNodeTaggedNull(rn.YNode()) ||
|
|
IsYNodeEmptyMap(rn.YNode()) ||
|
|
IsYNodeEmptySeq(rn.YNode()) ||
|
|
IsYNodeZero(rn.YNode())
|
|
}
|
|
|
|
// GetMeta returns the ResourceMeta for an RNode
|
|
func (rn *RNode) GetMeta() (ResourceMeta, error) {
|
|
if IsMissingOrNull(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{}
|
|
|
|
// TODO: consider optimizing this parsing
|
|
if f := n.Field(APIVersionField); !f.IsNilOrEmpty() {
|
|
m.APIVersion = GetValue(f.Value)
|
|
missingMeta = false
|
|
}
|
|
if f := n.Field(KindField); !f.IsNilOrEmpty() {
|
|
m.Kind = GetValue(f.Value)
|
|
missingMeta = false
|
|
}
|
|
|
|
mf := n.Field(MetadataField)
|
|
if mf.IsNilOrEmpty() {
|
|
if missingMeta {
|
|
return m, ErrMissingMetadata
|
|
}
|
|
return m, nil
|
|
}
|
|
meta := mf.Value
|
|
|
|
if f := meta.Field(NameField); !f.IsNilOrEmpty() {
|
|
m.Name = f.Value.YNode().Value
|
|
missingMeta = false
|
|
}
|
|
if f := meta.Field(NamespaceField); !f.IsNilOrEmpty() {
|
|
m.Namespace = GetValue(f.Value)
|
|
missingMeta = false
|
|
}
|
|
|
|
if f := meta.Field(LabelsField); !f.IsNilOrEmpty() {
|
|
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); !f.IsNilOrEmpty() {
|
|
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
|
|
}
|
|
|
|
// Pipe sequentially invokes each Filter, and passes the result to the next
|
|
// Filter.
|
|
//
|
|
// Analogous to http://www.linfo.org/pipes.html
|
|
//
|
|
// * rn is provided as input to the first Filter.
|
|
// * if any Filter returns an error, immediately return the error
|
|
// * if any Filter returns a nil RNode, immediately return nil, nil
|
|
// * if all Filters succeed with non-empty results, return the final result
|
|
func (rn *RNode) Pipe(functions ...Filter) (*RNode, error) {
|
|
// check if rn is nil to make chaining Pipe calls easier
|
|
if rn == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var v *RNode
|
|
var err error
|
|
if rn.value != nil && rn.value.Kind == yaml.DocumentNode {
|
|
// the first node may be a DocumentNode containing a single MappingNode
|
|
v = &RNode{value: rn.value.Content[0]}
|
|
} else {
|
|
v = rn
|
|
}
|
|
|
|
// return each fn in sequence until encountering an error or missing value
|
|
for _, c := range functions {
|
|
v, err = c.Filter(v)
|
|
if err != nil || v == nil {
|
|
return v, errors.Wrap(err)
|
|
}
|
|
}
|
|
return v, err
|
|
}
|
|
|
|
// PipeE runs Pipe, dropping the *RNode return value.
|
|
// Useful for directly returning the Pipe error value from functions.
|
|
func (rn *RNode) PipeE(functions ...Filter) error {
|
|
_, err := rn.Pipe(functions...)
|
|
return errors.Wrap(err)
|
|
}
|
|
|
|
// Document returns the Node for the value.
|
|
func (rn *RNode) Document() *yaml.Node {
|
|
return rn.value
|
|
}
|
|
|
|
// YNode returns the yaml.Node value. If the yaml.Node value is a DocumentNode,
|
|
// YNode will return the DocumentNode Content entry instead of the DocumentNode.
|
|
func (rn *RNode) YNode() *yaml.Node {
|
|
if rn == nil || rn.value == nil {
|
|
return nil
|
|
}
|
|
if rn.value.Kind == yaml.DocumentNode {
|
|
return rn.value.Content[0]
|
|
}
|
|
return rn.value
|
|
}
|
|
|
|
// SetYNode sets the yaml.Node value on an RNode.
|
|
func (rn *RNode) SetYNode(node *yaml.Node) {
|
|
if rn.value == nil || node == nil {
|
|
rn.value = node
|
|
return
|
|
}
|
|
*rn.value = *node
|
|
}
|
|
|
|
// GetKind returns the kind, if it exists, else empty string.
|
|
func (rn *RNode) GetKind() string {
|
|
if node := rn.getMapFieldValue(KindField); node != nil {
|
|
return node.Value
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// SetKind sets the kind.
|
|
func (rn *RNode) SetKind(k string) {
|
|
rn.SetMapField(NewScalarRNode(k), KindField)
|
|
}
|
|
|
|
// GetApiVersion returns the apiversion, if it exists, else empty string.
|
|
func (rn *RNode) GetApiVersion() string {
|
|
if node := rn.getMapFieldValue(APIVersionField); node != nil {
|
|
return node.Value
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// SetApiVersion sets the apiVersion.
|
|
func (rn *RNode) SetApiVersion(av string) {
|
|
rn.SetMapField(NewScalarRNode(av), APIVersionField)
|
|
}
|
|
|
|
// getMapFieldValue returns the value (*yaml.Node) of a mapping field.
|
|
// The value might be nil. Also, the function returns nil, not an error,
|
|
// if this node is not a mapping node, or if this node does not have the
|
|
// given field, so this function cannot be used to make distinctions
|
|
// between these cases.
|
|
func (rn *RNode) getMapFieldValue(field string) *yaml.Node {
|
|
for i := 0; i < len(rn.Content()); i = IncrementFieldIndex(i) {
|
|
if rn.Content()[i].Value == field {
|
|
return rn.Content()[i+1]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetName returns the name, or empty string if
|
|
// field not found. The setter is more restrictive.
|
|
func (rn *RNode) GetName() string {
|
|
return rn.getMetaStringField(NameField)
|
|
}
|
|
|
|
// getMetaStringField returns the value of a string field in metadata.
|
|
func (rn *RNode) getMetaStringField(fName string) string {
|
|
md := rn.getMetaData()
|
|
if md == nil {
|
|
return ""
|
|
}
|
|
f := md.Field(fName)
|
|
if f.IsNilOrEmpty() {
|
|
return ""
|
|
}
|
|
return GetValue(f.Value)
|
|
}
|
|
|
|
// getMetaData returns the RNode holding the value of the metadata field.
|
|
// Return nil if field not found (no error).
|
|
func (rn *RNode) getMetaData() *RNode {
|
|
if IsMissingOrNull(rn) {
|
|
return nil
|
|
}
|
|
var n *RNode
|
|
if rn.YNode().Kind == DocumentNode {
|
|
// get the content if this is the document node
|
|
n = NewRNode(rn.Content()[0])
|
|
} else {
|
|
n = rn
|
|
}
|
|
mf := n.Field(MetadataField)
|
|
if mf.IsNilOrEmpty() {
|
|
return nil
|
|
}
|
|
return mf.Value
|
|
}
|
|
|
|
// SetName sets the metadata name field.
|
|
func (rn *RNode) SetName(name string) error {
|
|
return rn.SetMapField(NewScalarRNode(name), MetadataField, NameField)
|
|
}
|
|
|
|
// GetNamespace gets the metadata namespace field, or empty string if
|
|
// field not found. The setter is more restrictive.
|
|
func (rn *RNode) GetNamespace() string {
|
|
return rn.getMetaStringField(NamespaceField)
|
|
}
|
|
|
|
// SetNamespace tries to set the metadata namespace field. If the argument
|
|
// is empty, the field is dropped.
|
|
func (rn *RNode) SetNamespace(ns string) error {
|
|
meta, err := rn.Pipe(Lookup(MetadataField))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ns == "" {
|
|
if rn == nil {
|
|
return nil
|
|
}
|
|
return meta.PipeE(Clear(NamespaceField))
|
|
}
|
|
return rn.SetMapField(
|
|
NewScalarRNode(ns), MetadataField, NamespaceField)
|
|
}
|
|
|
|
// GetAnnotations gets the metadata annotations field.
|
|
// If the field is missing, returns an empty map.
|
|
// Use another method to check for missing metadata.
|
|
func (rn *RNode) GetAnnotations() map[string]string {
|
|
meta := rn.getMetaData()
|
|
if meta == nil {
|
|
return make(map[string]string)
|
|
}
|
|
return rn.getMapFromMeta(meta, AnnotationsField)
|
|
}
|
|
|
|
// SetAnnotations tries to set the metadata annotations field.
|
|
func (rn *RNode) SetAnnotations(m map[string]string) error {
|
|
return rn.setMapInMetadata(m, AnnotationsField)
|
|
}
|
|
|
|
// GetLabels gets the metadata labels field.
|
|
// If the field is missing, returns an empty map.
|
|
// Use another method to check for missing metadata.
|
|
func (rn *RNode) GetLabels() map[string]string {
|
|
meta := rn.getMetaData()
|
|
if meta == nil {
|
|
return make(map[string]string)
|
|
}
|
|
return rn.getMapFromMeta(meta, LabelsField)
|
|
}
|
|
|
|
// getMapFromMeta returns map, sometimes empty, from metadata.
|
|
func (rn *RNode) getMapFromMeta(meta *RNode, fName string) map[string]string {
|
|
result := make(map[string]string)
|
|
if f := meta.Field(fName); !f.IsNilOrEmpty() {
|
|
_ = f.Value.VisitFields(func(node *MapNode) error {
|
|
result[GetValue(node.Key)] = GetValue(node.Value)
|
|
return nil
|
|
})
|
|
}
|
|
return result
|
|
}
|
|
|
|
// SetLabels sets the metadata labels field.
|
|
func (rn *RNode) SetLabels(m map[string]string) error {
|
|
return rn.setMapInMetadata(m, LabelsField)
|
|
}
|
|
|
|
// This established proper quoting on string values, and sorts by key.
|
|
func (rn *RNode) setMapInMetadata(m map[string]string, field string) error {
|
|
meta, err := rn.Pipe(LookupCreate(MappingNode, MetadataField))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err = meta.PipeE(Clear(field)); err != nil {
|
|
return err
|
|
}
|
|
if len(m) == 0 {
|
|
return nil
|
|
}
|
|
mapNode, err := meta.Pipe(LookupCreate(MappingNode, field))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, k := range SortedMapKeys(m) {
|
|
if _, err := mapNode.Pipe(
|
|
SetField(k, NewStringRNode(m[k]))); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (rn *RNode) SetMapField(value *RNode, path ...string) error {
|
|
return rn.PipeE(
|
|
LookupCreate(yaml.MappingNode, path[0:len(path)-1]...),
|
|
SetField(path[len(path)-1], value),
|
|
)
|
|
}
|
|
|
|
func (rn *RNode) GetDataMap() map[string]string {
|
|
n, err := rn.Pipe(Lookup(DataField))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
result := map[string]string{}
|
|
_ = n.VisitFields(func(node *MapNode) error {
|
|
result[GetValue(node.Key)] = GetValue(node.Value)
|
|
return nil
|
|
})
|
|
return result
|
|
}
|
|
|
|
func (rn *RNode) GetBinaryDataMap() map[string]string {
|
|
n, err := rn.Pipe(Lookup(BinaryDataField))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
result := map[string]string{}
|
|
_ = n.VisitFields(func(node *MapNode) error {
|
|
result[GetValue(node.Key)] = GetValue(node.Value)
|
|
return nil
|
|
})
|
|
return result
|
|
}
|
|
|
|
// GetValidatedDataMap retrieves the data map and returns an error if the data
|
|
// map contains entries which are not included in the expectedKeys set.
|
|
func (rn *RNode) GetValidatedDataMap(expectedKeys []string) (map[string]string, error) {
|
|
dataMap := rn.GetDataMap()
|
|
err := rn.validateDataMap(dataMap, expectedKeys)
|
|
return dataMap, err
|
|
}
|
|
|
|
func (rn *RNode) validateDataMap(dataMap map[string]string, expectedKeys []string) error {
|
|
if dataMap == nil {
|
|
return fmt.Errorf("The datamap is unassigned")
|
|
}
|
|
for key := range dataMap {
|
|
found := false
|
|
for _, expected := range expectedKeys {
|
|
if expected == key {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("an unexpected key (%v) was found", key)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (rn *RNode) SetDataMap(m map[string]string) {
|
|
if rn == nil {
|
|
log.Fatal("cannot set data map on nil Rnode")
|
|
}
|
|
if err := rn.PipeE(Clear(DataField)); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if len(m) == 0 {
|
|
return
|
|
}
|
|
if err := rn.LoadMapIntoConfigMapData(m); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func (rn *RNode) SetBinaryDataMap(m map[string]string) {
|
|
if rn == nil {
|
|
log.Fatal("cannot set binaryData map on nil Rnode")
|
|
}
|
|
if err := rn.PipeE(Clear(BinaryDataField)); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if len(m) == 0 {
|
|
return
|
|
}
|
|
if err := rn.LoadMapIntoConfigMapBinaryData(m); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
// AppendToFieldPath appends a field name to the FieldPath.
|
|
func (rn *RNode) AppendToFieldPath(parts ...string) {
|
|
rn.fieldPath = append(rn.fieldPath, parts...)
|
|
}
|
|
|
|
// FieldPath returns the field path from the Resource root node, to rn.
|
|
// Does not include list indexes.
|
|
func (rn *RNode) FieldPath() []string {
|
|
return rn.fieldPath
|
|
}
|
|
|
|
// String returns string representation of the RNode
|
|
func (rn *RNode) String() (string, error) {
|
|
if rn == nil {
|
|
return "", nil
|
|
}
|
|
return String(rn.value)
|
|
}
|
|
|
|
// MustString returns string representation of the RNode or panics if there is an error
|
|
func (rn *RNode) MustString() string {
|
|
s, err := rn.String()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Content returns Node Content field.
|
|
func (rn *RNode) Content() []*yaml.Node {
|
|
if rn == nil {
|
|
return nil
|
|
}
|
|
return rn.YNode().Content
|
|
}
|
|
|
|
// Fields returns the list of field names for a MappingNode.
|
|
// Returns an error for non-MappingNodes.
|
|
func (rn *RNode) Fields() ([]string, error) {
|
|
if err := ErrorIfInvalid(rn, yaml.MappingNode); err != nil {
|
|
return nil, errors.Wrap(err)
|
|
}
|
|
var fields []string
|
|
for i := 0; i < len(rn.Content()); i += 2 {
|
|
fields = append(fields, rn.Content()[i].Value)
|
|
}
|
|
return fields, nil
|
|
}
|
|
|
|
// FieldRNodes returns the list of field key RNodes for a MappingNode.
|
|
// Returns an error for non-MappingNodes.
|
|
func (rn *RNode) FieldRNodes() ([]*RNode, error) {
|
|
if err := ErrorIfInvalid(rn, yaml.MappingNode); err != nil {
|
|
return nil, errors.Wrap(err)
|
|
}
|
|
var fields []*RNode
|
|
for i := 0; i < len(rn.Content()); i += 2 {
|
|
yNode := rn.Content()[i]
|
|
// for each key node in the input mapping node contents create equivalent rNode
|
|
rNode := &RNode{}
|
|
rNode.SetYNode(yNode)
|
|
fields = append(fields, rNode)
|
|
}
|
|
return fields, nil
|
|
}
|
|
|
|
// Field returns a fieldName, fieldValue pair for MappingNodes.
|
|
// Returns nil for non-MappingNodes.
|
|
func (rn *RNode) Field(field string) *MapNode {
|
|
if rn.YNode().Kind != yaml.MappingNode {
|
|
return nil
|
|
}
|
|
for i := 0; i < len(rn.Content()); i = IncrementFieldIndex(i) {
|
|
isMatchingField := rn.Content()[i].Value == field
|
|
if isMatchingField {
|
|
return &MapNode{Key: NewRNode(rn.Content()[i]), Value: NewRNode(rn.Content()[i+1])}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// VisitFields calls fn for each field in the RNode.
|
|
// Returns an error for non-MappingNodes.
|
|
func (rn *RNode) VisitFields(fn func(node *MapNode) error) error {
|
|
// get the list of srcFieldNames
|
|
srcFieldNames, err := rn.Fields()
|
|
if err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
|
|
// visit each field
|
|
for _, fieldName := range srcFieldNames {
|
|
if err := fn(rn.Field(fieldName)); err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Elements returns the list of elements in the RNode.
|
|
// Returns an error for non-SequenceNodes.
|
|
func (rn *RNode) Elements() ([]*RNode, error) {
|
|
if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
|
|
return nil, errors.Wrap(err)
|
|
}
|
|
var elements []*RNode
|
|
for i := 0; i < len(rn.Content()); i++ {
|
|
elements = append(elements, NewRNode(rn.Content()[i]))
|
|
}
|
|
return elements, nil
|
|
}
|
|
|
|
// ElementValues returns a list of all observed values for a given field name
|
|
// in a list of elements.
|
|
// Returns error for non-SequenceNodes.
|
|
func (rn *RNode) ElementValues(key string) ([]string, error) {
|
|
if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
|
|
return nil, errors.Wrap(err)
|
|
}
|
|
var elements []string
|
|
for i := 0; i < len(rn.Content()); i++ {
|
|
field := NewRNode(rn.Content()[i]).Field(key)
|
|
if !field.IsNilOrEmpty() {
|
|
elements = append(elements, field.Value.YNode().Value)
|
|
}
|
|
}
|
|
return elements, nil
|
|
}
|
|
|
|
// ElementValuesList returns a list of lists, where each list is a set of
|
|
// values corresponding to each key in keys.
|
|
// Returns error for non-SequenceNodes.
|
|
func (rn *RNode) ElementValuesList(keys []string) ([][]string, error) {
|
|
if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
|
|
return nil, errors.Wrap(err)
|
|
}
|
|
elements := make([][]string, len(rn.Content()))
|
|
|
|
for i := 0; i < len(rn.Content()); i++ {
|
|
for _, key := range keys {
|
|
field := NewRNode(rn.Content()[i]).Field(key)
|
|
if field.IsNilOrEmpty() {
|
|
elements[i] = append(elements[i], "")
|
|
} else {
|
|
elements[i] = append(elements[i], field.Value.YNode().Value)
|
|
}
|
|
}
|
|
}
|
|
return elements, nil
|
|
}
|
|
|
|
// Element returns the element in the list which contains the field matching the value.
|
|
// Returns nil for non-SequenceNodes or if no Element matches.
|
|
func (rn *RNode) Element(key, value string) *RNode {
|
|
if rn.YNode().Kind != yaml.SequenceNode {
|
|
return nil
|
|
}
|
|
elem, err := rn.Pipe(MatchElement(key, value))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return elem
|
|
}
|
|
|
|
// ElementList returns the element in the list in which all fields keys[i] matches all
|
|
// corresponding values[i].
|
|
// Returns nil for non-SequenceNodes or if no Element matches.
|
|
func (rn *RNode) ElementList(keys []string, values []string) *RNode {
|
|
if rn.YNode().Kind != yaml.SequenceNode {
|
|
return nil
|
|
}
|
|
elem, err := rn.Pipe(MatchElementList(keys, values))
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return elem
|
|
}
|
|
|
|
// VisitElements calls fn for each element in a SequenceNode.
|
|
// Returns an error for non-SequenceNodes
|
|
func (rn *RNode) VisitElements(fn func(node *RNode) error) error {
|
|
elements, err := rn.Elements()
|
|
if err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
|
|
for i := range elements {
|
|
if err := fn(elements[i]); err != nil {
|
|
return errors.Wrap(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AssociativeSequenceKeys is a map of paths to sequences that have associative keys.
|
|
// The order sets the precedence of the merge keys -- if multiple keys are present
|
|
// in Resources in a list, then the FIRST key which ALL elements in the list have is used as the
|
|
// associative key for merging that list.
|
|
// Only infer name as a merge key.
|
|
var AssociativeSequenceKeys = []string{"name"}
|
|
|
|
// IsAssociative returns true if the RNode contains an AssociativeSequenceKey as a field.
|
|
func (rn *RNode) IsAssociative() bool {
|
|
return rn.GetAssociativeKey() != ""
|
|
}
|
|
|
|
// GetAssociativeKey returns the AssociativeSequenceKey used to merge the elements in the
|
|
// SequenceNode, or "" if the list is not associative.
|
|
func (rn *RNode) GetAssociativeKey() string {
|
|
// look for any associative keys in the first element
|
|
for _, key := range AssociativeSequenceKeys {
|
|
if checkKey(key, rn.Content()) {
|
|
return key
|
|
}
|
|
}
|
|
|
|
// element doesn't have an associative keys
|
|
return ""
|
|
}
|
|
|
|
// MarshalJSON creates a byte slice from the RNode.
|
|
func (rn *RNode) MarshalJSON() ([]byte, error) {
|
|
s, err := rn.String()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if rn.YNode().Kind == SequenceNode {
|
|
var a []interface{}
|
|
if err := Unmarshal([]byte(s), &a); err != nil {
|
|
return nil, err
|
|
}
|
|
return json.Marshal(a)
|
|
}
|
|
|
|
m := map[string]interface{}{}
|
|
if err := Unmarshal([]byte(s), &m); err != nil {
|
|
return nil, err
|
|
}
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
// UnmarshalJSON overwrites this RNode with data from []byte.
|
|
func (rn *RNode) UnmarshalJSON(b []byte) error {
|
|
m := map[string]interface{}{}
|
|
if err := json.Unmarshal(b, &m); err != nil {
|
|
return err
|
|
}
|
|
r, err := FromMap(m)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rn.value = r.value
|
|
return nil
|
|
}
|
|
|
|
// GetValidatedMetadata returns metadata after subjecting it to some tests.
|
|
func (rn *RNode) GetValidatedMetadata() (ResourceMeta, error) {
|
|
m, err := rn.GetMeta()
|
|
if err != nil {
|
|
return m, err
|
|
}
|
|
if m.Kind == "" {
|
|
return m, fmt.Errorf("missing kind in object %v", m)
|
|
}
|
|
if strings.HasSuffix(m.Kind, "List") {
|
|
// A list doesn't require a name.
|
|
return m, nil
|
|
}
|
|
if m.NameMeta.Name == "" {
|
|
return m, fmt.Errorf("missing metadata.name in object %v", m)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
// MatchesAnnotationSelector returns true on a selector match to annotations.
|
|
func (rn *RNode) MatchesAnnotationSelector(selector string) (bool, error) {
|
|
s, err := labels.Parse(selector)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return s.Matches(labels.Set(rn.GetAnnotations())), nil
|
|
}
|
|
|
|
// MatchesLabelSelector returns true on a selector match to labels.
|
|
func (rn *RNode) MatchesLabelSelector(selector string) (bool, error) {
|
|
s, err := labels.Parse(selector)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return s.Matches(labels.Set(rn.GetLabels())), nil
|
|
}
|
|
|
|
// HasNilEntryInList returns true if the RNode contains a list which has
|
|
// a nil item, along with the path to the missing item.
|
|
// TODO(broken): This doesn't do what it claims to do.
|
|
// (see TODO in unit test and pr 1513).
|
|
func (rn *RNode) HasNilEntryInList() (bool, string) {
|
|
return hasNilEntryInList(rn.value)
|
|
}
|
|
|
|
func hasNilEntryInList(in interface{}) (bool, string) {
|
|
switch v := in.(type) {
|
|
case map[string]interface{}:
|
|
for key, s := range v {
|
|
if result, path := hasNilEntryInList(s); result {
|
|
return result, key + "/" + path
|
|
}
|
|
}
|
|
case []interface{}:
|
|
for index, s := range v {
|
|
if s == nil {
|
|
return true, ""
|
|
}
|
|
if result, path := hasNilEntryInList(s); result {
|
|
return result, "[" + strconv.Itoa(index) + "]/" + path
|
|
}
|
|
}
|
|
}
|
|
return false, ""
|
|
}
|
|
|
|
func FromMap(m map[string]interface{}) (*RNode, error) {
|
|
c, err := Marshal(m)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Parse(string(c))
|
|
}
|
|
|
|
func (rn *RNode) Map() (map[string]interface{}, error) {
|
|
if rn == nil || rn.value == nil {
|
|
return make(map[string]interface{}), nil
|
|
}
|
|
var result map[string]interface{}
|
|
if err := rn.value.Decode(&result); err != nil {
|
|
// Should not be able to create an RNode that cannot be decoded;
|
|
// this is an unrecoverable error.
|
|
str, _ := rn.String()
|
|
return nil, fmt.Errorf("received error %w for the following resource:\n%s", err, str)
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ConvertJSONToYamlNode parses input json string and returns equivalent yaml node
|
|
func ConvertJSONToYamlNode(jsonStr string) (*RNode, error) {
|
|
var body map[string]interface{}
|
|
err := json.Unmarshal([]byte(jsonStr), &body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
yml, err := yaml.Marshal(body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
node, err := Parse(string(yml))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return node, nil
|
|
}
|
|
|
|
// checkKey returns true if all elems have the key
|
|
func checkKey(key string, elems []*Node) bool {
|
|
count := 0
|
|
for i := range elems {
|
|
elem := NewRNode(elems[i])
|
|
if elem.Field(key) != nil {
|
|
count++
|
|
}
|
|
}
|
|
return count == len(elems)
|
|
}
|
|
|
|
// Deprecated: use pipes instead.
|
|
// GetSlice returns the contents of the slice field at the given path.
|
|
func (rn *RNode) GetSlice(path string) ([]interface{}, error) {
|
|
value, err := rn.GetFieldValue(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if sliceValue, ok := value.([]interface{}); ok {
|
|
return sliceValue, nil
|
|
}
|
|
return nil, fmt.Errorf("node %s is not a slice", path)
|
|
}
|
|
|
|
// Deprecated: use pipes instead.
|
|
// GetString returns the contents of the string field at the given path.
|
|
func (rn *RNode) GetString(path string) (string, error) {
|
|
value, err := rn.GetFieldValue(path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if v, ok := value.(string); ok {
|
|
return v, nil
|
|
}
|
|
return "", fmt.Errorf("node %s is not a string: %v", path, value)
|
|
}
|
|
|
|
// Deprecated: use slash paths instead.
|
|
// GetFieldValue finds period delimited fields.
|
|
// TODO: When doing kustomize var replacement, which is likely a
|
|
// a primary use of this function and the reason it returns interface{}
|
|
// rather than string, we do conversion from Nodes to Go types and back
|
|
// to nodes. We should figure out how to do replacement using raw nodes,
|
|
// assuming we keep the var feature in kustomize.
|
|
// The other end of this is: refvar.go:updateNodeValue.
|
|
func (rn *RNode) GetFieldValue(path string) (interface{}, error) {
|
|
fields := convertSliceIndex(strings.Split(path, "."))
|
|
rn, err := rn.Pipe(Lookup(fields...))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if rn == nil {
|
|
return nil, NoFieldError{path}
|
|
}
|
|
yn := rn.YNode()
|
|
|
|
// If this is an alias node, resolve it
|
|
if yn.Kind == yaml.AliasNode {
|
|
yn = yn.Alias
|
|
}
|
|
|
|
// Return value as map for DocumentNode and MappingNode kinds
|
|
if yn.Kind == yaml.DocumentNode || yn.Kind == yaml.MappingNode {
|
|
var result map[string]interface{}
|
|
if err := yn.Decode(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
// Return value as slice for SequenceNode kind
|
|
if yn.Kind == yaml.SequenceNode {
|
|
var result []interface{}
|
|
if err := yn.Decode(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
return result, nil
|
|
}
|
|
if yn.Kind != yaml.ScalarNode {
|
|
return nil, fmt.Errorf("expected ScalarNode, got Kind=%d", yn.Kind)
|
|
}
|
|
|
|
switch yn.Tag {
|
|
case NodeTagString:
|
|
return yn.Value, nil
|
|
case NodeTagInt:
|
|
return strconv.Atoi(yn.Value)
|
|
case NodeTagFloat:
|
|
return strconv.ParseFloat(yn.Value, 64)
|
|
case NodeTagBool:
|
|
return strconv.ParseBool(yn.Value)
|
|
default:
|
|
// Possibly this should be an error or log.
|
|
return yn.Value, nil
|
|
}
|
|
}
|
|
|
|
// convertSliceIndex traverses the items in `fields` and find
|
|
// if there is a slice index in the item and change it to a
|
|
// valid Lookup field path. For example, 'ports[0]' will be
|
|
// converted to 'ports' and '0'.
|
|
func convertSliceIndex(fields []string) []string {
|
|
var res []string
|
|
for _, s := range fields {
|
|
if !strings.HasSuffix(s, "]") {
|
|
res = append(res, s)
|
|
continue
|
|
}
|
|
re := regexp.MustCompile(`^(.*)\[(\d+)\]$`)
|
|
groups := re.FindStringSubmatch(s)
|
|
if len(groups) == 0 {
|
|
// no match, add to result
|
|
res = append(res, s)
|
|
continue
|
|
}
|
|
if groups[1] != "" {
|
|
res = append(res, groups[1])
|
|
}
|
|
res = append(res, groups[2])
|
|
}
|
|
return res
|
|
}
|
|
|
|
type NoFieldError struct {
|
|
Field string
|
|
}
|
|
|
|
func (e NoFieldError) Error() string {
|
|
return fmt.Sprintf("no field named '%s'", e.Field)
|
|
}
|