mirror of https://github.com/k3s-io/k3s
327 lines
9.0 KiB
Go
327 lines
9.0 KiB
Go
// Copyright 2019 The Kubernetes Authors.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package walk
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/go-errors/errors"
|
|
"sigs.k8s.io/kustomize/kyaml/openapi"
|
|
"sigs.k8s.io/kustomize/kyaml/sets"
|
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
|
)
|
|
|
|
// appendListNode will append the nodes from src to dst and return dst.
|
|
// src and dst should be both sequence node. key is used to call ElementSetter.
|
|
// ElementSetter will use key-value pair to find and set the element in sequence
|
|
// node.
|
|
func appendListNode(dst, src *yaml.RNode, keys []string) (*yaml.RNode, error) {
|
|
var err error
|
|
for _, elem := range src.Content() {
|
|
// If key is empty, we know this is a scalar value and we can directly set the
|
|
// node
|
|
if keys[0] == "" {
|
|
_, err = dst.Pipe(yaml.ElementSetter{
|
|
Element: elem,
|
|
Keys: []string{""},
|
|
Values: []string{elem.Value},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
continue
|
|
}
|
|
|
|
// we need to get the value for key so that we can find the element to set
|
|
// in sequence.
|
|
v := []string{}
|
|
for _, key := range keys {
|
|
tmpNode := yaml.NewRNode(elem)
|
|
valueNode, err := tmpNode.Pipe(yaml.Get(key))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if valueNode.IsNil() {
|
|
// no key found, directly append to dst
|
|
err = dst.PipeE(yaml.Append(elem))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
continue
|
|
}
|
|
v = append(v, valueNode.YNode().Value)
|
|
}
|
|
|
|
// We use the key and value from elem to find the corresponding element in dst.
|
|
// Then we will use ElementSetter to replace the element with elem. If we cannot
|
|
_, err = dst.Pipe(yaml.ElementSetter{
|
|
Element: elem,
|
|
Keys: keys,
|
|
Values: v,
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return dst, nil
|
|
}
|
|
|
|
// validateKeys returns a list of valid key-value pairs
|
|
// if secondary merge key values are missing, use only the available merge keys
|
|
func validateKeys(valuesList [][]string, values []string, keys []string) ([]string, []string) {
|
|
validKeys := make([]string, 0)
|
|
validValues := make([]string, 0)
|
|
validKeySet := sets.String{}
|
|
for _, values := range valuesList {
|
|
for i, v := range values {
|
|
if v != "" {
|
|
validKeySet.Insert(keys[i])
|
|
}
|
|
}
|
|
}
|
|
if validKeySet.Len() == 0 { // if values missing, fall back to primary keys
|
|
return keys, values
|
|
}
|
|
for _, k := range keys {
|
|
if validKeySet.Has(k) {
|
|
validKeys = append(validKeys, k)
|
|
}
|
|
}
|
|
for i, v := range values {
|
|
if v != "" || validKeySet.Has(keys[i]) {
|
|
validValues = append(validValues, v)
|
|
}
|
|
}
|
|
return validKeys, validValues
|
|
}
|
|
|
|
// setAssociativeSequenceElements recursively set the elements in the list
|
|
func (l *Walker) setAssociativeSequenceElements(valuesList [][]string, keys []string, dest *yaml.RNode) (*yaml.RNode, error) {
|
|
// itemsToBeAdded contains the items that will be added to dest
|
|
itemsToBeAdded := yaml.NewListRNode()
|
|
var schema *openapi.ResourceSchema
|
|
if l.Schema != nil {
|
|
schema = l.Schema.Elements()
|
|
}
|
|
|
|
// each element in valuesList is a list of values corresponding to the keys
|
|
// for example, for the following yaml:
|
|
// - containerPort: 8080
|
|
// protocol: UDP
|
|
// - containerPort: 8080
|
|
// protocol: TCP
|
|
// `keys` would be [containerPort, protocol]
|
|
// and `valuesList` would be [ [8080, UDP], [8080, TCP] ]
|
|
for _, values := range valuesList {
|
|
if len(values) == 0 {
|
|
continue
|
|
}
|
|
|
|
validKeys, validValues := validateKeys(valuesList, values, keys)
|
|
val, err := Walker{
|
|
VisitKeysAsScalars: l.VisitKeysAsScalars,
|
|
InferAssociativeLists: l.InferAssociativeLists,
|
|
Visitor: l,
|
|
Schema: schema,
|
|
Sources: l.elementValueList(validKeys, validValues),
|
|
MergeOptions: l.MergeOptions,
|
|
}.Walk()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
exit := false
|
|
for i, key := range validKeys {
|
|
// delete the node from **dest** if it's null or empty
|
|
if yaml.IsMissingOrNull(val) || yaml.IsEmptyMap(val) {
|
|
_, err = dest.Pipe(yaml.ElementSetter{
|
|
Keys: validKeys,
|
|
Values: validValues,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
exit = true
|
|
} else if val.Field(key) == nil {
|
|
// make sure the key is set on the field
|
|
_, err = val.Pipe(yaml.SetField(key, yaml.NewScalarRNode(validValues[i])))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
if exit {
|
|
continue
|
|
}
|
|
|
|
// Add the val to the sequence. val will replace the item in the sequence if
|
|
// there is an item that matches all key-value pairs. Otherwise val will be appended
|
|
// the the sequence.
|
|
_, err = itemsToBeAdded.Pipe(yaml.ElementSetter{
|
|
Element: val.YNode(),
|
|
Keys: validKeys,
|
|
Values: values,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
var err error
|
|
if len(valuesList) > 0 {
|
|
validKeys, _ := validateKeys(valuesList, valuesList[0], keys)
|
|
if l.MergeOptions.ListIncreaseDirection == yaml.MergeOptionsListPrepend {
|
|
// items from patches are needed to be prepended. so we append the
|
|
// dest to itemsToBeAdded
|
|
dest, err = appendListNode(itemsToBeAdded, dest, validKeys)
|
|
} else {
|
|
// append the items
|
|
dest, err = appendListNode(dest, itemsToBeAdded, validKeys)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// sequence is empty
|
|
if yaml.IsMissingOrNull(dest) {
|
|
return nil, nil
|
|
}
|
|
return dest, nil
|
|
}
|
|
|
|
func (l *Walker) walkAssociativeSequence() (*yaml.RNode, error) {
|
|
// may require initializing the dest node
|
|
dest, err := l.Sources.setDestNode(l.VisitList(l.Sources, l.Schema, AssociativeList))
|
|
if dest == nil || err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// get the merge key(s) from schema
|
|
var strategy string
|
|
var keys []string
|
|
if l.Schema != nil {
|
|
strategy, keys = l.Schema.PatchStrategyAndKeyList()
|
|
}
|
|
if strategy == "" && len(keys) == 0 { // neither strategy nor keys present in the schema -- infer the key
|
|
// find the list of elements we need to recursively walk
|
|
key, err := l.elementKey()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if key != "" {
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
|
|
// non-primitive associative list -- merge the elements
|
|
values := l.elementValues(keys)
|
|
if len(values) != 0 || len(keys) > 0 {
|
|
return l.setAssociativeSequenceElements(values, keys, dest)
|
|
}
|
|
|
|
// primitive associative list -- merge the values
|
|
return l.setAssociativeSequenceElements(l.elementPrimitiveValues(), []string{""}, dest)
|
|
}
|
|
|
|
// elementKey returns the merge key to use for the associative list
|
|
func (l Walker) elementKey() (string, error) {
|
|
var key string
|
|
for i := range l.Sources {
|
|
if l.Sources[i] != nil && len(l.Sources[i].Content()) > 0 {
|
|
newKey := l.Sources[i].GetAssociativeKey()
|
|
if key != "" && key != newKey {
|
|
return "", errors.Errorf(
|
|
"conflicting merge keys [%s,%s] for field %s",
|
|
key, newKey, strings.Join(l.Path, "."))
|
|
}
|
|
key = newKey
|
|
}
|
|
}
|
|
if key == "" {
|
|
return "", errors.Errorf("no merge key found for field %s",
|
|
strings.Join(l.Path, "."))
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// elementValues returns a slice containing all values for the field across all elements
|
|
// from all sources.
|
|
// Return value slice is ordered using the original ordering from the elements, where
|
|
// elements missing from earlier sources appear later.
|
|
func (l Walker) elementValues(keys []string) [][]string {
|
|
// use slice to to keep elements in the original order
|
|
var returnValues [][]string
|
|
var seen sets.StringList
|
|
|
|
// if we are doing append, dest node should be the first.
|
|
// otherwise dest node should be the last.
|
|
beginIdx := 0
|
|
if l.MergeOptions.ListIncreaseDirection == yaml.MergeOptionsListPrepend {
|
|
beginIdx = 1
|
|
}
|
|
for i := range l.Sources {
|
|
src := l.Sources[(i+beginIdx)%len(l.Sources)]
|
|
if src == nil {
|
|
continue
|
|
}
|
|
|
|
// add the value of the field for each element
|
|
// don't check error, we know this is a list node
|
|
values, _ := src.ElementValuesList(keys)
|
|
for _, s := range values {
|
|
if len(s) == 0 || seen.Has(s) {
|
|
continue
|
|
}
|
|
returnValues = append(returnValues, s)
|
|
seen = seen.Insert(s)
|
|
}
|
|
}
|
|
return returnValues
|
|
}
|
|
|
|
// elementPrimitiveValues returns the primitive values in an associative list -- eg. finalizers
|
|
func (l Walker) elementPrimitiveValues() [][]string {
|
|
// use slice to to keep elements in the original order
|
|
var returnValues [][]string
|
|
seen := sets.String{}
|
|
// if we are doing append, dest node should be the first.
|
|
// otherwise dest node should be the last.
|
|
beginIdx := 0
|
|
if l.MergeOptions.ListIncreaseDirection == yaml.MergeOptionsListPrepend {
|
|
beginIdx = 1
|
|
}
|
|
for i := range l.Sources {
|
|
src := l.Sources[(i+beginIdx)%len(l.Sources)]
|
|
if src == nil {
|
|
continue
|
|
}
|
|
|
|
// add the value of the field for each element
|
|
// don't check error, we know this is a list node
|
|
for _, item := range src.YNode().Content {
|
|
if seen.Has(item.Value) {
|
|
continue
|
|
}
|
|
returnValues = append(returnValues, []string{item.Value})
|
|
seen.Insert(item.Value)
|
|
}
|
|
}
|
|
return returnValues
|
|
}
|
|
|
|
// fieldValue returns a slice containing each source's value for fieldName
|
|
func (l Walker) elementValueList(keys []string, values []string) []*yaml.RNode {
|
|
var fields []*yaml.RNode
|
|
for i := range l.Sources {
|
|
if l.Sources[i] == nil {
|
|
fields = append(fields, nil)
|
|
continue
|
|
}
|
|
fields = append(fields, l.Sources[i].ElementList(keys, values))
|
|
}
|
|
return fields
|
|
}
|