update modules kustomize add helm

Signed-off-by: LiHui <andrewli@yunify.com>
This commit is contained in:
LiHui
2021-04-14 14:06:59 +08:00
parent e587887aac
commit ce4cfbee51
98 changed files with 108932 additions and 14 deletions

44
vendor/sigs.k8s.io/kustomize/kyaml/yaml/alias.go generated vendored Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"io"
"gopkg.in/yaml.v3"
)
// Expose the yaml.v3 functions so this package can be used as a replacement
type Decoder = yaml.Decoder
type Encoder = yaml.Encoder
type IsZeroer = yaml.IsZeroer
type Kind = yaml.Kind
type Marshaler = yaml.Marshaler
type Node = yaml.Node
type Style = yaml.Style
type TypeError = yaml.TypeError
type Unmarshaler = yaml.Unmarshaler
var Marshal = yaml.Marshal
var Unmarshal = yaml.Unmarshal
var NewDecoder = yaml.NewDecoder
var NewEncoder = func(w io.Writer) *yaml.Encoder {
e := yaml.NewEncoder(w)
e.SetIndent(2)
return e
}
var AliasNode yaml.Kind = yaml.AliasNode
var DocumentNode yaml.Kind = yaml.DocumentNode
var MappingNode yaml.Kind = yaml.MappingNode
var ScalarNode yaml.Kind = yaml.ScalarNode
var SequenceNode yaml.Kind = yaml.SequenceNode
var DoubleQuotedStyle yaml.Style = yaml.DoubleQuotedStyle
var FlowStyle yaml.Style = yaml.FlowStyle
var FoldedStyle yaml.Style = yaml.FoldedStyle
var LiteralStyle yaml.Style = yaml.LiteralStyle
var SingleQuotedStyle yaml.Style = yaml.SingleQuotedStyle
var TaggedStyle yaml.Style = yaml.TaggedStyle

View File

@@ -0,0 +1,92 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"reflect"
"strings"
"github.com/go-openapi/spec"
y1_1 "gopkg.in/yaml.v2"
y1_2 "gopkg.in/yaml.v3"
)
// typeToTag maps OpenAPI schema types to yaml 1.2 tags
var typeToTag = map[string]string{
"string": NodeTagString,
"integer": NodeTagInt,
"boolean": NodeTagBool,
"number": NodeTagFloat,
}
// FormatNonStringStyle makes sure that values which parse as non-string values in yaml 1.1
// are correctly formatted given the Schema type.
func FormatNonStringStyle(node *Node, schema spec.Schema) {
if len(schema.Type) != 1 {
return
}
t := schema.Type[0]
if !IsYaml1_1NonString(node) {
return
}
switch {
case t == "string" && schema.Format != "int-or-string":
if (node.Style&DoubleQuotedStyle == 0) && (node.Style&SingleQuotedStyle == 0) {
// must quote values so they are parsed as strings
node.Style = DoubleQuotedStyle
}
case t == "boolean" || t == "integer" || t == "number":
if (node.Style&DoubleQuotedStyle != 0) || (node.Style&SingleQuotedStyle != 0) {
// must NOT quote the values so they aren't parsed as strings
node.Style = 0
}
default:
return
}
if tag, found := typeToTag[t]; found {
// make sure the right tag is set
node.Tag = tag
}
}
// IsYaml1_1NonString returns true if the value parses as a non-string value in yaml 1.1
// when unquoted.
//
// Note: yaml 1.2 uses different keywords than yaml 1.1. Example: yaml 1.2 interprets
// `field: on` and `field: "on"` as equivalent (both strings). However Yaml 1.1 interprets
// `field: on` as on being a bool and `field: "on"` as on being a string.
// If an input is read with `field: "on"`, and the style is changed from DoubleQuote to 0,
// it will change the type of the field from a string to a bool. For this reason, fields
// which are keywords in yaml 1.1 should never have their style changed, as it would break
// backwards compatibility with yaml 1.1 -- which is what is used by the Kubernetes apiserver.
func IsYaml1_1NonString(node *Node) bool {
if node.Kind != y1_2.ScalarNode {
// not a keyword
return false
}
return IsValueNonString(node.Value)
}
func IsValueNonString(value string) bool {
if value == "" {
return false
}
if strings.Contains(value, "\n") {
// multi-line strings will fail to unmarshal
return false
}
// check if the value will unmarshal into a non-string value using a yaml 1.1 parser
var i1 interface{}
if err := y1_1.Unmarshal([]byte(value), &i1); err != nil {
return false
}
if reflect.TypeOf(i1) != stringType {
return true
}
return false
}
var stringType = reflect.TypeOf("string")

30
vendor/sigs.k8s.io/kustomize/kyaml/yaml/const.go generated vendored Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
const (
// NodeTagNull is the tag set for a yaml.Document that contains no data;
// e.g. it isn't a Map, Slice, Document, etc
NodeTagNull = "!!null"
NodeTagFloat = "!!float"
NodeTagString = "!!str"
NodeTagBool = "!!bool"
NodeTagInt = "!!int"
NodeTagMap = "!!map"
NodeTagSeq = "!!seq"
NodeTagEmpty = ""
)
// Field names
const (
AnnotationsField = "annotations"
APIVersionField = "apiVersion"
KindField = "kind"
MetadataField = "metadata"
DataField = "data"
BinaryDataField = "binaryData"
NameField = "name"
NamespaceField = "namespace"
LabelsField = "labels"
)

121
vendor/sigs.k8s.io/kustomize/kyaml/yaml/datamap.go generated vendored Normal file
View File

@@ -0,0 +1,121 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"encoding/base64"
"sort"
"strings"
"unicode/utf8"
)
// SortedMapKeys returns a sorted slice of keys to the given map.
// Writing this function never gets old.
func SortedMapKeys(m map[string]string) []string {
keys := make([]string, len(m))
i := 0
for k := range m {
keys[i] = k
i++
}
sort.Strings(keys)
return keys
}
func (rn *RNode) LoadMapIntoConfigMapData(m map[string]string) error {
for _, k := range SortedMapKeys(m) {
fldName, vrN := makeConfigMapValueRNode(m[k])
if _, err := rn.Pipe(
LookupCreate(MappingNode, fldName),
SetField(k, vrN)); err != nil {
return err
}
}
return nil
}
func (rn *RNode) LoadMapIntoConfigMapBinaryData(m map[string]string) error {
for _, k := range SortedMapKeys(m) {
_, vrN := makeConfigMapValueRNode(m[k])
// we know this is binary data
fldName := BinaryDataField
if _, err := rn.Pipe(
LookupCreate(MappingNode, fldName),
SetField(k, vrN)); err != nil {
return err
}
}
return nil
}
func makeConfigMapValueRNode(s string) (field string, rN *RNode) {
yN := &Node{Kind: ScalarNode}
yN.Tag = NodeTagString
if utf8.ValidString(s) {
field = DataField
yN.Value = s
} else {
field = BinaryDataField
yN.Value = encodeBase64(s)
}
if strings.Contains(yN.Value, "\n") {
yN.Style = LiteralStyle
}
return field, NewRNode(yN)
}
func (rn *RNode) LoadMapIntoSecretData(m map[string]string) error {
mapNode, err := rn.Pipe(LookupCreate(MappingNode, DataField))
if err != nil {
return err
}
for _, k := range SortedMapKeys(m) {
vrN := makeSecretValueRNode(m[k])
if _, err := mapNode.Pipe(SetField(k, vrN)); err != nil {
return err
}
}
return nil
}
// In a secret, all data is base64 encoded, regardless of its conformance
// or lack thereof to UTF-8.
func makeSecretValueRNode(s string) *RNode {
yN := &Node{Kind: ScalarNode}
// Purposely don't use YAML tags to identify the data as being plain text or
// binary. It kubernetes Secrets the values in the `data` map are expected
// to be base64 encoded, and in ConfigMaps that same can be said for the
// values in the `binaryData` field.
yN.Tag = NodeTagString
yN.Value = encodeBase64(s)
if strings.Contains(yN.Value, "\n") {
yN.Style = LiteralStyle
}
return NewRNode(yN)
}
// encodeBase64 encodes s as base64 that is broken up into multiple lines
// as appropriate for the resulting length.
func encodeBase64(s string) string {
const lineLen = 70
encLen := base64.StdEncoding.EncodedLen(len(s))
lines := encLen/lineLen + 1
buf := make([]byte, encLen*2+lines)
in := buf[0:encLen]
out := buf[encLen:]
base64.StdEncoding.Encode(in, []byte(s))
k := 0
for i := 0; i < len(in); i += lineLen {
j := i + lineLen
if j > len(in) {
j = len(in)
}
k += copy(out[k:], in[i:j])
if lines > 1 {
out[k] = '\n'
k++
}
}
return string(out[:k])
}

49
vendor/sigs.k8s.io/kustomize/kyaml/yaml/doc.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package yaml contains libraries for manipulating individual Kubernetes Resource
// Configuration as yaml, keeping yaml structure and comments.
//
// Parsing Resources
//
// Typically Resources will be initialized as collections through the kio package libraries.
// However it is possible to directly initialize Resources using Parse.
// resource, err := yaml.Parse("apiVersion: apps/v1\nkind: Deployment")
//
// Processing Resources
//
// Individual Resources are manipulated using the Pipe and PipeE to apply Filter functions
// to transform the Resource data.
// err := resource.PipeE(yaml.SetAnnotation("key", "value"))
//
// If multiple Filter functions are provided to Pipe or PipeE, each function is applied to
// the result of the last function -- e.g. yaml.Lookup(...), yaml.SetField(...)
//
// Field values may also be retrieved using Pipe.
// annotationValue, err := resource.Pipe(yaml.GetAnnotation("key"))
//
// See http://www.linfo.org/filters.html for a definition of filters.
//
// Common Filters
//
// There are a number of standard filter functions provided by the yaml package.
//
// Working with annotations:
// [AnnotationSetter{}, AnnotationGetter{}, AnnotationClearer{}]
//
// Working with fields by path:
// [PathMatcher{}, PathGetter{}]
//
// Working with individual fields on Maps and Objects:
// [FieldMatcher{}, FieldSetter{}, FieldGetter{}]
//
// Working with individual elements in Sequences:
// [ElementAppender{}, ElementSetter{}, ElementMatcher{}]
//
// Writing Filters
//
// Users may implement their own filter functions. When doing so, can be necessary to work with
// the RNode directly rather than through Pipe. RNode provides a number of functions for doing
// so. See:
// [GetMeta(), Fields(), Elements(), String()]
package yaml

146
vendor/sigs.k8s.io/kustomize/kyaml/yaml/filters.go generated vendored Normal file
View File

@@ -0,0 +1,146 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"fmt"
"regexp"
"sort"
"strings"
)
// Filters is the list of serializable Pipeline Filters
var Filters = map[string]func() Filter{
"AnnotationClearer": func() Filter { return &AnnotationClearer{} },
"AnnotationGetter": func() Filter { return &AnnotationGetter{} },
"AnnotationSetter": func() Filter { return &AnnotationSetter{} },
"LabelSetter": func() Filter { return &LabelSetter{} },
"ElementAppender": func() Filter { return &ElementAppender{} },
"ElementMatcher": func() Filter { return &ElementMatcher{} },
"FieldClearer": func() Filter { return &FieldClearer{} },
"FilterMatcher": func() Filter { return &FilterMatcher{} },
"FieldMatcher": func() Filter { return &FieldMatcher{} },
"FieldSetter": func() Filter { return &FieldSetter{} },
"PathGetter": func() Filter { return &PathGetter{} },
"PathMatcher": func() Filter { return &PathMatcher{} },
"Parser": func() Filter { return &Parser{} },
"PrefixSetter": func() Filter { return &PrefixSetter{} },
"ValueReplacer": func() Filter { return &ValueReplacer{} },
"SuffixSetter": func() Filter { return &SuffixSetter{} },
"TeePiper": func() Filter { return &TeePiper{} },
}
// YFilter wraps the Filter interface so the filter can be represented as
// data and can be unmarshalled into a struct from a yaml config file.
// This allows Pipelines to be expressed as data rather than code.
type YFilter struct {
Filter
}
func (y YFilter) MarshalYAML() (interface{}, error) {
return y.Filter, nil
}
func (y *YFilter) UnmarshalYAML(unmarshal func(interface{}) error) error {
meta := &ResourceMeta{}
if err := unmarshal(meta); err != nil {
return err
}
filter, found := Filters[meta.Kind]
if !found {
var knownFilters []string
for k := range Filters {
knownFilters = append(knownFilters, k)
}
sort.Strings(knownFilters)
return fmt.Errorf("unsupported Filter Kind %s: may be one of: [%s]",
meta.Kind, strings.Join(knownFilters, ","))
}
y.Filter = filter()
if err := unmarshal(y.Filter); err != nil {
return err
}
return nil
}
type YFilters []YFilter
func (y YFilters) Filters() []Filter {
var f []Filter
for i := range y {
f = append(f, y[i].Filter)
}
return f
}
type FilterMatcher struct {
Kind string `yaml:"kind"`
// Filters are the set of Filters run by TeePiper.
Filters YFilters `yaml:"pipeline,omitempty"`
}
func (t FilterMatcher) Filter(rn *RNode) (*RNode, error) {
v, err := rn.Pipe(t.Filters.Filters()...)
if v == nil || err != nil {
return nil, err
}
// return the original input if the pipeline resolves to true
return rn, err
}
type ValueReplacer struct {
Kind string `yaml:"kind"`
StringMatch string `yaml:"stringMatch"`
RegexMatch string `yaml:"regexMatch"`
Replace string `yaml:"replace"`
Count int `yaml:"count"`
}
func (s ValueReplacer) Filter(object *RNode) (*RNode, error) {
if s.Count == 0 {
s.Count = -1
}
switch {
case s.StringMatch != "":
object.value.Value = strings.Replace(object.value.Value, s.StringMatch, s.Replace, s.Count)
case s.RegexMatch != "":
r, err := regexp.Compile(s.RegexMatch)
if err != nil {
return nil, fmt.Errorf("ValueReplacer RegexMatch does not compile: %v", err)
}
object.value.Value = r.ReplaceAllString(object.value.Value, s.Replace)
default:
return nil, fmt.Errorf("ValueReplacer missing StringMatch and RegexMatch")
}
return object, nil
}
type PrefixSetter struct {
Kind string `yaml:"kind"`
Value string `yaml:"value"`
}
func (s PrefixSetter) Filter(object *RNode) (*RNode, error) {
if !strings.HasPrefix(object.value.Value, s.Value) {
object.value.Value = s.Value + object.value.Value
}
return object, nil
}
type SuffixSetter struct {
Kind string `yaml:"kind"`
Value string `yaml:"value"`
}
func (s SuffixSetter) Filter(object *RNode) (*RNode, error) {
if !strings.HasSuffix(object.value.Value, s.Value) {
object.value.Value += s.Value
}
return object, nil
}

769
vendor/sigs.k8s.io/kustomize/kyaml/yaml/fns.go generated vendored Normal file
View File

@@ -0,0 +1,769 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/davecgh/go-spew/spew"
"gopkg.in/yaml.v3"
"sigs.k8s.io/kustomize/kyaml/errors"
)
// Append creates an ElementAppender
func Append(elements ...*yaml.Node) ElementAppender {
return ElementAppender{Elements: elements}
}
// ElementAppender adds all element to a SequenceNode's Content.
// Returns Elements[0] if len(Elements) == 1, otherwise returns nil.
type ElementAppender struct {
Kind string `yaml:"kind,omitempty"`
// Elem is the value to append.
Elements []*yaml.Node `yaml:"elements,omitempty"`
}
func (a ElementAppender) Filter(rn *RNode) (*RNode, error) {
if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
return nil, err
}
for i := range a.Elements {
rn.YNode().Content = append(rn.Content(), a.Elements[i])
}
if len(a.Elements) == 1 {
return NewRNode(a.Elements[0]), nil
}
return nil, nil
}
// ElementSetter sets the value for an Element in an associative list.
// ElementSetter will append, replace or delete an element in an associative list.
// To append, user a key-value pair that doesn't exist in the sequence. this
// behavior is intended to handle the case that not matching element found. It's
// not designed for this purpose. To append an element, please use ElementAppender.
// To replace, set the key-value pair and a non-nil Element.
// To delete, set the key-value pair and leave the Element as nil.
// Every key must have a corresponding value.
type ElementSetter struct {
Kind string `yaml:"kind,omitempty"`
// Element is the new value to set -- remove the existing element if nil
Element *Node
// Key is a list of fields on the elements. It is used to find matching elements to
// update / delete
Keys []string
// Value is a list of field values on the elements corresponding to the keys. It is
// used to find matching elements to update / delete.
Values []string
}
// isMappingNode returns whether node is a mapping node
func (e ElementSetter) isMappingNode(node *RNode) bool {
return ErrorIfInvalid(node, yaml.MappingNode) == nil
}
// isMappingSetter returns is this setter intended to set a mapping node
func (e ElementSetter) isMappingSetter() bool {
return len(e.Keys) > 0 && e.Keys[0] != "" &&
len(e.Values) > 0 && e.Values[0] != ""
}
func (e ElementSetter) Filter(rn *RNode) (*RNode, error) {
if len(e.Keys) == 0 {
e.Keys = append(e.Keys, "")
}
if err := ErrorIfInvalid(rn, SequenceNode); err != nil {
return nil, err
}
// build the new Content slice
var newContent []*yaml.Node
matchingElementFound := false
for i := range rn.YNode().Content {
elem := rn.Content()[i]
newNode := NewRNode(elem)
// empty elements are not valid -- they at least need an associative key
if IsMissingOrNull(newNode) || IsEmptyMap(newNode) {
continue
}
// keep non-mapping node in the Content when we want to match a mapping.
if !e.isMappingNode(newNode) && e.isMappingSetter() {
newContent = append(newContent, elem)
continue
}
// check if this is the element we are matching
var val *RNode
var err error
found := true
for j := range e.Keys {
if j < len(e.Values) {
val, err = newNode.Pipe(FieldMatcher{Name: e.Keys[j], StringValue: e.Values[j]})
}
if err != nil {
return nil, err
}
if val == nil {
found = false
break
}
}
if !found {
// not the element we are looking for, keep it in the Content
if len(e.Values) > 0 {
newContent = append(newContent, elem)
}
continue
}
matchingElementFound = true
// deletion operation -- remove the element from the new Content
if e.Element == nil {
continue
}
// replace operation -- replace the element in the Content
newContent = append(newContent, e.Element)
}
rn.YNode().Content = newContent
// deletion operation -- return nil
if IsMissingOrNull(NewRNode(e.Element)) {
return nil, nil
}
// append operation -- add the element to the Content
if !matchingElementFound {
rn.YNode().Content = append(rn.YNode().Content, e.Element)
}
return NewRNode(e.Element), nil
}
// GetElementByIndex will return a Filter which can be applied to a sequence
// node to get the element specified by the index
func GetElementByIndex(index int) ElementIndexer {
return ElementIndexer{Index: index}
}
// ElementIndexer picks the element with a specified index. Index starts from
// 0 to len(list) - 1. a hyphen ("-") means the last index.
type ElementIndexer struct {
Index int
}
// Filter implements Filter
func (i ElementIndexer) Filter(rn *RNode) (*RNode, error) {
// rn.Elements will return error if rn is not a sequence node.
elems, err := rn.Elements()
if err != nil {
return nil, err
}
if i.Index < 0 {
return elems[len(elems)-1], nil
}
if i.Index >= len(elems) {
return nil, nil
}
return elems[i.Index], nil
}
// Clear returns a FieldClearer
func Clear(name string) FieldClearer {
return FieldClearer{Name: name}
}
// FieldClearer removes the field or map key.
// Returns a RNode with the removed field or map entry.
type FieldClearer struct {
Kind string `yaml:"kind,omitempty"`
// Name is the name of the field or key in the map.
Name string `yaml:"name,omitempty"`
IfEmpty bool `yaml:"ifEmpty,omitempty"`
}
func (c FieldClearer) Filter(rn *RNode) (*RNode, error) {
if err := ErrorIfInvalid(rn, yaml.MappingNode); err != nil {
return nil, err
}
for i := 0; i < len(rn.Content()); i += 2 {
// if name matches, remove these 2 elements from the list because
// they are treated as a fieldName/fieldValue pair.
if rn.Content()[i].Value == c.Name {
if c.IfEmpty {
if len(rn.Content()[i+1].Content) > 0 {
continue
}
}
// save the item we are about to remove
removed := NewRNode(rn.Content()[i+1])
if len(rn.YNode().Content) > i+2 {
l := len(rn.YNode().Content)
// remove from the middle of the list
rn.YNode().Content = rn.Content()[:i]
rn.YNode().Content = append(
rn.YNode().Content,
rn.Content()[i+2:l]...)
} else {
// remove from the end of the list
rn.YNode().Content = rn.Content()[:i]
}
// return the removed field name and value
return removed, nil
}
}
// nothing removed
return nil, nil
}
func MatchElement(field, value string) ElementMatcher {
return ElementMatcher{Keys: []string{field}, Values: []string{value}}
}
func MatchElementList(keys []string, values []string) ElementMatcher {
return ElementMatcher{Keys: keys, Values: values}
}
func GetElementByKey(key string) ElementMatcher {
return ElementMatcher{Keys: []string{key}, MatchAnyValue: true}
}
// ElementMatcher returns the first element from a Sequence matching the
// specified key-value pairs. If there's no match, and no configuration error,
// the matcher returns nil, nil.
type ElementMatcher struct {
Kind string `yaml:"kind,omitempty"`
// Keys are the list of fields upon which to match this element.
Keys []string
// Values are the list of values upon which to match this element.
Values []string
// Create will create the Element if it is not found
Create *RNode `yaml:"create,omitempty"`
// MatchAnyValue indicates that matcher should only consider the key and ignore
// the actual value in the list. Values must be empty when MatchAnyValue is
// set to true.
MatchAnyValue bool `yaml:"noValue,omitempty"`
}
func (e ElementMatcher) Filter(rn *RNode) (*RNode, error) {
if len(e.Keys) == 0 {
e.Keys = append(e.Keys, "")
}
if len(e.Values) == 0 {
e.Values = append(e.Values, "")
}
if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
return nil, err
}
if e.MatchAnyValue && len(e.Values) != 0 && e.Values[0] != "" {
return nil, fmt.Errorf("Values must be empty when MatchAnyValue is set to true")
}
// SequenceNode Content is a slice of ScalarNodes. Each ScalarNode has a
// YNode containing the primitive data.
if len(e.Keys) == 0 || len(e.Keys[0]) == 0 {
for i := range rn.Content() {
if rn.Content()[i].Value == e.Values[0] {
return &RNode{value: rn.Content()[i]}, nil
}
}
if e.Create != nil {
return rn.Pipe(Append(e.Create.YNode()))
}
return nil, nil
}
// SequenceNode Content is a slice of MappingNodes. Each MappingNode has Content
// with a slice of key-value pairs containing the fields.
for i := range rn.Content() {
// cast the entry to a RNode so we can operate on it
elem := NewRNode(rn.Content()[i])
var field *RNode
var err error
// only check mapping node
if err = ErrorIfInvalid(elem, yaml.MappingNode); err != nil {
continue
}
if !e.MatchAnyValue && len(e.Keys) != len(e.Values) {
return nil, fmt.Errorf("length of keys must equal length of values when MatchAnyValue is false")
}
matchesElement := true
for i := range e.Keys {
if e.MatchAnyValue {
field, err = elem.Pipe(Get(e.Keys[i]))
} else {
field, err = elem.Pipe(MatchField(e.Keys[i], e.Values[i]))
}
if !IsFoundOrError(field, err) {
// this is not the element we are looking for
matchesElement = false
break
}
}
if matchesElement {
return elem, err
}
}
// create the element
if e.Create != nil {
return rn.Pipe(Append(e.Create.YNode()))
}
return nil, nil
}
func Get(name string) FieldMatcher {
return FieldMatcher{Name: name}
}
func MatchField(name, value string) FieldMatcher {
return FieldMatcher{Name: name, Value: NewScalarRNode(value)}
}
func Match(value string) FieldMatcher {
return FieldMatcher{Value: NewScalarRNode(value)}
}
// FieldMatcher returns the value of a named field or map entry.
type FieldMatcher struct {
Kind string `yaml:"kind,omitempty"`
// Name of the field to return
Name string `yaml:"name,omitempty"`
// YNode of the field to return.
// Optional. Will only need to match field name if unset.
Value *RNode `yaml:"value,omitempty"`
StringValue string `yaml:"stringValue,omitempty"`
StringRegexValue string `yaml:"stringRegexValue,omitempty"`
// Create will cause the field to be created with this value
// if it is set.
Create *RNode `yaml:"create,omitempty"`
}
func (f FieldMatcher) Filter(rn *RNode) (*RNode, error) {
if f.StringValue != "" && f.Value == nil {
f.Value = NewScalarRNode(f.StringValue)
}
// never match nil or null fields
if IsMissingOrNull(rn) {
return nil, nil
}
if f.Name == "" {
if err := ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
return nil, err
}
switch {
case f.StringRegexValue != "":
// TODO(pwittrock): pre-compile this when unmarshalling and cache to a field
rg, err := regexp.Compile(f.StringRegexValue)
if err != nil {
return nil, err
}
if match := rg.MatchString(rn.value.Value); match {
return rn, nil
}
return nil, nil
case GetValue(rn) == GetValue(f.Value):
return rn, nil
default:
return nil, nil
}
}
if err := ErrorIfInvalid(rn, yaml.MappingNode); err != nil {
return nil, err
}
for i := 0; i < len(rn.Content()); i = IncrementFieldIndex(i) {
isMatchingField := rn.Content()[i].Value == f.Name
if isMatchingField {
requireMatchFieldValue := f.Value != nil
if !requireMatchFieldValue || rn.Content()[i+1].Value == f.Value.YNode().Value {
return NewRNode(rn.Content()[i+1]), nil
}
}
}
if f.Create != nil {
return rn.Pipe(SetField(f.Name, f.Create))
}
return nil, nil
}
// Lookup returns a PathGetter to lookup a field by its path.
func Lookup(path ...string) PathGetter {
return PathGetter{Path: path}
}
// Lookup returns a PathGetter to lookup a field by its path and create it if it doesn't already
// exist.
func LookupCreate(kind yaml.Kind, path ...string) PathGetter {
return PathGetter{Path: path, Create: kind}
}
// PathGetter returns the RNode under Path.
type PathGetter struct {
Kind string `yaml:"kind,omitempty"`
// Path is a slice of parts leading to the RNode to lookup.
// Each path part may be one of:
// * FieldMatcher -- e.g. "spec"
// * Map Key -- e.g. "app.k8s.io/version"
// * List Entry -- e.g. "[name=nginx]" or "[=-jar]" or "0" or "-"
//
// Map Keys and Fields are equivalent.
// See FieldMatcher for more on Fields and Map Keys.
//
// List Entries can be specified as map entry to match [fieldName=fieldValue]
// or a positional index like 0 to get the element. - (unquoted hyphen) is
// special and means the last element.
//
// See Elem for more on List Entries.
//
// Examples:
// * spec.template.spec.container with matching name: [name=nginx]
// * spec.template.spec.container.argument matching a value: [=-jar]
Path []string `yaml:"path,omitempty"`
// Create will cause missing path parts to be created as they are walked.
//
// * The leaf Node (final path) will be created with a Kind matching Create
// * Intermediary Nodes will be created as either a MappingNodes or
// SequenceNodes as appropriate for each's Path location.
// * If a list item is specified by a index (an offset or "-"), this item will
// not be created even Create is set.
Create yaml.Kind `yaml:"create,omitempty"`
// Style is the style to apply to created value Nodes.
// Created key Nodes keep an unspecified Style.
Style yaml.Style `yaml:"style,omitempty"`
}
func (l PathGetter) Filter(rn *RNode) (*RNode, error) {
var err error
fieldPath := append([]string{}, rn.FieldPath()...)
match := rn
// iterate over path until encountering an error or missing value
l.Path = cleanPath(l.Path)
for i := range l.Path {
var part, nextPart string
part = l.Path[i]
if len(l.Path) > i+1 {
nextPart = l.Path[i+1]
}
var fltr Filter
fltr, err = l.getFilter(part, nextPart, &fieldPath)
if err != nil {
return nil, err
}
match, err = match.Pipe(fltr)
if IsMissingOrError(match, err) {
return nil, err
}
match.AppendToFieldPath(fieldPath...)
}
return match, nil
}
func (l PathGetter) getFilter(part, nextPart string, fieldPath *[]string) (Filter, error) {
idx, err := strconv.Atoi(part)
switch {
case err == nil:
// part is a number
if idx < 0 {
return nil, fmt.Errorf("array index %d cannot be negative", idx)
}
return GetElementByIndex(idx), nil
case part == "-":
// part is a hyphen
return GetElementByIndex(-1), nil
case IsListIndex(part):
// part is surrounded by brackets
return l.elemFilter(part)
default:
// mapping node
*fieldPath = append(*fieldPath, part)
return l.fieldFilter(part, l.getKind(nextPart))
}
}
func (l PathGetter) elemFilter(part string) (Filter, error) {
var match *RNode
name, value, err := SplitIndexNameValue(part)
if err != nil {
return nil, errors.Wrap(err)
}
if !IsCreate(l.Create) {
return MatchElement(name, value), nil
}
var elem *RNode
primitiveElement := len(name) == 0
if primitiveElement {
// append a ScalarNode
elem = NewScalarRNode(value)
elem.YNode().Style = l.Style
match = elem
} else {
// append a MappingNode
match = NewRNode(&yaml.Node{Kind: yaml.ScalarNode, Value: value, Style: l.Style})
elem = NewRNode(&yaml.Node{
Kind: yaml.MappingNode,
Content: []*yaml.Node{{Kind: yaml.ScalarNode, Value: name}, match.YNode()},
Style: l.Style,
})
}
// Append the Node
return ElementMatcher{Keys: []string{name}, Values: []string{value}, Create: elem}, nil
}
func (l PathGetter) fieldFilter(
name string, kind yaml.Kind) (Filter, error) {
if !IsCreate(l.Create) {
return Get(name), nil
}
return FieldMatcher{Name: name, Create: &RNode{value: &yaml.Node{Kind: kind, Style: l.Style}}}, nil
}
func (l PathGetter) getKind(nextPart string) yaml.Kind {
if IsListIndex(nextPart) {
// if nextPart is of the form [a=b], then it is an index into a Sequence
// so the current part must be a SequenceNode
return yaml.SequenceNode
}
if nextPart == "" {
// final name in the path, use the l.Create defined Kind
return l.Create
}
// non-sequence intermediate Node
return yaml.MappingNode
}
func SetField(name string, value *RNode) FieldSetter {
return FieldSetter{Name: name, Value: value}
}
func Set(value *RNode) FieldSetter {
return FieldSetter{Value: value}
}
// FieldSetter sets a field or map entry to a value.
type FieldSetter struct {
Kind string `yaml:"kind,omitempty"`
// Name is the name of the field or key to lookup in a MappingNode.
// If Name is unspecified, and the input is a ScalarNode, FieldSetter will set the
// value on the ScalarNode.
Name string `yaml:"name,omitempty"`
// Comments for the field
Comments Comments `yaml:"comments,omitempty"`
// Value is the value to set.
// Optional if Kind is set.
Value *RNode `yaml:"value,omitempty"`
StringValue string `yaml:"stringValue,omitempty"`
// OverrideStyle can be set to override the style of the existing node
// when setting it. Otherwise, if an existing node is found, the style is
// retained.
OverrideStyle bool `yaml:"overrideStyle,omitempty"`
}
func (s FieldSetter) Filter(rn *RNode) (*RNode, error) {
if s.StringValue != "" && s.Value == nil {
s.Value = NewScalarRNode(s.StringValue)
}
if s.Name == "" {
if err := ErrorIfInvalid(rn, yaml.ScalarNode); err != nil {
return rn, err
}
if IsMissingOrNull(s.Value) {
return rn, nil
}
// only apply the style if there is not an existing style
// or we want to override it
if !s.OverrideStyle || s.Value.YNode().Style == 0 {
// keep the original style if it exists
s.Value.YNode().Style = rn.YNode().Style
}
rn.SetYNode(s.Value.YNode())
return rn, nil
}
// Clear the field if it is empty, or explicitly null
if s.Value == nil || s.Value.IsTaggedNull() {
return rn.Pipe(Clear(s.Name))
}
field, err := rn.Pipe(FieldMatcher{Name: s.Name})
if err != nil {
return nil, err
}
if field != nil {
// only apply the style if there is not an existing style
// or we want to override it
if !s.OverrideStyle || field.YNode().Style == 0 {
// keep the original style if it exists
s.Value.YNode().Style = field.YNode().Style
}
// need to def ref the Node since field is ephemeral
field.SetYNode(s.Value.YNode())
return field, nil
}
// create the field
rn.YNode().Content = append(
rn.YNode().Content,
&yaml.Node{
Kind: yaml.ScalarNode,
Value: s.Name,
HeadComment: s.Comments.HeadComment,
LineComment: s.Comments.LineComment,
FootComment: s.Comments.FootComment,
},
s.Value.YNode())
return s.Value, nil
}
// Tee calls the provided Filters, and returns its argument rather than the result
// of the filters.
// May be used to fork sub-filters from a call.
// e.g. locate field, set value; locate another field, set another value
func Tee(filters ...Filter) Filter {
return TeePiper{Filters: filters}
}
// TeePiper Calls a slice of Filters and returns its input.
// May be used to fork sub-filters from a call.
// e.g. locate field, set value; locate another field, set another value
type TeePiper struct {
Kind string `yaml:"kind,omitempty"`
// Filters are the set of Filters run by TeePiper.
Filters []Filter `yaml:"filters,omitempty"`
}
func (t TeePiper) Filter(rn *RNode) (*RNode, error) {
_, err := rn.Pipe(t.Filters...)
return rn, err
}
// IsCreate returns true if kind is specified
func IsCreate(kind yaml.Kind) bool {
return kind != 0
}
// IsMissingOrError returns true if rn is NOT found or err is non-nil
func IsMissingOrError(rn *RNode, err error) bool {
return rn == nil || err != nil
}
// IsFoundOrError returns true if rn is found or err is non-nil
func IsFoundOrError(rn *RNode, err error) bool {
return rn != nil || err != nil
}
func ErrorIfAnyInvalidAndNonNull(kind yaml.Kind, rn ...*RNode) error {
for i := range rn {
if IsMissingOrNull(rn[i]) {
continue
}
if err := ErrorIfInvalid(rn[i], kind); err != nil {
return err
}
}
return nil
}
var nodeTypeIndex = map[yaml.Kind]string{
yaml.SequenceNode: "SequenceNode",
yaml.MappingNode: "MappingNode",
yaml.ScalarNode: "ScalarNode",
yaml.DocumentNode: "DocumentNode",
yaml.AliasNode: "AliasNode",
}
func ErrorIfInvalid(rn *RNode, kind yaml.Kind) error {
if IsMissingOrNull(rn) {
// node has no type, pass validation
return nil
}
if rn.YNode().Kind != kind {
s, _ := rn.String()
return errors.Errorf(
"wrong Node Kind for %s expected: %v was %v: value: {%s}",
strings.Join(rn.FieldPath(), "."),
nodeTypeIndex[kind], nodeTypeIndex[rn.YNode().Kind], strings.TrimSpace(s))
}
if kind == yaml.MappingNode {
if len(rn.YNode().Content)%2 != 0 {
return errors.Errorf(
"yaml MappingNodes must have even length contents: %v", spew.Sdump(rn))
}
}
return nil
}
// IsListIndex returns true if p is an index into a Val.
// e.g. [fieldName=fieldValue]
// e.g. [=primitiveValue]
func IsListIndex(p string) bool {
return strings.HasPrefix(p, "[") && strings.HasSuffix(p, "]")
}
// SplitIndexNameValue splits a lookup part Val index into the field name
// and field value to match.
// e.g. splits [name=nginx] into (name, nginx)
// e.g. splits [=-jar] into ("", -jar)
func SplitIndexNameValue(p string) (string, string, error) {
elem := strings.TrimSuffix(p, "]")
elem = strings.TrimPrefix(elem, "[")
parts := strings.SplitN(elem, "=", 2)
if len(parts) == 1 {
return "", "", fmt.Errorf("list path element must contain fieldName=fieldValue for element to match")
}
return parts[0], parts[1], nil
}
// IncrementFieldIndex increments i to point to the next field name element in
// a slice of Contents.
func IncrementFieldIndex(i int) int {
return i + 2
}

View File

@@ -0,0 +1,43 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/labels/zz_generated.deepcopy.go
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package labels
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Requirement) DeepCopyInto(out *Requirement) {
*out = *in
if in.strValues != nil {
in, out := &in.strValues, &out.strValues
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Requirement.
func (in *Requirement) DeepCopy() *Requirement {
if in == nil {
return nil
}
out := new(Requirement)
in.DeepCopyInto(out)
return out
}

View File

@@ -0,0 +1,192 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/labels/labels.go
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package labels
import (
"fmt"
"sort"
"strings"
)
// Labels allows you to present labels independently from their storage.
type Labels interface {
// Has returns whether the provided label exists.
Has(label string) (exists bool)
// Get returns the value for the provided label.
Get(label string) (value string)
}
// Set is a map of label:value. It implements Labels.
type Set map[string]string
// String returns all labels listed as a human readable string.
// Conveniently, exactly the format that ParseSelector takes.
func (ls Set) String() string {
selector := make([]string, 0, len(ls))
for key, value := range ls {
selector = append(selector, key+"="+value)
}
// Sort for determinism.
sort.StringSlice(selector).Sort()
return strings.Join(selector, ",")
}
// Has returns whether the provided label exists in the map.
func (ls Set) Has(label string) bool {
_, exists := ls[label]
return exists
}
// Get returns the value in the map for the provided label.
func (ls Set) Get(label string) string {
return ls[label]
}
// AsSelector converts labels into a selectors. It does not
// perform any validation, which means the server will reject
// the request if the Set contains invalid values.
func (ls Set) AsSelector() Selector {
return SelectorFromSet(ls)
}
// AsValidatedSelector converts labels into a selectors.
// The Set is validated client-side, which allows to catch errors early.
func (ls Set) AsValidatedSelector() (Selector, error) {
return ValidatedSelectorFromSet(ls)
}
// AsSelectorPreValidated converts labels into a selector, but
// assumes that labels are already validated and thus doesn't
// perform any validation.
// According to our measurements this is significantly faster
// in codepaths that matter at high scale.
func (ls Set) AsSelectorPreValidated() Selector {
return SelectorFromValidatedSet(ls)
}
// FormatLabels convert label map into plain string
func FormatLabels(labelMap map[string]string) string {
l := Set(labelMap).String()
if l == "" {
l = "<none>"
}
return l
}
// Conflicts takes 2 maps and returns true if there a key match between
// the maps but the value doesn't match, and returns false in other cases
func Conflicts(labels1, labels2 Set) bool {
small := labels1
big := labels2
if len(labels2) < len(labels1) {
small = labels2
big = labels1
}
for k, v := range small {
if val, match := big[k]; match {
if val != v {
return true
}
}
}
return false
}
// Merge combines given maps, and does not check for any conflicts
// between the maps. In case of conflicts, second map (labels2) wins
func Merge(labels1, labels2 Set) Set {
mergedMap := Set{}
for k, v := range labels1 {
mergedMap[k] = v
}
for k, v := range labels2 {
mergedMap[k] = v
}
return mergedMap
}
// Equals returns true if the given maps are equal
func Equals(labels1, labels2 Set) bool {
if len(labels1) != len(labels2) {
return false
}
for k, v := range labels1 {
value, ok := labels2[k]
if !ok {
return false
}
if value != v {
return false
}
}
return true
}
// AreLabelsInWhiteList verifies if the provided label list
// is in the provided whitelist and returns true, otherwise false.
func AreLabelsInWhiteList(labels, whitelist Set) bool {
if len(whitelist) == 0 {
return true
}
for k, v := range labels {
value, ok := whitelist[k]
if !ok {
return false
}
if value != v {
return false
}
}
return true
}
// ConvertSelectorToLabelsMap converts selector string to labels map
// and validates keys and values
func ConvertSelectorToLabelsMap(selector string) (Set, error) {
labelsMap := Set{}
if len(selector) == 0 {
return labelsMap, nil
}
labels := strings.Split(selector, ",")
for _, label := range labels {
l := strings.Split(label, "=")
if len(l) != 2 {
return labelsMap, fmt.Errorf("invalid selector: %s", l)
}
key := strings.TrimSpace(l[0])
if err := validateLabelKey(key); err != nil {
return labelsMap, err
}
value := strings.TrimSpace(l[1])
if err := validateLabelValue(key, value); err != nil {
return labelsMap, err
}
labelsMap[key] = value
}
return labelsMap, nil
}

View File

@@ -0,0 +1,925 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/labels/selector.go
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package labels
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"log"
"sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/selection"
"sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/util/sets"
"sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/util/validation"
)
// Requirements is AND of all requirements.
type Requirements []Requirement
// Selector represents a label selector.
type Selector interface {
// Matches returns true if this selector matches the given set of labels.
Matches(Labels) bool
// Empty returns true if this selector does not restrict the selection space.
Empty() bool
// String returns a human readable string that represents this selector.
String() string
// Add adds requirements to the Selector
Add(r ...Requirement) Selector
// Requirements converts this interface into Requirements to expose
// more detailed selection information.
// If there are querying parameters, it will return converted requirements and selectable=true.
// If this selector doesn't want to select anything, it will return selectable=false.
Requirements() (requirements Requirements, selectable bool)
// Make a deep copy of the selector.
DeepCopySelector() Selector
// RequiresExactMatch allows a caller to introspect whether a given selector
// requires a single specific label to be set, and if so returns the value it
// requires.
RequiresExactMatch(label string) (value string, found bool)
}
// Everything returns a selector that matches all labels.
func Everything() Selector {
return internalSelector{}
}
type nothingSelector struct{}
func (n nothingSelector) Matches(_ Labels) bool { return false }
func (n nothingSelector) Empty() bool { return false }
func (n nothingSelector) String() string { return "" }
func (n nothingSelector) Add(_ ...Requirement) Selector { return n }
func (n nothingSelector) Requirements() (Requirements, bool) { return nil, false }
func (n nothingSelector) DeepCopySelector() Selector { return n }
func (n nothingSelector) RequiresExactMatch(label string) (value string, found bool) {
return "", false
}
// Nothing returns a selector that matches no labels
func Nothing() Selector {
return nothingSelector{}
}
// NewSelector returns a nil selector
func NewSelector() Selector {
return internalSelector(nil)
}
type internalSelector []Requirement
func (s internalSelector) DeepCopy() internalSelector {
if s == nil {
return nil
}
result := make([]Requirement, len(s))
for i := range s {
s[i].DeepCopyInto(&result[i])
}
return result
}
func (s internalSelector) DeepCopySelector() Selector {
return s.DeepCopy()
}
// ByKey sorts requirements by key to obtain deterministic parser
type ByKey []Requirement
func (a ByKey) Len() int { return len(a) }
func (a ByKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByKey) Less(i, j int) bool { return a[i].key < a[j].key }
// Requirement contains values, a key, and an operator that relates the key and values.
// The zero value of Requirement is invalid.
// Requirement implements both set based match and exact match
// Requirement should be initialized via NewRequirement constructor for creating a valid Requirement.
type Requirement struct {
key string
operator selection.Operator
// In huge majority of cases we have at most one value here.
// It is generally faster to operate on a single-element slice
// than on a single-element map, so we have a slice here.
strValues []string
}
// NewRequirement is the constructor for a Requirement.
// If any of these rules is violated, an error is returned:
// (1) The operator can only be In, NotIn, Equals, DoubleEquals, NotEquals, Exists, or DoesNotExist.
// (2) If the operator is In or NotIn, the values set must be non-empty.
// (3) If the operator is Equals, DoubleEquals, or NotEquals, the values set must contain one value.
// (4) If the operator is Exists or DoesNotExist, the value set must be empty.
// (5) If the operator is Gt or Lt, the values set must contain only one value, which will be interpreted as an integer.
// (6) The key is invalid due to its length, or sequence
// of characters. See validateLabelKey for more details.
//
// The empty string is a valid value in the input values set.
func NewRequirement(key string, op selection.Operator, vals []string) (*Requirement, error) {
if err := validateLabelKey(key); err != nil {
return nil, err
}
switch op {
case selection.In, selection.NotIn:
if len(vals) == 0 {
return nil, fmt.Errorf("for 'in', 'notin' operators, values set can't be empty")
}
case selection.Equals, selection.DoubleEquals, selection.NotEquals:
if len(vals) != 1 {
return nil, fmt.Errorf("exact-match compatibility requires one single value")
}
case selection.Exists, selection.DoesNotExist:
if len(vals) != 0 {
return nil, fmt.Errorf("values set must be empty for exists and does not exist")
}
case selection.GreaterThan, selection.LessThan:
if len(vals) != 1 {
return nil, fmt.Errorf("for 'Gt', 'Lt' operators, exactly one value is required")
}
for i := range vals {
if _, err := strconv.ParseInt(vals[i], 10, 64); err != nil {
return nil, fmt.Errorf("for 'Gt', 'Lt' operators, the value must be an integer")
}
}
default:
return nil, fmt.Errorf("operator '%v' is not recognized", op)
}
for i := range vals {
if err := validateLabelValue(key, vals[i]); err != nil {
return nil, err
}
}
return &Requirement{key: key, operator: op, strValues: vals}, nil
}
func (r *Requirement) hasValue(value string) bool {
for i := range r.strValues {
if r.strValues[i] == value {
return true
}
}
return false
}
// Matches returns true if the Requirement matches the input Labels.
// There is a match in the following cases:
// (1) The operator is Exists and Labels has the Requirement's key.
// (2) The operator is In, Labels has the Requirement's key and Labels'
// value for that key is in Requirement's value set.
// (3) The operator is NotIn, Labels has the Requirement's key and
// Labels' value for that key is not in Requirement's value set.
// (4) The operator is DoesNotExist or NotIn and Labels does not have the
// Requirement's key.
// (5) The operator is GreaterThanOperator or LessThanOperator, and Labels has
// the Requirement's key and the corresponding value satisfies mathematical inequality.
func (r *Requirement) Matches(ls Labels) bool {
switch r.operator {
case selection.In, selection.Equals, selection.DoubleEquals:
if !ls.Has(r.key) {
return false
}
return r.hasValue(ls.Get(r.key))
case selection.NotIn, selection.NotEquals:
if !ls.Has(r.key) {
return true
}
return !r.hasValue(ls.Get(r.key))
case selection.Exists:
return ls.Has(r.key)
case selection.DoesNotExist:
return !ls.Has(r.key)
case selection.GreaterThan, selection.LessThan:
if !ls.Has(r.key) {
return false
}
lsValue, err := strconv.ParseInt(ls.Get(r.key), 10, 64)
if err != nil {
log.Printf("ParseInt failed for value %+v in label %+v, %+v", ls.Get(r.key), ls, err)
return false
}
// There should be only one strValue in r.strValues, and can be converted to an integer.
if len(r.strValues) != 1 {
log.Printf("Invalid values count %+v of requirement %#v, for 'Gt', 'Lt' operators, exactly one value is required", len(r.strValues), r)
return false
}
var rValue int64
for i := range r.strValues {
rValue, err = strconv.ParseInt(r.strValues[i], 10, 64)
if err != nil {
log.Printf("ParseInt failed for value %+v in requirement %#v, for 'Gt', 'Lt' operators, the value must be an integer", r.strValues[i], r)
return false
}
}
return (r.operator == selection.GreaterThan && lsValue > rValue) || (r.operator == selection.LessThan && lsValue < rValue)
default:
return false
}
}
// Key returns requirement key
func (r *Requirement) Key() string {
return r.key
}
// Operator returns requirement operator
func (r *Requirement) Operator() selection.Operator {
return r.operator
}
// Values returns requirement values
func (r *Requirement) Values() sets.String {
ret := sets.String{}
for i := range r.strValues {
ret.Insert(r.strValues[i])
}
return ret
}
// Empty returns true if the internalSelector doesn't restrict selection space
func (lsel internalSelector) Empty() bool {
if lsel == nil {
return true
}
return len(lsel) == 0
}
// String returns a human-readable string that represents this
// Requirement. If called on an invalid Requirement, an error is
// returned. See NewRequirement for creating a valid Requirement.
func (r *Requirement) String() string {
var buffer bytes.Buffer
if r.operator == selection.DoesNotExist {
buffer.WriteString("!")
}
buffer.WriteString(r.key)
switch r.operator {
case selection.Equals:
buffer.WriteString("=")
case selection.DoubleEquals:
buffer.WriteString("==")
case selection.NotEquals:
buffer.WriteString("!=")
case selection.In:
buffer.WriteString(" in ")
case selection.NotIn:
buffer.WriteString(" notin ")
case selection.GreaterThan:
buffer.WriteString(">")
case selection.LessThan:
buffer.WriteString("<")
case selection.Exists, selection.DoesNotExist:
return buffer.String()
}
switch r.operator {
case selection.In, selection.NotIn:
buffer.WriteString("(")
}
if len(r.strValues) == 1 {
buffer.WriteString(r.strValues[0])
} else { // only > 1 since == 0 prohibited by NewRequirement
// normalizes value order on output, without mutating the in-memory selector representation
// also avoids normalization when it is not required, and ensures we do not mutate shared data
buffer.WriteString(strings.Join(safeSort(r.strValues), ","))
}
switch r.operator {
case selection.In, selection.NotIn:
buffer.WriteString(")")
}
return buffer.String()
}
// safeSort sort input strings without modification
func safeSort(in []string) []string {
if sort.StringsAreSorted(in) {
return in
}
out := make([]string, len(in))
copy(out, in)
sort.Strings(out)
return out
}
// Add adds requirements to the selector. It copies the current selector returning a new one
func (lsel internalSelector) Add(reqs ...Requirement) Selector {
var sel internalSelector
for ix := range lsel {
sel = append(sel, lsel[ix])
}
for _, r := range reqs {
sel = append(sel, r)
}
sort.Sort(ByKey(sel))
return sel
}
// Matches for a internalSelector returns true if all
// its Requirements match the input Labels. If any
// Requirement does not match, false is returned.
func (lsel internalSelector) Matches(l Labels) bool {
for ix := range lsel {
if matches := lsel[ix].Matches(l); !matches {
return false
}
}
return true
}
func (lsel internalSelector) Requirements() (Requirements, bool) { return Requirements(lsel), true }
// String returns a comma-separated string of all
// the internalSelector Requirements' human-readable strings.
func (lsel internalSelector) String() string {
var reqs []string
for ix := range lsel {
reqs = append(reqs, lsel[ix].String())
}
return strings.Join(reqs, ",")
}
// RequiresExactMatch introspect whether a given selector requires a single specific field
// to be set, and if so returns the value it requires.
func (lsel internalSelector) RequiresExactMatch(label string) (value string, found bool) {
for ix := range lsel {
if lsel[ix].key == label {
switch lsel[ix].operator {
case selection.Equals, selection.DoubleEquals, selection.In:
if len(lsel[ix].strValues) == 1 {
return lsel[ix].strValues[0], true
}
}
return "", false
}
}
return "", false
}
// Token represents constant definition for lexer token
type Token int
const (
// ErrorToken represents scan error
ErrorToken Token = iota
// EndOfStringToken represents end of string
EndOfStringToken
// ClosedParToken represents close parenthesis
ClosedParToken
// CommaToken represents the comma
CommaToken
// DoesNotExistToken represents logic not
DoesNotExistToken
// DoubleEqualsToken represents double equals
DoubleEqualsToken
// EqualsToken represents equal
EqualsToken
// GreaterThanToken represents greater than
GreaterThanToken
// IdentifierToken represents identifier, e.g. keys and values
IdentifierToken
// InToken represents in
InToken
// LessThanToken represents less than
LessThanToken
// NotEqualsToken represents not equal
NotEqualsToken
// NotInToken represents not in
NotInToken
// OpenParToken represents open parenthesis
OpenParToken
)
// string2token contains the mapping between lexer Token and token literal
// (except IdentifierToken, EndOfStringToken and ErrorToken since it makes no sense)
var string2token = map[string]Token{
")": ClosedParToken,
",": CommaToken,
"!": DoesNotExistToken,
"==": DoubleEqualsToken,
"=": EqualsToken,
">": GreaterThanToken,
"in": InToken,
"<": LessThanToken,
"!=": NotEqualsToken,
"notin": NotInToken,
"(": OpenParToken,
}
// ScannedItem contains the Token and the literal produced by the lexer.
type ScannedItem struct {
tok Token
literal string
}
// isWhitespace returns true if the rune is a space, tab, or newline.
func isWhitespace(ch byte) bool {
return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'
}
// isSpecialSymbol detect if the character ch can be an operator
func isSpecialSymbol(ch byte) bool {
switch ch {
case '=', '!', '(', ')', ',', '>', '<':
return true
}
return false
}
// Lexer represents the Lexer struct for label selector.
// It contains necessary informationt to tokenize the input string
type Lexer struct {
// s stores the string to be tokenized
s string
// pos is the position currently tokenized
pos int
}
// read return the character currently lexed
// increment the position and check the buffer overflow
func (l *Lexer) read() (b byte) {
b = 0
if l.pos < len(l.s) {
b = l.s[l.pos]
l.pos++
}
return b
}
// unread 'undoes' the last read character
func (l *Lexer) unread() {
l.pos--
}
// scanIDOrKeyword scans string to recognize literal token (for example 'in') or an identifier.
func (l *Lexer) scanIDOrKeyword() (tok Token, lit string) {
var buffer []byte
IdentifierLoop:
for {
switch ch := l.read(); {
case ch == 0:
break IdentifierLoop
case isSpecialSymbol(ch) || isWhitespace(ch):
l.unread()
break IdentifierLoop
default:
buffer = append(buffer, ch)
}
}
s := string(buffer)
if val, ok := string2token[s]; ok { // is a literal token?
return val, s
}
return IdentifierToken, s // otherwise is an identifier
}
// scanSpecialSymbol scans string starting with special symbol.
// special symbol identify non literal operators. "!=", "==", "="
func (l *Lexer) scanSpecialSymbol() (Token, string) {
lastScannedItem := ScannedItem{}
var buffer []byte
SpecialSymbolLoop:
for {
switch ch := l.read(); {
case ch == 0:
break SpecialSymbolLoop
case isSpecialSymbol(ch):
buffer = append(buffer, ch)
if token, ok := string2token[string(buffer)]; ok {
lastScannedItem = ScannedItem{tok: token, literal: string(buffer)}
} else if lastScannedItem.tok != 0 {
l.unread()
break SpecialSymbolLoop
}
default:
l.unread()
break SpecialSymbolLoop
}
}
if lastScannedItem.tok == 0 {
return ErrorToken, fmt.Sprintf("error expected: keyword found '%s'", buffer)
}
return lastScannedItem.tok, lastScannedItem.literal
}
// skipWhiteSpaces consumes all blank characters
// returning the first non blank character
func (l *Lexer) skipWhiteSpaces(ch byte) byte {
for {
if !isWhitespace(ch) {
return ch
}
ch = l.read()
}
}
// Lex returns a pair of Token and the literal
// literal is meaningfull only for IdentifierToken token
func (l *Lexer) Lex() (tok Token, lit string) {
switch ch := l.skipWhiteSpaces(l.read()); {
case ch == 0:
return EndOfStringToken, ""
case isSpecialSymbol(ch):
l.unread()
return l.scanSpecialSymbol()
default:
l.unread()
return l.scanIDOrKeyword()
}
}
// Parser data structure contains the label selector parser data structure
type Parser struct {
l *Lexer
scannedItems []ScannedItem
position int
}
// ParserContext represents context during parsing:
// some literal for example 'in' and 'notin' can be
// recognized as operator for example 'x in (a)' but
// it can be recognized as value for example 'value in (in)'
type ParserContext int
const (
// KeyAndOperator represents key and operator
KeyAndOperator ParserContext = iota
// Values represents values
Values
)
// lookahead func returns the current token and string. No increment of current position
func (p *Parser) lookahead(context ParserContext) (Token, string) {
tok, lit := p.scannedItems[p.position].tok, p.scannedItems[p.position].literal
if context == Values {
switch tok {
case InToken, NotInToken:
tok = IdentifierToken
}
}
return tok, lit
}
// consume returns current token and string. Increments the position
func (p *Parser) consume(context ParserContext) (Token, string) {
p.position++
tok, lit := p.scannedItems[p.position-1].tok, p.scannedItems[p.position-1].literal
if context == Values {
switch tok {
case InToken, NotInToken:
tok = IdentifierToken
}
}
return tok, lit
}
// scan runs through the input string and stores the ScannedItem in an array
// Parser can now lookahead and consume the tokens
func (p *Parser) scan() {
for {
token, literal := p.l.Lex()
p.scannedItems = append(p.scannedItems, ScannedItem{token, literal})
if token == EndOfStringToken {
break
}
}
}
// parse runs the left recursive descending algorithm
// on input string. It returns a list of Requirement objects.
func (p *Parser) parse() (internalSelector, error) {
p.scan() // init scannedItems
var requirements internalSelector
for {
tok, lit := p.lookahead(Values)
switch tok {
case IdentifierToken, DoesNotExistToken:
r, err := p.parseRequirement()
if err != nil {
return nil, fmt.Errorf("unable to parse requirement: %v", err)
}
requirements = append(requirements, *r)
t, l := p.consume(Values)
switch t {
case EndOfStringToken:
return requirements, nil
case CommaToken:
t2, l2 := p.lookahead(Values)
if t2 != IdentifierToken && t2 != DoesNotExistToken {
return nil, fmt.Errorf("found '%s', expected: identifier after ','", l2)
}
default:
return nil, fmt.Errorf("found '%s', expected: ',' or 'end of string'", l)
}
case EndOfStringToken:
return requirements, nil
default:
return nil, fmt.Errorf("found '%s', expected: !, identifier, or 'end of string'", lit)
}
}
}
func (p *Parser) parseRequirement() (*Requirement, error) {
key, operator, err := p.parseKeyAndInferOperator()
if err != nil {
return nil, err
}
if operator == selection.Exists || operator == selection.DoesNotExist { // operator found lookahead set checked
return NewRequirement(key, operator, []string{})
}
operator, err = p.parseOperator()
if err != nil {
return nil, err
}
var values sets.String
switch operator {
case selection.In, selection.NotIn:
values, err = p.parseValues()
case selection.Equals, selection.DoubleEquals, selection.NotEquals, selection.GreaterThan, selection.LessThan:
values, err = p.parseExactValue()
}
if err != nil {
return nil, err
}
return NewRequirement(key, operator, values.List())
}
// parseKeyAndInferOperator parse literals.
// in case of no operator '!, in, notin, ==, =, !=' are found
// the 'exists' operator is inferred
func (p *Parser) parseKeyAndInferOperator() (string, selection.Operator, error) {
var operator selection.Operator
tok, literal := p.consume(Values)
if tok == DoesNotExistToken {
operator = selection.DoesNotExist
tok, literal = p.consume(Values)
}
if tok != IdentifierToken {
err := fmt.Errorf("found '%s', expected: identifier", literal)
return "", "", err
}
if err := validateLabelKey(literal); err != nil {
return "", "", err
}
if t, _ := p.lookahead(Values); t == EndOfStringToken || t == CommaToken {
if operator != selection.DoesNotExist {
operator = selection.Exists
}
}
return literal, operator, nil
}
// parseOperator return operator and eventually matchType
// matchType can be exact
func (p *Parser) parseOperator() (op selection.Operator, err error) {
tok, lit := p.consume(KeyAndOperator)
switch tok {
// DoesNotExistToken shouldn't be here because it's a unary operator, not a binary operator
case InToken:
op = selection.In
case EqualsToken:
op = selection.Equals
case DoubleEqualsToken:
op = selection.DoubleEquals
case GreaterThanToken:
op = selection.GreaterThan
case LessThanToken:
op = selection.LessThan
case NotInToken:
op = selection.NotIn
case NotEqualsToken:
op = selection.NotEquals
default:
return "", fmt.Errorf("found '%s', expected: '=', '!=', '==', 'in', notin'", lit)
}
return op, nil
}
// parseValues parses the values for set based matching (x,y,z)
func (p *Parser) parseValues() (sets.String, error) {
tok, lit := p.consume(Values)
if tok != OpenParToken {
return nil, fmt.Errorf("found '%s' expected: '('", lit)
}
tok, lit = p.lookahead(Values)
switch tok {
case IdentifierToken, CommaToken:
s, err := p.parseIdentifiersList() // handles general cases
if err != nil {
return s, err
}
if tok, _ = p.consume(Values); tok != ClosedParToken {
return nil, fmt.Errorf("found '%s', expected: ')'", lit)
}
return s, nil
case ClosedParToken: // handles "()"
p.consume(Values)
return sets.NewString(""), nil
default:
return nil, fmt.Errorf("found '%s', expected: ',', ')' or identifier", lit)
}
}
// parseIdentifiersList parses a (possibly empty) list of
// of comma separated (possibly empty) identifiers
func (p *Parser) parseIdentifiersList() (sets.String, error) {
s := sets.NewString()
for {
tok, lit := p.consume(Values)
switch tok {
case IdentifierToken:
s.Insert(lit)
tok2, lit2 := p.lookahead(Values)
switch tok2 {
case CommaToken:
continue
case ClosedParToken:
return s, nil
default:
return nil, fmt.Errorf("found '%s', expected: ',' or ')'", lit2)
}
case CommaToken: // handled here since we can have "(,"
if s.Len() == 0 {
s.Insert("") // to handle (,
}
tok2, _ := p.lookahead(Values)
if tok2 == ClosedParToken {
s.Insert("") // to handle ,) Double "" removed by StringSet
return s, nil
}
if tok2 == CommaToken {
p.consume(Values)
s.Insert("") // to handle ,, Double "" removed by StringSet
}
default: // it can be operator
return s, fmt.Errorf("found '%s', expected: ',', or identifier", lit)
}
}
}
// parseExactValue parses the only value for exact match style
func (p *Parser) parseExactValue() (sets.String, error) {
s := sets.NewString()
tok, lit := p.lookahead(Values)
if tok == EndOfStringToken || tok == CommaToken {
s.Insert("")
return s, nil
}
tok, lit = p.consume(Values)
if tok == IdentifierToken {
s.Insert(lit)
return s, nil
}
return nil, fmt.Errorf("found '%s', expected: identifier", lit)
}
// Parse takes a string representing a selector and returns a selector
// object, or an error. This parsing function differs from ParseSelector
// as they parse different selectors with different syntaxes.
// The input will cause an error if it does not follow this form:
//
// <selector-syntax> ::= <requirement> | <requirement> "," <selector-syntax>
// <requirement> ::= [!] KEY [ <set-based-restriction> | <exact-match-restriction> ]
// <set-based-restriction> ::= "" | <inclusion-exclusion> <value-set>
// <inclusion-exclusion> ::= <inclusion> | <exclusion>
// <exclusion> ::= "notin"
// <inclusion> ::= "in"
// <value-set> ::= "(" <values> ")"
// <values> ::= VALUE | VALUE "," <values>
// <exact-match-restriction> ::= ["="|"=="|"!="] VALUE
//
// KEY is a sequence of one or more characters following [ DNS_SUBDOMAIN "/" ] DNS_LABEL. Max length is 63 characters.
// VALUE is a sequence of zero or more characters "([A-Za-z0-9_-\.])". Max length is 63 characters.
// Delimiter is white space: (' ', '\t')
// Example of valid syntax:
// "x in (foo,,baz),y,z notin ()"
//
// Note:
// (1) Inclusion - " in " - denotes that the KEY exists and is equal to any of the
// VALUEs in its requirement
// (2) Exclusion - " notin " - denotes that the KEY is not equal to any
// of the VALUEs in its requirement or does not exist
// (3) The empty string is a valid VALUE
// (4) A requirement with just a KEY - as in "y" above - denotes that
// the KEY exists and can be any VALUE.
// (5) A requirement with just !KEY requires that the KEY not exist.
//
func Parse(selector string) (Selector, error) {
parsedSelector, err := parse(selector)
if err == nil {
return parsedSelector, nil
}
return nil, err
}
// parse parses the string representation of the selector and returns the internalSelector struct.
// The callers of this method can then decide how to return the internalSelector struct to their
// callers. This function has two callers now, one returns a Selector interface and the other
// returns a list of requirements.
func parse(selector string) (internalSelector, error) {
p := &Parser{l: &Lexer{s: selector, pos: 0}}
items, err := p.parse()
if err != nil {
return nil, err
}
sort.Sort(ByKey(items)) // sort to grant determistic parsing
return internalSelector(items), err
}
func validateLabelKey(k string) error {
if errs := validation.IsQualifiedName(k); len(errs) != 0 {
return fmt.Errorf("invalid label key %q: %s", k, strings.Join(errs, "; "))
}
return nil
}
func validateLabelValue(k, v string) error {
if errs := validation.IsValidLabelValue(v); len(errs) != 0 {
return fmt.Errorf("invalid label value: %q: at key: %q: %s", v, k, strings.Join(errs, "; "))
}
return nil
}
// SelectorFromSet returns a Selector which will match exactly the given Set. A
// nil and empty Sets are considered equivalent to Everything().
// It does not perform any validation, which means the server will reject
// the request if the Set contains invalid values.
func SelectorFromSet(ls Set) Selector {
return SelectorFromValidatedSet(ls)
}
// ValidatedSelectorFromSet returns a Selector which will match exactly the given Set. A
// nil and empty Sets are considered equivalent to Everything().
// The Set is validated client-side, which allows to catch errors early.
func ValidatedSelectorFromSet(ls Set) (Selector, error) {
if ls == nil || len(ls) == 0 {
return internalSelector{}, nil
}
requirements := make([]Requirement, 0, len(ls))
for label, value := range ls {
r, err := NewRequirement(label, selection.Equals, []string{value})
if err != nil {
return nil, err
}
requirements = append(requirements, *r)
}
// sort to have deterministic string representation
sort.Sort(ByKey(requirements))
return internalSelector(requirements), nil
}
// SelectorFromValidatedSet returns a Selector which will match exactly the given Set.
// A nil and empty Sets are considered equivalent to Everything().
// It assumes that Set is already validated and doesn't do any validation.
func SelectorFromValidatedSet(ls Set) Selector {
if ls == nil || len(ls) == 0 {
return internalSelector{}
}
requirements := make([]Requirement, 0, len(ls))
for label, value := range ls {
requirements = append(requirements, Requirement{key: label, operator: selection.Equals, strValues: []string{value}})
}
// sort to have deterministic string representation
sort.Sort(ByKey(requirements))
return internalSelector(requirements)
}
// ParseToRequirements takes a string representing a selector and returns a list of
// requirements. This function is suitable for those callers that perform additional
// processing on selector requirements.
// See the documentation for Parse() function for more details.
// TODO: Consider exporting the internalSelector type instead.
func ParseToRequirements(selector string) ([]Requirement, error) {
return parse(selector)
}

View File

@@ -0,0 +1,36 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/selection/operator.go
/*
Copyright 2016 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package selection
// Operator represents a key/field's relationship to value(s).
// See labels.Requirement and fields.Requirement for more details.
type Operator string
const (
DoesNotExist Operator = "!"
Equals Operator = "="
DoubleEquals Operator = "=="
In Operator = "in"
NotEquals Operator = "!="
NotIn Operator = "notin"
Exists Operator = "exists"
GreaterThan Operator = "gt"
LessThan Operator = "lt"
)

View File

@@ -0,0 +1,252 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/util/errors/errors.go
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package errors
import (
"errors"
"fmt"
"sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/util/sets"
)
// MessageCountMap contains occurrence for each error message.
type MessageCountMap map[string]int
// Aggregate represents an object that contains multiple errors, but does not
// necessarily have singular semantic meaning.
// The aggregate can be used with `errors.Is()` to check for the occurrence of
// a specific error type.
// Errors.As() is not supported, because the caller presumably cares about a
// specific error of potentially multiple that match the given type.
type Aggregate interface {
error
Errors() []error
Is(error) bool
}
// NewAggregate converts a slice of errors into an Aggregate interface, which
// is itself an implementation of the error interface. If the slice is empty,
// this returns nil.
// It will check if any of the element of input error list is nil, to avoid
// nil pointer panic when call Error().
func NewAggregate(errlist []error) Aggregate {
if len(errlist) == 0 {
return nil
}
// In case of input error list contains nil
var errs []error
for _, e := range errlist {
if e != nil {
errs = append(errs, e)
}
}
if len(errs) == 0 {
return nil
}
return aggregate(errs)
}
// This helper implements the error and Errors interfaces. Keeping it private
// prevents people from making an aggregate of 0 errors, which is not
// an error, but does satisfy the error interface.
type aggregate []error
// Error is part of the error interface.
func (agg aggregate) Error() string {
if len(agg) == 0 {
// This should never happen, really.
return ""
}
if len(agg) == 1 {
return agg[0].Error()
}
seenerrs := sets.NewString()
result := ""
agg.visit(func(err error) bool {
msg := err.Error()
if seenerrs.Has(msg) {
return false
}
seenerrs.Insert(msg)
if len(seenerrs) > 1 {
result += ", "
}
result += msg
return false
})
if len(seenerrs) == 1 {
return result
}
return "[" + result + "]"
}
func (agg aggregate) Is(target error) bool {
return agg.visit(func(err error) bool {
return errors.Is(err, target)
})
}
func (agg aggregate) visit(f func(err error) bool) bool {
for _, err := range agg {
switch err := err.(type) {
case aggregate:
if match := err.visit(f); match {
return match
}
case Aggregate:
for _, nestedErr := range err.Errors() {
if match := f(nestedErr); match {
return match
}
}
default:
if match := f(err); match {
return match
}
}
}
return false
}
// Errors is part of the Aggregate interface.
func (agg aggregate) Errors() []error {
return []error(agg)
}
// Matcher is used to match errors. Returns true if the error matches.
type Matcher func(error) bool
// FilterOut removes all errors that match any of the matchers from the input
// error. If the input is a singular error, only that error is tested. If the
// input implements the Aggregate interface, the list of errors will be
// processed recursively.
//
// This can be used, for example, to remove known-OK errors (such as io.EOF or
// os.PathNotFound) from a list of errors.
func FilterOut(err error, fns ...Matcher) error {
if err == nil {
return nil
}
if agg, ok := err.(Aggregate); ok {
return NewAggregate(filterErrors(agg.Errors(), fns...))
}
if !matchesError(err, fns...) {
return err
}
return nil
}
// matchesError returns true if any Matcher returns true
func matchesError(err error, fns ...Matcher) bool {
for _, fn := range fns {
if fn(err) {
return true
}
}
return false
}
// filterErrors returns any errors (or nested errors, if the list contains
// nested Errors) for which all fns return false. If no errors
// remain a nil list is returned. The resulting silec will have all
// nested slices flattened as a side effect.
func filterErrors(list []error, fns ...Matcher) []error {
result := []error{}
for _, err := range list {
r := FilterOut(err, fns...)
if r != nil {
result = append(result, r)
}
}
return result
}
// Flatten takes an Aggregate, which may hold other Aggregates in arbitrary
// nesting, and flattens them all into a single Aggregate, recursively.
func Flatten(agg Aggregate) Aggregate {
result := []error{}
if agg == nil {
return nil
}
for _, err := range agg.Errors() {
if a, ok := err.(Aggregate); ok {
r := Flatten(a)
if r != nil {
result = append(result, r.Errors()...)
}
} else {
if err != nil {
result = append(result, err)
}
}
}
return NewAggregate(result)
}
// CreateAggregateFromMessageCountMap converts MessageCountMap Aggregate
func CreateAggregateFromMessageCountMap(m MessageCountMap) Aggregate {
if m == nil {
return nil
}
result := make([]error, 0, len(m))
for errStr, count := range m {
var countStr string
if count > 1 {
countStr = fmt.Sprintf(" (repeated %v times)", count)
}
result = append(result, fmt.Errorf("%v%v", errStr, countStr))
}
return NewAggregate(result)
}
// Reduce will return err or, if err is an Aggregate and only has one item,
// the first item in the aggregate.
func Reduce(err error) error {
if agg, ok := err.(Aggregate); ok && err != nil {
switch len(agg.Errors()) {
case 1:
return agg.Errors()[0]
case 0:
return nil
}
}
return err
}
// AggregateGoroutines runs the provided functions in parallel, stuffing all
// non-nil errors into the returned Aggregate.
// Returns nil if all the functions complete successfully.
func AggregateGoroutines(funcs ...func() error) Aggregate {
errChan := make(chan error, len(funcs))
for _, f := range funcs {
go func(f func() error) { errChan <- f() }(f)
}
errs := make([]error, 0)
for i := 0; i < cap(errChan); i++ {
if err := <-errChan; err != nil {
errs = append(errs, err)
}
}
return NewAggregate(errs)
}
// ErrPreconditionViolated is returned when the precondition is violated
var ErrPreconditionViolated = errors.New("precondition is violated")

View File

@@ -0,0 +1,24 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/util/sets/empty.go
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sets
// Empty is public since it is used by some internal API objects for conversions between external
// string arrays and internal sets, and conversion logic requires public types today.
type Empty struct{}

View File

@@ -0,0 +1,206 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/util/sets/string.go
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sets
import (
"reflect"
"sort"
)
// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption.
type String map[string]Empty
// NewString creates a String from a list of values.
func NewString(items ...string) String {
ss := String{}
ss.Insert(items...)
return ss
}
// StringKeySet creates a String from a keys of a map[string](? extends interface{}).
// If the value passed in is not actually a map, this will panic.
func StringKeySet(theMap interface{}) String {
v := reflect.ValueOf(theMap)
ret := String{}
for _, keyValue := range v.MapKeys() {
ret.Insert(keyValue.Interface().(string))
}
return ret
}
// Insert adds items to the set.
func (s String) Insert(items ...string) String {
for _, item := range items {
s[item] = Empty{}
}
return s
}
// Delete removes all items from the set.
func (s String) Delete(items ...string) String {
for _, item := range items {
delete(s, item)
}
return s
}
// Has returns true if and only if item is contained in the set.
func (s String) Has(item string) bool {
_, contained := s[item]
return contained
}
// HasAll returns true if and only if all items are contained in the set.
func (s String) HasAll(items ...string) bool {
for _, item := range items {
if !s.Has(item) {
return false
}
}
return true
}
// HasAny returns true if any items are contained in the set.
func (s String) HasAny(items ...string) bool {
for _, item := range items {
if s.Has(item) {
return true
}
}
return false
}
// Difference returns a set of objects that are not in s2
// For example:
// s1 = {a1, a2, a3}
// s2 = {a1, a2, a4, a5}
// s1.Difference(s2) = {a3}
// s2.Difference(s1) = {a4, a5}
func (s String) Difference(s2 String) String {
result := NewString()
for key := range s {
if !s2.Has(key) {
result.Insert(key)
}
}
return result
}
// Union returns a new set which includes items in either s1 or s2.
// For example:
// s1 = {a1, a2}
// s2 = {a3, a4}
// s1.Union(s2) = {a1, a2, a3, a4}
// s2.Union(s1) = {a1, a2, a3, a4}
func (s1 String) Union(s2 String) String {
result := NewString()
for key := range s1 {
result.Insert(key)
}
for key := range s2 {
result.Insert(key)
}
return result
}
// Intersection returns a new set which includes the item in BOTH s1 and s2
// For example:
// s1 = {a1, a2}
// s2 = {a2, a3}
// s1.Intersection(s2) = {a2}
func (s1 String) Intersection(s2 String) String {
var walk, other String
result := NewString()
if s1.Len() < s2.Len() {
walk = s1
other = s2
} else {
walk = s2
other = s1
}
for key := range walk {
if other.Has(key) {
result.Insert(key)
}
}
return result
}
// IsSuperset returns true if and only if s1 is a superset of s2.
func (s1 String) IsSuperset(s2 String) bool {
for item := range s2 {
if !s1.Has(item) {
return false
}
}
return true
}
// Equal returns true if and only if s1 is equal (as a set) to s2.
// Two sets are equal if their membership is identical.
// (In practice, this means same elements, order doesn't matter)
func (s1 String) Equal(s2 String) bool {
return len(s1) == len(s2) && s1.IsSuperset(s2)
}
type sortableSliceOfString []string
func (s sortableSliceOfString) Len() int { return len(s) }
func (s sortableSliceOfString) Less(i, j int) bool { return lessString(s[i], s[j]) }
func (s sortableSliceOfString) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// List returns the contents as a sorted string slice.
func (s String) List() []string {
res := make(sortableSliceOfString, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.Sort(res)
return []string(res)
}
// UnsortedList returns the slice with contents in random order.
func (s String) UnsortedList() []string {
res := make([]string, 0, len(s))
for key := range s {
res = append(res, key)
}
return res
}
// Returns a single element from the set.
func (s String) PopAny() (string, bool) {
for key := range s {
s.Delete(key)
return key, true
}
var zeroValue string
return zeroValue, false
}
// Len returns the size of the set.
func (s String) Len() int {
return len(s)
}
func lessString(lhs, rhs string) bool {
return lhs < rhs
}

View File

@@ -0,0 +1,275 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/util/validation/field/errors.go
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package field
import (
"fmt"
"reflect"
"strconv"
"strings"
utilerrors "sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/util/errors"
"sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/util/sets"
)
// Error is an implementation of the 'error' interface, which represents a
// field-level validation error.
type Error struct {
Type ErrorType
Field string
BadValue interface{}
Detail string
}
var _ error = &Error{}
// Error implements the error interface.
func (v *Error) Error() string {
return fmt.Sprintf("%s: %s", v.Field, v.ErrorBody())
}
// ErrorBody returns the error message without the field name. This is useful
// for building nice-looking higher-level error reporting.
func (v *Error) ErrorBody() string {
var s string
switch v.Type {
case ErrorTypeRequired, ErrorTypeForbidden, ErrorTypeTooLong, ErrorTypeInternal:
s = v.Type.String()
default:
value := v.BadValue
valueType := reflect.TypeOf(value)
if value == nil || valueType == nil {
value = "null"
} else if valueType.Kind() == reflect.Ptr {
if reflectValue := reflect.ValueOf(value); reflectValue.IsNil() {
value = "null"
} else {
value = reflectValue.Elem().Interface()
}
}
switch t := value.(type) {
case int64, int32, float64, float32, bool:
// use simple printer for simple types
s = fmt.Sprintf("%s: %v", v.Type, value)
case string:
s = fmt.Sprintf("%s: %q", v.Type, t)
case fmt.Stringer:
// anything that defines String() is better than raw struct
s = fmt.Sprintf("%s: %s", v.Type, t.String())
default:
// fallback to raw struct
// TODO: internal types have panic guards against json.Marshalling to prevent
// accidental use of internal types in external serialized form. For now, use
// %#v, although it would be better to show a more expressive output in the future
s = fmt.Sprintf("%s: %#v", v.Type, value)
}
}
if len(v.Detail) != 0 {
s += fmt.Sprintf(": %s", v.Detail)
}
return s
}
// ErrorType is a machine readable value providing more detail about why
// a field is invalid. These values are expected to match 1-1 with
// CauseType in api/types.go.
type ErrorType string
// TODO: These values are duplicated in api/types.go, but there's a circular dep. Fix it.
const (
// ErrorTypeNotFound is used to report failure to find a requested value
// (e.g. looking up an ID). See NotFound().
ErrorTypeNotFound ErrorType = "FieldValueNotFound"
// ErrorTypeRequired is used to report required values that are not
// provided (e.g. empty strings, null values, or empty arrays). See
// Required().
ErrorTypeRequired ErrorType = "FieldValueRequired"
// ErrorTypeDuplicate is used to report collisions of values that must be
// unique (e.g. unique IDs). See Duplicate().
ErrorTypeDuplicate ErrorType = "FieldValueDuplicate"
// ErrorTypeInvalid is used to report malformed values (e.g. failed regex
// match, too long, out of bounds). See Invalid().
ErrorTypeInvalid ErrorType = "FieldValueInvalid"
// ErrorTypeNotSupported is used to report unknown values for enumerated
// fields (e.g. a list of valid values). See NotSupported().
ErrorTypeNotSupported ErrorType = "FieldValueNotSupported"
// ErrorTypeForbidden is used to report valid (as per formatting rules)
// values which would be accepted under some conditions, but which are not
// permitted by the current conditions (such as security policy). See
// Forbidden().
ErrorTypeForbidden ErrorType = "FieldValueForbidden"
// ErrorTypeTooLong is used to report that the given value is too long.
// This is similar to ErrorTypeInvalid, but the error will not include the
// too-long value. See TooLong().
ErrorTypeTooLong ErrorType = "FieldValueTooLong"
// ErrorTypeTooMany is used to report "too many". This is used to
// report that a given list has too many items. This is similar to FieldValueTooLong,
// but the error indicates quantity instead of length.
ErrorTypeTooMany ErrorType = "FieldValueTooMany"
// ErrorTypeInternal is used to report other errors that are not related
// to user input. See InternalError().
ErrorTypeInternal ErrorType = "InternalError"
)
// String converts a ErrorType into its corresponding canonical error message.
func (t ErrorType) String() string {
switch t {
case ErrorTypeNotFound:
return "Not found"
case ErrorTypeRequired:
return "Required value"
case ErrorTypeDuplicate:
return "Duplicate value"
case ErrorTypeInvalid:
return "Invalid value"
case ErrorTypeNotSupported:
return "Unsupported value"
case ErrorTypeForbidden:
return "Forbidden"
case ErrorTypeTooLong:
return "Too long"
case ErrorTypeTooMany:
return "Too many"
case ErrorTypeInternal:
return "Internal error"
default:
panic(fmt.Sprintf("unrecognized validation error: %q", string(t)))
}
}
// NotFound returns a *Error indicating "value not found". This is
// used to report failure to find a requested value (e.g. looking up an ID).
func NotFound(field *Path, value interface{}) *Error {
return &Error{ErrorTypeNotFound, field.String(), value, ""}
}
// Required returns a *Error indicating "value required". This is used
// to report required values that are not provided (e.g. empty strings, null
// values, or empty arrays).
func Required(field *Path, detail string) *Error {
return &Error{ErrorTypeRequired, field.String(), "", detail}
}
// Duplicate returns a *Error indicating "duplicate value". This is
// used to report collisions of values that must be unique (e.g. names or IDs).
func Duplicate(field *Path, value interface{}) *Error {
return &Error{ErrorTypeDuplicate, field.String(), value, ""}
}
// Invalid returns a *Error indicating "invalid value". This is used
// to report malformed values (e.g. failed regex match, too long, out of bounds).
func Invalid(field *Path, value interface{}, detail string) *Error {
return &Error{ErrorTypeInvalid, field.String(), value, detail}
}
// NotSupported returns a *Error indicating "unsupported value".
// This is used to report unknown values for enumerated fields (e.g. a list of
// valid values).
func NotSupported(field *Path, value interface{}, validValues []string) *Error {
detail := ""
if validValues != nil && len(validValues) > 0 {
quotedValues := make([]string, len(validValues))
for i, v := range validValues {
quotedValues[i] = strconv.Quote(v)
}
detail = "supported values: " + strings.Join(quotedValues, ", ")
}
return &Error{ErrorTypeNotSupported, field.String(), value, detail}
}
// Forbidden returns a *Error indicating "forbidden". This is used to
// report valid (as per formatting rules) values which would be accepted under
// some conditions, but which are not permitted by current conditions (e.g.
// security policy).
func Forbidden(field *Path, detail string) *Error {
return &Error{ErrorTypeForbidden, field.String(), "", detail}
}
// TooLong returns a *Error indicating "too long". This is used to
// report that the given value is too long. This is similar to
// Invalid, but the returned error will not include the too-long
// value.
func TooLong(field *Path, value interface{}, maxLength int) *Error {
return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d bytes", maxLength)}
}
// TooMany returns a *Error indicating "too many". This is used to
// report that a given list has too many items. This is similar to TooLong,
// but the returned error indicates quantity instead of length.
func TooMany(field *Path, actualQuantity, maxQuantity int) *Error {
return &Error{ErrorTypeTooMany, field.String(), actualQuantity, fmt.Sprintf("must have at most %d items", maxQuantity)}
}
// InternalError returns a *Error indicating "internal error". This is used
// to signal that an error was found that was not directly related to user
// input. The err argument must be non-nil.
func InternalError(field *Path, err error) *Error {
return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
}
// ErrorList holds a set of Errors. It is plausible that we might one day have
// non-field errors in this same umbrella package, but for now we don't, so
// we can keep it simple and leave ErrorList here.
type ErrorList []*Error
// NewErrorTypeMatcher returns an errors.Matcher that returns true
// if the provided error is a Error and has the provided ErrorType.
func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
return func(err error) bool {
if e, ok := err.(*Error); ok {
return e.Type == t
}
return false
}
}
// ToAggregate converts the ErrorList into an errors.Aggregate.
func (list ErrorList) ToAggregate() utilerrors.Aggregate {
errs := make([]error, 0, len(list))
errorMsgs := sets.NewString()
for _, err := range list {
msg := fmt.Sprintf("%v", err)
if errorMsgs.Has(msg) {
continue
}
errorMsgs.Insert(msg)
errs = append(errs, err)
}
return utilerrors.NewAggregate(errs)
}
func fromAggregate(agg utilerrors.Aggregate) ErrorList {
errs := agg.Errors()
list := make(ErrorList, len(errs))
for i := range errs {
list[i] = errs[i].(*Error)
}
return list
}
// Filter removes items from the ErrorList that match the provided fns.
func (list ErrorList) Filter(fns ...utilerrors.Matcher) ErrorList {
err := utilerrors.FilterOut(list.ToAggregate(), fns...)
if err == nil {
return nil
}
// FilterOut takes an Aggregate and returns an Aggregate
return fromAggregate(err.(utilerrors.Aggregate))
}

View File

@@ -0,0 +1,94 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/util/validation/field/path.go
/*
Copyright 2015 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package field
import (
"bytes"
"fmt"
"strconv"
)
// Path represents the path from some root to a particular field.
type Path struct {
name string // the name of this field or "" if this is an index
index string // if name == "", this is a subscript (index or map key) of the previous element
parent *Path // nil if this is the root element
}
// NewPath creates a root Path object.
func NewPath(name string, moreNames ...string) *Path {
r := &Path{name: name, parent: nil}
for _, anotherName := range moreNames {
r = &Path{name: anotherName, parent: r}
}
return r
}
// Root returns the root element of this Path.
func (p *Path) Root() *Path {
for ; p.parent != nil; p = p.parent {
// Do nothing.
}
return p
}
// Child creates a new Path that is a child of the method receiver.
func (p *Path) Child(name string, moreNames ...string) *Path {
r := NewPath(name, moreNames...)
r.Root().parent = p
return r
}
// Index indicates that the previous Path is to be subscripted by an int.
// This sets the same underlying value as Key.
func (p *Path) Index(index int) *Path {
return &Path{index: strconv.Itoa(index), parent: p}
}
// Key indicates that the previous Path is to be subscripted by a string.
// This sets the same underlying value as Index.
func (p *Path) Key(key string) *Path {
return &Path{index: key, parent: p}
}
// String produces a string representation of the Path.
func (p *Path) String() string {
// make a slice to iterate
elems := []*Path{}
for ; p != nil; p = p.parent {
elems = append(elems, p)
}
// iterate, but it has to be backwards
buf := bytes.NewBuffer(nil)
for i := range elems {
p := elems[len(elems)-1-i]
if p.parent != nil && len(p.name) > 0 {
// This is either the root or it is a subscript.
buf.WriteString(".")
}
if len(p.name) > 0 {
buf.WriteString(p.name)
} else {
fmt.Fprintf(buf, "[%s]", p.index)
}
}
return buf.String()
}

View File

@@ -0,0 +1,506 @@
// Code generated by k8scopy from k8s.io/apimachinery@v0.19.8; DO NOT EDIT.
// File content copied from k8s.io/apimachinery@v0.19.8/pkg/util/validation/validation.go
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"fmt"
"math"
"net"
"regexp"
"strconv"
"strings"
"sigs.k8s.io/kustomize/kyaml/yaml/internal/k8sgen/pkg/util/validation/field"
)
const qnameCharFmt string = "[A-Za-z0-9]"
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
const qualifiedNameMaxLength int = 63
var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
// IsQualifiedName tests whether the value passed is what Kubernetes calls a
// "qualified name". This is a format used in various places throughout the
// system. If the value is not valid, a list of error strings is returned.
// Otherwise an empty list (or nil) is returned.
func IsQualifiedName(value string) []string {
var errs []string
parts := strings.Split(value, "/")
var name string
switch len(parts) {
case 1:
name = parts[0]
case 2:
var prefix string
prefix, name = parts[0], parts[1]
if len(prefix) == 0 {
errs = append(errs, "prefix part "+EmptyError())
} else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
errs = append(errs, prefixEach(msgs, "prefix part ")...)
}
default:
return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
" with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')")
}
if len(name) == 0 {
errs = append(errs, "name part "+EmptyError())
} else if len(name) > qualifiedNameMaxLength {
errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength))
}
if !qualifiedNameRegexp.MatchString(name) {
errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"))
}
return errs
}
// IsFullyQualifiedName checks if the name is fully qualified. This is similar
// to IsFullyQualifiedDomainName but requires a minimum of 3 segments instead of
// 2 and does not accept a trailing . as valid.
// TODO: This function is deprecated and preserved until all callers migrate to
// IsFullyQualifiedDomainName; please don't add new callers.
func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList {
var allErrors field.ErrorList
if len(name) == 0 {
return append(allErrors, field.Required(fldPath, ""))
}
if errs := IsDNS1123Subdomain(name); len(errs) > 0 {
return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ",")))
}
if len(strings.Split(name, ".")) < 3 {
return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least three segments separated by dots"))
}
return allErrors
}
// IsFullyQualifiedDomainName checks if the domain name is fully qualified. This
// is similar to IsFullyQualifiedName but only requires a minimum of 2 segments
// instead of 3 and accepts a trailing . as valid.
func IsFullyQualifiedDomainName(fldPath *field.Path, name string) field.ErrorList {
var allErrors field.ErrorList
if len(name) == 0 {
return append(allErrors, field.Required(fldPath, ""))
}
if strings.HasSuffix(name, ".") {
name = name[:len(name)-1]
}
if errs := IsDNS1123Subdomain(name); len(errs) > 0 {
return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ",")))
}
if len(strings.Split(name, ".")) < 2 {
return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least two segments separated by dots"))
}
for _, label := range strings.Split(name, ".") {
if errs := IsDNS1123Label(label); len(errs) > 0 {
return append(allErrors, field.Invalid(fldPath, label, strings.Join(errs, ",")))
}
}
return allErrors
}
// Allowed characters in an HTTP Path as defined by RFC 3986. A HTTP path may
// contain:
// * unreserved characters (alphanumeric, '-', '.', '_', '~')
// * percent-encoded octets
// * sub-delims ("!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "=")
// * a colon character (":")
const httpPathFmt string = `[A-Za-z0-9/\-._~%!$&'()*+,;=:]+`
var httpPathRegexp = regexp.MustCompile("^" + httpPathFmt + "$")
// IsDomainPrefixedPath checks if the given string is a domain-prefixed path
// (e.g. acme.io/foo). All characters before the first "/" must be a valid
// subdomain as defined by RFC 1123. All characters trailing the first "/" must
// be valid HTTP Path characters as defined by RFC 3986.
func IsDomainPrefixedPath(fldPath *field.Path, dpPath string) field.ErrorList {
var allErrs field.ErrorList
if len(dpPath) == 0 {
return append(allErrs, field.Required(fldPath, ""))
}
segments := strings.SplitN(dpPath, "/", 2)
if len(segments) != 2 || len(segments[0]) == 0 || len(segments[1]) == 0 {
return append(allErrs, field.Invalid(fldPath, dpPath, "must be a domain-prefixed path (such as \"acme.io/foo\")"))
}
host := segments[0]
for _, err := range IsDNS1123Subdomain(host) {
allErrs = append(allErrs, field.Invalid(fldPath, host, err))
}
path := segments[1]
if !httpPathRegexp.MatchString(path) {
return append(allErrs, field.Invalid(fldPath, path, RegexError("Invalid path", httpPathFmt)))
}
return allErrs
}
const labelValueFmt string = "(" + qualifiedNameFmt + ")?"
const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
// LabelValueMaxLength is a label's max length
const LabelValueMaxLength int = 63
var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
// IsValidLabelValue tests whether the value passed is a valid label value. If
// the value is not valid, a list of error strings is returned. Otherwise an
// empty list (or nil) is returned.
func IsValidLabelValue(value string) []string {
var errs []string
if len(value) > LabelValueMaxLength {
errs = append(errs, MaxLenError(LabelValueMaxLength))
}
if !labelValueRegexp.MatchString(value) {
errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
}
return errs
}
const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
const dns1123LabelErrMsg string = "a DNS-1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
// DNS1123LabelMaxLength is a label's max length in DNS (RFC 1123)
const DNS1123LabelMaxLength int = 63
var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
// IsDNS1123Label tests for a string that conforms to the definition of a label in
// DNS (RFC 1123).
func IsDNS1123Label(value string) []string {
var errs []string
if len(value) > DNS1123LabelMaxLength {
errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
}
if !dns1123LabelRegexp.MatchString(value) {
errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
}
return errs
}
const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
const dns1123SubdomainErrorMsg string = "a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
// DNS1123SubdomainMaxLength is a subdomain's max length in DNS (RFC 1123)
const DNS1123SubdomainMaxLength int = 253
var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
// IsDNS1123Subdomain tests for a string that conforms to the definition of a
// subdomain in DNS (RFC 1123).
func IsDNS1123Subdomain(value string) []string {
var errs []string
if len(value) > DNS1123SubdomainMaxLength {
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
}
if !dns1123SubdomainRegexp.MatchString(value) {
errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
}
return errs
}
const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"
// DNS1035LabelMaxLength is a label's max length in DNS (RFC 1035)
const DNS1035LabelMaxLength int = 63
var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
// IsDNS1035Label tests for a string that conforms to the definition of a label in
// DNS (RFC 1035).
func IsDNS1035Label(value string) []string {
var errs []string
if len(value) > DNS1035LabelMaxLength {
errs = append(errs, MaxLenError(DNS1035LabelMaxLength))
}
if !dns1035LabelRegexp.MatchString(value) {
errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
}
return errs
}
// wildcard definition - RFC 1034 section 4.3.3.
// examples:
// - valid: *.bar.com, *.foo.bar.com
// - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, *
const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt
const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character"
// IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a
// wildcard subdomain in DNS (RFC 1034 section 4.3.3).
func IsWildcardDNS1123Subdomain(value string) []string {
wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$")
var errs []string
if len(value) > DNS1123SubdomainMaxLength {
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
}
if !wildcardDNS1123SubdomainRegexp.MatchString(value) {
errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com"))
}
return errs
}
const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*"
const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'"
var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$")
// IsCIdentifier tests for a string that conforms the definition of an identifier
// in C. This checks the format, but not the length.
func IsCIdentifier(value string) []string {
if !cIdentifierRegexp.MatchString(value) {
return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")}
}
return nil
}
// IsValidPortNum tests that the argument is a valid, non-zero port number.
func IsValidPortNum(port int) []string {
if 1 <= port && port <= 65535 {
return nil
}
return []string{InclusiveRangeError(1, 65535)}
}
// IsInRange tests that the argument is in an inclusive range.
func IsInRange(value int, min int, max int) []string {
if value >= min && value <= max {
return nil
}
return []string{InclusiveRangeError(min, max)}
}
// Now in libcontainer UID/GID limits is 0 ~ 1<<31 - 1
// TODO: once we have a type for UID/GID we should make these that type.
const (
minUserID = 0
maxUserID = math.MaxInt32
minGroupID = 0
maxGroupID = math.MaxInt32
)
// IsValidGroupID tests that the argument is a valid Unix GID.
func IsValidGroupID(gid int64) []string {
if minGroupID <= gid && gid <= maxGroupID {
return nil
}
return []string{InclusiveRangeError(minGroupID, maxGroupID)}
}
// IsValidUserID tests that the argument is a valid Unix UID.
func IsValidUserID(uid int64) []string {
if minUserID <= uid && uid <= maxUserID {
return nil
}
return []string{InclusiveRangeError(minUserID, maxUserID)}
}
var portNameCharsetRegex = regexp.MustCompile("^[-a-z0-9]+$")
var portNameOneLetterRegexp = regexp.MustCompile("[a-z]")
// IsValidPortName check that the argument is valid syntax. It must be
// non-empty and no more than 15 characters long. It may contain only [-a-z0-9]
// and must contain at least one letter [a-z]. It must not start or end with a
// hyphen, nor contain adjacent hyphens.
//
// Note: We only allow lower-case characters, even though RFC 6335 is case
// insensitive.
func IsValidPortName(port string) []string {
var errs []string
if len(port) > 15 {
errs = append(errs, MaxLenError(15))
}
if !portNameCharsetRegex.MatchString(port) {
errs = append(errs, "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)")
}
if !portNameOneLetterRegexp.MatchString(port) {
errs = append(errs, "must contain at least one letter or number (a-z, 0-9)")
}
if strings.Contains(port, "--") {
errs = append(errs, "must not contain consecutive hyphens")
}
if len(port) > 0 && (port[0] == '-' || port[len(port)-1] == '-') {
errs = append(errs, "must not begin or end with a hyphen")
}
return errs
}
// IsValidIP tests that the argument is a valid IP address.
func IsValidIP(value string) []string {
if net.ParseIP(value) == nil {
return []string{"must be a valid IP address, (e.g. 10.9.8.7)"}
}
return nil
}
// IsValidIPv4Address tests that the argument is a valid IPv4 address.
func IsValidIPv4Address(fldPath *field.Path, value string) field.ErrorList {
var allErrors field.ErrorList
ip := net.ParseIP(value)
if ip == nil || ip.To4() == nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv4 address"))
}
return allErrors
}
// IsValidIPv6Address tests that the argument is a valid IPv6 address.
func IsValidIPv6Address(fldPath *field.Path, value string) field.ErrorList {
var allErrors field.ErrorList
ip := net.ParseIP(value)
if ip == nil || ip.To4() != nil {
allErrors = append(allErrors, field.Invalid(fldPath, value, "must be a valid IPv6 address"))
}
return allErrors
}
const percentFmt string = "[0-9]+%"
const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'"
var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
// IsValidPercent checks that string is in the form of a percentage
func IsValidPercent(percent string) []string {
if !percentRegexp.MatchString(percent) {
return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")}
}
return nil
}
const httpHeaderNameFmt string = "[-A-Za-z0-9]+"
const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'"
var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$")
// IsHTTPHeaderName checks that a string conforms to the Go HTTP library's
// definition of a valid header field name (a stricter subset than RFC7230).
func IsHTTPHeaderName(value string) []string {
if !httpHeaderNameRegexp.MatchString(value) {
return []string{RegexError(httpHeaderNameErrMsg, httpHeaderNameFmt, "X-Header-Name")}
}
return nil
}
const envVarNameFmt = "[-._a-zA-Z][-._a-zA-Z0-9]*"
const envVarNameFmtErrMsg string = "a valid environment variable name must consist of alphabetic characters, digits, '_', '-', or '.', and must not start with a digit"
var envVarNameRegexp = regexp.MustCompile("^" + envVarNameFmt + "$")
// IsEnvVarName tests if a string is a valid environment variable name.
func IsEnvVarName(value string) []string {
var errs []string
if !envVarNameRegexp.MatchString(value) {
errs = append(errs, RegexError(envVarNameFmtErrMsg, envVarNameFmt, "my.env-name", "MY_ENV.NAME", "MyEnvName1"))
}
errs = append(errs, hasChDirPrefix(value)...)
return errs
}
const configMapKeyFmt = `[-._a-zA-Z0-9]+`
const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'"
var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$")
// IsConfigMapKey tests for a string that is a valid key for a ConfigMap or Secret
func IsConfigMapKey(value string) []string {
var errs []string
if len(value) > DNS1123SubdomainMaxLength {
errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
}
if !configMapKeyRegexp.MatchString(value) {
errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name"))
}
errs = append(errs, hasChDirPrefix(value)...)
return errs
}
// MaxLenError returns a string explanation of a "string too long" validation
// failure.
func MaxLenError(length int) string {
return fmt.Sprintf("must be no more than %d characters", length)
}
// RegexError returns a string explanation of a regex validation failure.
func RegexError(msg string, fmt string, examples ...string) string {
if len(examples) == 0 {
return msg + " (regex used for validation is '" + fmt + "')"
}
msg += " (e.g. "
for i := range examples {
if i > 0 {
msg += " or "
}
msg += "'" + examples[i] + "', "
}
msg += "regex used for validation is '" + fmt + "')"
return msg
}
// EmptyError returns a string explanation of a "must not be empty" validation
// failure.
func EmptyError() string {
return "must be non-empty"
}
func prefixEach(msgs []string, prefix string) []string {
for i := range msgs {
msgs[i] = prefix + msgs[i]
}
return msgs
}
// InclusiveRangeError returns a string explanation of a numeric "must be
// between" validation failure.
func InclusiveRangeError(lo, hi int) string {
return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi)
}
func hasChDirPrefix(value string) []string {
var errs []string
switch {
case value == ".":
errs = append(errs, `must not be '.'`)
case value == "..":
errs = append(errs, `must not be '..'`)
case strings.HasPrefix(value, ".."):
errs = append(errs, `must not start with '..'`)
}
return errs
}
// IsValidSocketAddr checks that string represents a valid socket address
// as defined in RFC 789. (e.g 0.0.0.0:10254 or [::]:10254))
func IsValidSocketAddr(value string) []string {
var errs []string
ip, port, err := net.SplitHostPort(value)
if err != nil {
errs = append(errs, "must be a valid socket address format, (e.g. 0.0.0.0:10254 or [::]:10254)")
return errs
}
portInt, _ := strconv.Atoi(port)
errs = append(errs, IsValidPortNum(portInt)...)
errs = append(errs, IsValidIP(ip)...)
return errs
}

137
vendor/sigs.k8s.io/kustomize/kyaml/yaml/kfns.go generated vendored Normal file
View File

@@ -0,0 +1,137 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"gopkg.in/yaml.v3"
"sigs.k8s.io/kustomize/kyaml/errors"
)
// AnnotationClearer removes an annotation at metadata.annotations.
// Returns nil if the annotation or field does not exist.
type AnnotationClearer struct {
Kind string `yaml:"kind,omitempty"`
Key string `yaml:"key,omitempty"`
}
func (c AnnotationClearer) Filter(rn *RNode) (*RNode, error) {
return rn.Pipe(
PathGetter{Path: []string{MetadataField, AnnotationsField}},
FieldClearer{Name: c.Key})
}
func ClearAnnotation(key string) AnnotationClearer {
return AnnotationClearer{Key: key}
}
// ClearEmptyAnnotations clears the keys, annotations
// and metadata if they are empty/null
func ClearEmptyAnnotations(rn *RNode) error {
_, err := rn.Pipe(Lookup(MetadataField), FieldClearer{
Name: AnnotationsField, IfEmpty: true})
if err != nil {
return errors.Wrap(err)
}
_, err = rn.Pipe(FieldClearer{Name: MetadataField, IfEmpty: true})
if err != nil {
return errors.Wrap(err)
}
return nil
}
// k8sMetaSetter sets a name at metadata.{key}.
// Creates metadata if does not exist.
type k8sMetaSetter struct {
Key string `yaml:"key,omitempty"`
Value string `yaml:"value,omitempty"`
}
func (s k8sMetaSetter) Filter(rn *RNode) (*RNode, error) {
_, err := rn.Pipe(
PathGetter{Path: []string{MetadataField}, Create: yaml.MappingNode},
FieldSetter{Name: s.Key, Value: NewStringRNode(s.Value)})
return rn, err
}
func SetK8sName(value string) k8sMetaSetter {
return k8sMetaSetter{Key: NameField, Value: value}
}
func SetK8sNamespace(value string) k8sMetaSetter {
return k8sMetaSetter{Key: NamespaceField, Value: value}
}
// AnnotationSetter sets an annotation at metadata.annotations.
// Creates metadata.annotations if does not exist.
type AnnotationSetter struct {
Kind string `yaml:"kind,omitempty"`
Key string `yaml:"key,omitempty"`
Value string `yaml:"value,omitempty"`
}
func (s AnnotationSetter) Filter(rn *RNode) (*RNode, error) {
v := NewStringRNode(s.Value)
// some tools get confused about the type if annotations are not quoted
v.YNode().Style = yaml.SingleQuotedStyle
if err := ClearEmptyAnnotations(rn); err != nil {
return nil, err
}
return addMetadataNode(rn, AnnotationsField, s.Key, v)
}
func SetAnnotation(key, value string) AnnotationSetter {
return AnnotationSetter{Key: key, Value: value}
}
// AnnotationGetter gets an annotation at metadata.annotations.
// Returns nil if metadata.annotations does not exist.
type AnnotationGetter struct {
Kind string `yaml:"kind,omitempty"`
Key string `yaml:"key,omitempty"`
Value string `yaml:"value,omitempty"`
}
// AnnotationGetter returns the annotation value.
// Returns "", nil if the annotation does not exist.
func (g AnnotationGetter) Filter(rn *RNode) (*RNode, error) {
v, err := rn.Pipe(
PathGetter{Path: []string{MetadataField, AnnotationsField, g.Key}})
if v == nil || err != nil {
return v, err
}
if g.Value == "" || v.value.Value == g.Value {
return v, err
}
return nil, err
}
func GetAnnotation(key string) AnnotationGetter {
return AnnotationGetter{Key: key}
}
// LabelSetter sets a label at metadata.labels.
// Creates metadata.labels if does not exist.
type LabelSetter struct {
Kind string `yaml:"kind,omitempty"`
Key string `yaml:"key,omitempty"`
Value string `yaml:"value,omitempty"`
}
func (s LabelSetter) Filter(rn *RNode) (*RNode, error) {
v := NewStringRNode(s.Value)
// some tools get confused about the type if labels are not quoted
v.YNode().Style = yaml.SingleQuotedStyle
return addMetadataNode(rn, LabelsField, s.Key, v)
}
func addMetadataNode(rn *RNode, field, key string, v *RNode) (*RNode, error) {
return rn.Pipe(
PathGetter{
Path: []string{MetadataField, field}, Create: yaml.MappingNode},
FieldSetter{Name: key, Value: v})
}
func SetLabel(key, value string) LabelSetter {
return LabelSetter{Key: key, Value: value}
}

40
vendor/sigs.k8s.io/kustomize/kyaml/yaml/mapnode.go generated vendored Normal file
View File

@@ -0,0 +1,40 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
// MapNode wraps a field key and value.
type MapNode struct {
Key *RNode
Value *RNode
}
// IsNilOrEmpty returns true if the MapNode is nil,
// has no value, or has a value that appears empty.
func (mn *MapNode) IsNilOrEmpty() bool {
return mn == nil || mn.Value.IsNilOrEmpty()
}
type MapNodeSlice []*MapNode
func (m MapNodeSlice) Keys() []*RNode {
var keys []*RNode
for i := range m {
if m[i] != nil {
keys = append(keys, m[i].Key)
}
}
return keys
}
func (m MapNodeSlice) Values() []*RNode {
var values []*RNode
for i := range m {
if m[i] != nil {
values = append(values, m[i].Value)
} else {
values = append(values, nil)
}
}
return values
}

206
vendor/sigs.k8s.io/kustomize/kyaml/yaml/match.go generated vendored Normal file
View File

@@ -0,0 +1,206 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"regexp"
"strings"
)
// PathMatcher returns all RNodes matching the path wrapped in a SequenceNode.
// Lists may have multiple elements matching the path, and each matching element
// is added to the return result.
// If Path points to a SequenceNode, the SequenceNode is wrapped in another SequenceNode
// If Path does not contain any lists, the result is still wrapped in a SequenceNode of len == 1
type PathMatcher struct {
Kind string `yaml:"kind,omitempty"`
// Path is a slice of parts leading to the RNode to lookup.
// Each path part may be one of:
// * FieldMatcher -- e.g. "spec"
// * Map Key -- e.g. "app.k8s.io/version"
// * List Entry -- e.g. "[name=nginx]" or "[=-jar]"
//
// Map Keys and Fields are equivalent.
// See FieldMatcher for more on Fields and Map Keys.
//
// List Entries are specified as map entry to match [fieldName=fieldValue].
// See Elem for more on List Entries.
//
// Examples:
// * spec.template.spec.container with matching name: [name=nginx] -- match 'name': 'nginx'
// * spec.template.spec.container.argument matching a value: [=-jar] -- match '-jar'
Path []string `yaml:"path,omitempty"`
// Matches is set by PathMatch to publish the matched element values for each node.
// After running PathMatcher.Filter, each node from the SequenceNode result may be
// looked up in Matches to find the field values that were matched.
Matches map[*Node][]string
// StripComments may be set to remove the comments on the matching Nodes.
// This is useful for if the nodes are to be printed in FlowStyle.
StripComments bool
val *RNode
field string
matchRegex string
}
func (p *PathMatcher) stripComments(n *Node) {
if n == nil {
return
}
if p.StripComments {
n.LineComment = ""
n.HeadComment = ""
n.FootComment = ""
for i := range n.Content {
p.stripComments(n.Content[i])
}
}
}
func (p *PathMatcher) Filter(rn *RNode) (*RNode, error) {
val, err := p.filter(rn)
if err != nil {
return nil, err
}
p.stripComments(val.YNode())
return val, err
}
func (p *PathMatcher) filter(rn *RNode) (*RNode, error) {
p.Matches = map[*Node][]string{}
if len(p.Path) == 0 {
// return the element wrapped in a SequenceNode
p.appendRNode("", rn)
return p.val, nil
}
if IsListIndex(p.Path[0]) {
// match seq elements
return p.doSeq(rn)
}
// match a field
return p.doField(rn)
}
func (p *PathMatcher) doField(rn *RNode) (*RNode, error) {
// lookup the field
field, err := rn.Pipe(Get(p.Path[0]))
if err != nil || field == nil {
// if the field doesn't exist, return nil
return nil, err
}
// recurse on the field, removing the first element of the path
pm := &PathMatcher{Path: p.Path[1:]}
p.val, err = pm.filter(field)
p.Matches = pm.Matches
return p.val, err
}
// doSeq iterates over a sequence and appends elements matching the path regex to p.Val
func (p *PathMatcher) doSeq(rn *RNode) (*RNode, error) {
// parse the field + match pair
var err error
p.field, p.matchRegex, err = SplitIndexNameValue(p.Path[0])
if err != nil {
return nil, err
}
if p.field == "" {
err = rn.VisitElements(p.visitPrimitiveElem)
} else {
err = rn.VisitElements(p.visitElem)
}
if err != nil || p.val == nil || len(p.val.YNode().Content) == 0 {
return nil, err
}
return p.val, nil
}
func (p *PathMatcher) visitPrimitiveElem(elem *RNode) error {
r, err := regexp.Compile(p.matchRegex)
if err != nil {
return err
}
str, err := elem.String()
if err != nil {
return err
}
str = strings.TrimSpace(str)
if !r.MatchString(str) {
return nil
}
p.appendRNode("", elem)
return nil
}
func (p *PathMatcher) visitElem(elem *RNode) error {
r, err := regexp.Compile(p.matchRegex)
if err != nil {
return err
}
// check if this elements field matches the regex
val := elem.Field(p.field)
if val == nil || val.Value == nil {
return nil
}
str, err := val.Value.String()
if err != nil {
return err
}
str = strings.TrimSpace(str)
if !r.MatchString(str) {
return nil
}
// recurse on the matching element
pm := &PathMatcher{Path: p.Path[1:]}
add, err := pm.filter(elem)
for k, v := range pm.Matches {
p.Matches[k] = v
}
if err != nil || add == nil {
return err
}
p.append(str, add.Content()...)
return nil
}
func (p *PathMatcher) appendRNode(path string, node *RNode) {
p.append(path, node.YNode())
}
func (p *PathMatcher) append(path string, nodes ...*Node) {
if p.val == nil {
p.val = NewRNode(&Node{Kind: SequenceNode})
}
for i := range nodes {
node := nodes[i]
p.val.YNode().Content = append(p.val.YNode().Content, node)
// record the path if specified
if path != "" {
p.Matches[node] = append(p.Matches[node], path)
}
}
}
func cleanPath(path []string) []string {
var p []string
for _, elem := range path {
elem = strings.TrimSpace(elem)
if len(elem) == 0 {
continue
}
p = append(p, elem)
}
return p
}

107
vendor/sigs.k8s.io/kustomize/kyaml/yaml/order.go generated vendored Normal file
View File

@@ -0,0 +1,107 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
// fieldSortOrder contains the relative ordering of fields when formatting an
// object.
var fieldSortOrder = []string{
// top-level metadata
"name", "generateName", "namespace", "clusterName",
"apiVersion", "kind", "metadata", "type",
"labels", "annotations",
"spec", "status",
// secret and configmap
"stringData", "data", "binaryData",
// cronjobspec, daemonsetspec, deploymentspec, statefulsetspec,
// jobspec fields
"parallelism", "completions", "activeDeadlineSeconds", "backoffLimit",
"replicas", "selector", "manualSelector", "template",
"ttlSecondsAfterFinished", "volumeClaimTemplates", "service", "serviceName",
"podManagementPolicy", "updateStrategy", "strategy", "minReadySeconds",
"revision", "revisionHistoryLimit", "paused", "progressDeadlineSeconds",
// podspec
// podspec scalars
"restartPolicy", "terminationGracePeriodSeconds",
"activeDeadlineSeconds", "dnsPolicy", "serviceAccountName",
"serviceAccount", "automountServiceAccountToken", "nodeName",
"hostNetwork", "hostPID", "hostIPC", "shareProcessNamespace", "hostname",
"subdomain", "schedulerName", "priorityClassName", "priority",
"runtimeClassName", "enableServiceLinks",
// podspec lists and maps
"nodeSelector", "hostAliases",
// podspec objects
"initContainers", "containers", "volumes", "securityContext",
"imagePullSecrets", "affinity", "tolerations", "dnsConfig",
"readinessGates",
// containers
"image", "command", "args", "workingDir", "ports", "envFrom", "env",
"resources", "volumeMounts", "volumeDevices", "livenessProbe",
"readinessProbe", "lifecycle", "terminationMessagePath",
"terminationMessagePolicy", "imagePullPolicy", "securityContext",
"stdin", "stdinOnce", "tty",
// service
"clusterIP", "externalIPs", "loadBalancerIP", "loadBalancerSourceRanges",
"externalName", "externalTrafficPolicy", "sessionAffinity",
// ports
"protocol", "port", "targetPort", "hostPort", "containerPort", "hostIP",
// volumemount
"readOnly", "mountPath", "subPath", "subPathExpr", "mountPropagation",
// envvar + envvarsource
"value", "valueFrom", "fieldRef", "resourceFieldRef", "configMapKeyRef",
"secretKeyRef", "prefix", "configMapRef", "secretRef",
}
type set map[string]interface{}
func newSet(values ...string) set {
m := map[string]interface{}{}
for _, value := range values {
m[value] = nil
}
return m
}
func (s set) Has(key string) bool {
_, found := s[key]
return found
}
// WhitelistedListSortKinds contains the set of kinds that are whitelisted
// for sorting list field elements
var WhitelistedListSortKinds = newSet(
"CronJob", "DaemonSet", "Deployment", "Job", "ReplicaSet", "StatefulSet",
"ValidatingWebhookConfiguration")
// WhitelistedListSortApis contains the set of apis that are whitelisted for
// sorting list field elements
var WhitelistedListSortApis = newSet(
"apps/v1", "apps/v1beta1", "apps/v1beta2", "batch/v1", "batch/v1beta1",
"extensions/v1beta1", "v1", "admissionregistration.k8s.io/v1")
// WhitelistedListSortFields contains json paths to list fields that should
// be sorted, and the field they should be sorted by
var WhitelistedListSortFields = map[string]string{
".spec.template.spec.containers": "name",
".webhooks.rules.operations": "",
}
// FieldOrder indexes fields and maps them to relative precedence
var FieldOrder = func() map[string]int {
// create an index of field orderings
fo := map[string]int{}
for i, f := range fieldSortOrder {
fo[f] = i + 1
}
return fo
}()

1008
vendor/sigs.k8s.io/kustomize/kyaml/yaml/rnode.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import "gopkg.in/yaml.v3"
func DoSerializationHacksOnNodes(nodes []*RNode) {
for _, node := range nodes {
DoSerializationHacks(node.YNode())
}
}
// DoSerializationHacks addresses a bug in yaml V3 upstream, it parses the yaml node,
// and rearranges the head comments of the children of sequence node.
// Refer to https://github.com/go-yaml/yaml/issues/587 for more details
func DoSerializationHacks(node *yaml.Node) {
switch node.Kind {
case DocumentNode:
for _, node := range node.Content {
DoSerializationHacks(node)
}
case MappingNode:
for _, node := range node.Content {
DoSerializationHacks(node)
}
case SequenceNode:
for _, node := range node.Content {
// for each child mapping node, transfer the head comment of it's
// first child scalar node to the head comment of itself
// This is necessary to address serialization issue
// https://github.com/go-yaml/yaml/issues/587 in go-yaml.v3
// Remove this hack when the issue has been resolved
if len(node.Content) > 0 && node.Content[0].Kind == ScalarNode {
node.HeadComment = node.Content[0].HeadComment
node.Content[0].HeadComment = ""
}
}
}
}
func UndoSerializationHacksOnNodes(nodes []*RNode) {
for _, node := range nodes {
UndoSerializationHacks(node.YNode())
}
}
// UndoSerializationHacks reverts the changes made by DoSerializationHacks
// Refer to https://github.com/go-yaml/yaml/issues/587 for more details
func UndoSerializationHacks(node *yaml.Node) {
switch node.Kind {
case DocumentNode:
for _, node := range node.Content {
DoSerializationHacks(node)
}
case MappingNode:
for _, node := range node.Content {
DoSerializationHacks(node)
}
case SequenceNode:
for _, node := range node.Content {
// revert the changes made in DoSerializationHacks
// This is necessary to address serialization issue
// https://github.com/go-yaml/yaml/issues/587 in go-yaml.v3
// Remove this hack when the issue has been resolved
if len(node.Content) > 0 && node.Content[0].Kind == ScalarNode {
node.Content[0].HeadComment = node.HeadComment
node.HeadComment = ""
}
}
}
}

237
vendor/sigs.k8s.io/kustomize/kyaml/yaml/types.go generated vendored Normal file
View File

@@ -0,0 +1,237 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package yaml
import (
"bytes"
"strings"
"gopkg.in/yaml.v3"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/sets"
)
// CopyYNode returns a distinct copy of its argument.
// Use https://github.com/jinzhu/copier instead?
func CopyYNode(n *yaml.Node) *yaml.Node {
if n == nil {
return nil
}
c := *n
if len(n.Content) > 0 {
// Using Go 'copy' here doesn't yield independent slices.
c.Content = make([]*Node, len(n.Content))
for i, item := range n.Content {
c.Content[i] = CopyYNode(item)
}
}
return &c
}
// IsYNodeTaggedNull returns true if the node is explicitly tagged Null.
func IsYNodeTaggedNull(n *yaml.Node) bool {
return n != nil && n.Tag == NodeTagNull
}
// IsYNodeEmptyMap is true if the Node is a non-nil empty map.
func IsYNodeEmptyMap(n *yaml.Node) bool {
return n != nil && n.Kind == yaml.MappingNode && len(n.Content) == 0
}
// IsYNodeEmptyMap is true if the Node is a non-nil empty sequence.
func IsYNodeEmptySeq(n *yaml.Node) bool {
return n != nil && n.Kind == yaml.SequenceNode && len(n.Content) == 0
}
// IsYNodeEmptyDoc is true if the node is a Document with no content.
// E.g.: "---\n---"
func IsYNodeEmptyDoc(n *yaml.Node) bool {
return n.Kind == yaml.DocumentNode && n.Content[0].Tag == NodeTagNull
}
func IsYNodeString(n *yaml.Node) bool {
return n.Kind == yaml.ScalarNode &&
(n.Tag == NodeTagString || n.Tag == NodeTagEmpty)
}
// IsYNodeZero is true if all the public fields in the Node are empty.
// Which means it's not initialized and should be omitted when marshal.
// The Node itself has a method IsZero but it is not released
// in yaml.v3. https://pkg.go.dev/gopkg.in/yaml.v3#Node.IsZero
func IsYNodeZero(n *yaml.Node) bool {
// TODO: Change this to use IsZero when it's avaialable.
return n != nil && n.Kind == 0 && n.Style == 0 && n.Tag == "" && n.Value == "" &&
n.Anchor == "" && n.Alias == nil && n.Content == nil &&
n.HeadComment == "" && n.LineComment == "" && n.FootComment == "" &&
n.Line == 0 && n.Column == 0
}
// Parser parses values into configuration.
type Parser struct {
Kind string `yaml:"kind,omitempty"`
Value string `yaml:"value,omitempty"`
}
func (p Parser) Filter(_ *RNode) (*RNode, error) {
d := yaml.NewDecoder(bytes.NewBuffer([]byte(p.Value)))
o := &RNode{value: &yaml.Node{}}
return o, d.Decode(o.value)
}
// TODO(pwittrock): test this
func GetStyle(styles ...string) Style {
var style Style
for _, s := range styles {
switch s {
case "TaggedStyle":
style |= TaggedStyle
case "DoubleQuotedStyle":
style |= DoubleQuotedStyle
case "SingleQuotedStyle":
style |= SingleQuotedStyle
case "LiteralStyle":
style |= LiteralStyle
case "FoldedStyle":
style |= FoldedStyle
case "FlowStyle":
style |= FlowStyle
}
}
return style
}
// Filter defines a function to manipulate an individual RNode such as by changing
// its values, or returning a field.
//
// When possible, Filters should be serializable to yaml so that they can be described
// declaratively as data.
//
// Analogous to http://www.linfo.org/filters.html
type Filter interface {
Filter(object *RNode) (*RNode, error)
}
type FilterFunc func(object *RNode) (*RNode, error)
func (f FilterFunc) Filter(object *RNode) (*RNode, error) {
return f(object)
}
// TypeMeta partially copies apimachinery/pkg/apis/meta/v1.TypeMeta
// No need for a direct dependence; the fields are stable.
type TypeMeta struct {
// APIVersion is the apiVersion field of a Resource
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
// Kind is the kind field of a Resource
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}
// NameMeta contains name information.
type NameMeta struct {
// Name is the metadata.name field of a Resource
Name string `json:"name,omitempty" yaml:"name,omitempty"`
// Namespace is the metadata.namespace field of a Resource
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
}
// ResourceMeta contains the metadata for a both Resource Type and Resource.
type ResourceMeta struct {
TypeMeta `json:",inline" yaml:",inline"`
// ObjectMeta is the metadata field of a Resource
ObjectMeta `yaml:"metadata,omitempty"`
}
// ObjectMeta contains metadata about a Resource
type ObjectMeta struct {
NameMeta `json:",inline" yaml:",inline"`
// Labels is the metadata.labels field of a Resource
Labels map[string]string `yaml:"labels,omitempty"`
// Annotations is the metadata.annotations field of a Resource.
Annotations map[string]string `yaml:"annotations,omitempty"`
}
// GetIdentifier returns a ResourceIdentifier that includes
// the information needed to uniquely identify a resource in a cluster.
func (m *ResourceMeta) GetIdentifier() ResourceIdentifier {
return ResourceIdentifier{
TypeMeta: m.TypeMeta,
NameMeta: m.NameMeta,
}
}
// ResourceIdentifier contains the information needed to uniquely
// identify a resource in a cluster.
type ResourceIdentifier struct {
TypeMeta `json:",inline" yaml:",inline"`
NameMeta `json:",inline" yaml:",inline"`
}
// Comments struct is comment yaml comment types
type Comments struct {
LineComment string `yaml:"lineComment,omitempty"`
HeadComment string `yaml:"headComment,omitempty"`
FootComment string `yaml:"footComment,omitempty"`
}
func (r *ResourceIdentifier) GetName() string {
return r.Name
}
func (r *ResourceIdentifier) GetNamespace() string {
return r.Namespace
}
func (r *ResourceIdentifier) GetAPIVersion() string {
return r.APIVersion
}
func (r *ResourceIdentifier) GetKind() string {
return r.Kind
}
const (
Trim = "Trim"
Flow = "Flow"
)
// String returns a string value for a Node, applying the supplied formatting options
func String(node *yaml.Node, opts ...string) (string, error) {
if node == nil {
return "", nil
}
optsSet := sets.String{}
optsSet.Insert(opts...)
if optsSet.Has(Flow) {
oldStyle := node.Style
defer func() {
node.Style = oldStyle
}()
node.Style = yaml.FlowStyle
}
b := &bytes.Buffer{}
e := NewEncoder(b)
err := e.Encode(node)
e.Close()
val := b.String()
if optsSet.Has(Trim) {
val = strings.TrimSpace(val)
}
return val, errors.Wrap(err)
}
// MergeOptionsListIncreaseDirection is the type of list growth in merge
type MergeOptionsListIncreaseDirection int
const (
MergeOptionsListAppend MergeOptionsListIncreaseDirection = iota
MergeOptionsListPrepend
)
// MergeOptions is a struct which contains the options for merge
type MergeOptions struct {
// ListIncreaseDirection indicates should merge function prepend the items from
// source list to destination or append.
ListIncreaseDirection MergeOptionsListIncreaseDirection
}