update vendor

Signed-off-by: Roland.Ma <rolandma@yunify.com>
This commit is contained in:
Roland.Ma
2021-08-11 07:10:14 +00:00
parent a18f72b565
commit ea8f47c73a
2901 changed files with 269317 additions and 43103 deletions

248
vendor/sigs.k8s.io/kustomize/kyaml/kio/byteio_reader.go generated vendored Normal file
View File

@@ -0,0 +1,248 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kio
import (
"bytes"
"fmt"
"io"
"sort"
"strings"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
const (
ResourceListKind = "ResourceList"
ResourceListAPIVersion = "config.kubernetes.io/v1alpha1"
)
// ByteReadWriter reads from an input and writes to an output.
type ByteReadWriter struct {
// Reader is where ResourceNodes are decoded from.
Reader io.Reader
// Writer is where ResourceNodes are encoded.
Writer io.Writer
// OmitReaderAnnotations will configures Read to skip setting the config.kubernetes.io/index
// annotation on Resources as they are Read.
OmitReaderAnnotations bool
// KeepReaderAnnotations if set will keep the Reader specific annotations when writing
// the Resources, otherwise they will be cleared.
KeepReaderAnnotations bool
// Style is a style that is set on the Resource Node Document.
Style yaml.Style
FunctionConfig *yaml.RNode
Results *yaml.RNode
NoWrap bool
WrappingAPIVersion string
WrappingKind string
}
func (rw *ByteReadWriter) Read() ([]*yaml.RNode, error) {
b := &ByteReader{
Reader: rw.Reader,
OmitReaderAnnotations: rw.OmitReaderAnnotations,
}
val, err := b.Read()
if rw.FunctionConfig == nil {
rw.FunctionConfig = b.FunctionConfig
}
rw.Results = b.Results
if !rw.NoWrap {
rw.WrappingAPIVersion = b.WrappingAPIVersion
rw.WrappingKind = b.WrappingKind
}
return val, errors.Wrap(err)
}
func (rw *ByteReadWriter) Write(nodes []*yaml.RNode) error {
return ByteWriter{
Writer: rw.Writer,
KeepReaderAnnotations: rw.KeepReaderAnnotations,
Style: rw.Style,
FunctionConfig: rw.FunctionConfig,
Results: rw.Results,
WrappingAPIVersion: rw.WrappingAPIVersion,
WrappingKind: rw.WrappingKind,
}.Write(nodes)
}
// ParseAll reads all of the inputs into resources
func ParseAll(inputs ...string) ([]*yaml.RNode, error) {
return (&ByteReader{
Reader: bytes.NewBufferString(strings.Join(inputs, "\n---\n")),
}).Read()
}
// FromBytes reads from a byte slice.
func FromBytes(bs []byte) ([]*yaml.RNode, error) {
return (&ByteReader{
OmitReaderAnnotations: true,
Reader: bytes.NewBuffer(bs),
}).Read()
}
// StringAll writes all of the resources to a string
func StringAll(resources []*yaml.RNode) (string, error) {
var b bytes.Buffer
err := (&ByteWriter{Writer: &b}).Write(resources)
return b.String(), err
}
// ByteReader decodes ResourceNodes from bytes.
// By default, Read will set the config.kubernetes.io/index annotation on each RNode as it
// is read so they can be written back in the same order.
type ByteReader struct {
// Reader is where ResourceNodes are decoded from.
Reader io.Reader
// OmitReaderAnnotations will configures Read to skip setting the config.kubernetes.io/index
// annotation on Resources as they are Read.
OmitReaderAnnotations bool
// SetAnnotations is a map of caller specified annotations to set on resources as they are read
// These are independent of the annotations controlled by OmitReaderAnnotations
SetAnnotations map[string]string
FunctionConfig *yaml.RNode
Results *yaml.RNode
// DisableUnwrapping prevents Resources in Lists and ResourceLists from being unwrapped
DisableUnwrapping bool
// WrappingAPIVersion is set by Read(), and is the apiVersion of the object that
// the read objects were originally wrapped in.
WrappingAPIVersion string
// WrappingKind is set by Read(), and is the kind of the object that
// the read objects were originally wrapped in.
WrappingKind string
}
var _ Reader = &ByteReader{}
func (r *ByteReader) Read() ([]*yaml.RNode, error) {
output := ResourceNodeSlice{}
// by manually splitting resources -- otherwise the decoder will get the Resource
// boundaries wrong for header comments.
input := &bytes.Buffer{}
_, err := io.Copy(input, r.Reader)
if err != nil {
return nil, errors.Wrap(err)
}
// replace the ending \r\n (line ending used in windows) with \n and then separate by \n---\n
values := strings.Split(strings.ReplaceAll(input.String(), "\r\n", "\n"), "\n---\n")
index := 0
for i := range values {
// the Split used above will eat the tail '\n' from each resource. This may affect the
// literal string value since '\n' is meaningful in it.
if i != len(values)-1 {
values[i] += "\n"
}
decoder := yaml.NewDecoder(bytes.NewBufferString(values[i]))
node, err := r.decode(index, decoder)
if err == io.EOF {
continue
}
if err != nil {
return nil, errors.Wrap(err)
}
if yaml.IsMissingOrNull(node) {
// empty value
continue
}
// ok if no metadata -- assume not an InputList
meta, err := node.GetMeta()
if err != yaml.ErrMissingMetadata && err != nil {
return nil, errors.WrapPrefixf(err, "[%d]", i)
}
// the elements are wrapped in an InputList, unwrap them
// don't check apiVersion, we haven't standardized on the domain
if !r.DisableUnwrapping &&
len(values) == 1 && // Only unwrap if there is only 1 value
(meta.Kind == ResourceListKind || meta.Kind == "List") &&
(node.Field("items") != nil || node.Field("functionConfig") != nil) {
r.WrappingKind = meta.Kind
r.WrappingAPIVersion = meta.APIVersion
// unwrap the list
if fc := node.Field("functionConfig"); fc != nil {
r.FunctionConfig = fc.Value
}
if res := node.Field("results"); res != nil {
r.Results = res.Value
}
items := node.Field("items")
if items != nil {
for i := range items.Value.Content() {
// add items
output = append(output, yaml.NewRNode(items.Value.Content()[i]))
}
}
continue
}
// add the node to the list
output = append(output, node)
// increment the index annotation value
index++
}
return output, nil
}
func (r *ByteReader) decode(index int, decoder *yaml.Decoder) (*yaml.RNode, error) {
node := &yaml.Node{}
err := decoder.Decode(node)
if err == io.EOF {
return nil, io.EOF
}
if err != nil {
return nil, errors.Wrap(err)
}
if yaml.IsYNodeEmptyDoc(node) {
return nil, nil
}
// set annotations on the read Resources
// sort the annotations by key so the output Resources is consistent (otherwise the
// annotations will be in a random order)
n := yaml.NewRNode(node)
if r.SetAnnotations == nil {
r.SetAnnotations = map[string]string{}
}
if !r.OmitReaderAnnotations {
r.SetAnnotations[kioutil.IndexAnnotation] = fmt.Sprintf("%d", index)
}
var keys []string
for k := range r.SetAnnotations {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
_, err = n.Pipe(yaml.SetAnnotation(k, r.SetAnnotations[k]))
if err != nil {
return nil, errors.Wrap(err)
}
}
return yaml.NewRNode(node), nil
}

140
vendor/sigs.k8s.io/kustomize/kyaml/kio/byteio_writer.go generated vendored Normal file
View File

@@ -0,0 +1,140 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kio
import (
"encoding/json"
"io"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Writer writes ResourceNodes to bytes.
type ByteWriter struct {
// Writer is where ResourceNodes are encoded.
Writer io.Writer
// KeepReaderAnnotations if set will keep the Reader specific annotations when writing
// the Resources, otherwise they will be cleared.
KeepReaderAnnotations bool
// ClearAnnotations is a list of annotations to clear when writing the Resources.
ClearAnnotations []string
// Style is a style that is set on the Resource Node Document.
Style yaml.Style
// FunctionConfig is the function config for an ResourceList. If non-nil
// wrap the results in an ResourceList.
FunctionConfig *yaml.RNode
Results *yaml.RNode
// WrappingKind if set will cause ByteWriter to wrap the Resources in
// an 'items' field in this kind. e.g. if WrappingKind is 'List',
// ByteWriter will wrap the Resources in a List .items field.
WrappingKind string
// WrappingAPIVersion is the apiVersion for WrappingKind
WrappingAPIVersion string
// Sort if set, will cause ByteWriter to sort the the nodes before writing them.
Sort bool
}
var _ Writer = ByteWriter{}
func (w ByteWriter) Write(nodes []*yaml.RNode) error {
yaml.DoSerializationHacksOnNodes(nodes)
if w.Sort {
if err := kioutil.SortNodes(nodes); err != nil {
return errors.Wrap(err)
}
}
encoder := yaml.NewEncoder(w.Writer)
defer encoder.Close()
for i := range nodes {
// clean resources by removing annotations set by the Reader
if !w.KeepReaderAnnotations {
_, err := nodes[i].Pipe(yaml.ClearAnnotation(kioutil.IndexAnnotation))
if err != nil {
return errors.Wrap(err)
}
}
for _, a := range w.ClearAnnotations {
_, err := nodes[i].Pipe(yaml.ClearAnnotation(a))
if err != nil {
return errors.Wrap(err)
}
}
if err := yaml.ClearEmptyAnnotations(nodes[i]); err != nil {
return err
}
if w.Style != 0 {
nodes[i].YNode().Style = w.Style
}
}
// don't wrap the elements
if w.WrappingKind == "" {
for i := range nodes {
if err := w.encode(encoder, nodes[i].Document()); err != nil {
return err
}
}
return nil
}
// wrap the elements in a list
items := &yaml.Node{Kind: yaml.SequenceNode}
list := &yaml.Node{
Kind: yaml.MappingNode,
Style: w.Style,
Content: []*yaml.Node{
{Kind: yaml.ScalarNode, Value: "apiVersion"},
{Kind: yaml.ScalarNode, Value: w.WrappingAPIVersion},
{Kind: yaml.ScalarNode, Value: "kind"},
{Kind: yaml.ScalarNode, Value: w.WrappingKind},
{Kind: yaml.ScalarNode, Value: "items"}, items,
}}
if w.FunctionConfig != nil {
list.Content = append(list.Content,
&yaml.Node{Kind: yaml.ScalarNode, Value: "functionConfig"},
w.FunctionConfig.YNode())
}
if w.Results != nil {
list.Content = append(list.Content,
&yaml.Node{Kind: yaml.ScalarNode, Value: "results"},
w.Results.YNode())
}
doc := &yaml.Node{
Kind: yaml.DocumentNode,
Content: []*yaml.Node{list}}
for i := range nodes {
items.Content = append(items.Content, nodes[i].YNode())
}
err := w.encode(encoder, doc)
yaml.UndoSerializationHacksOnNodes(nodes)
return err
}
// encode encodes the input document node to appropriate node format
func (w ByteWriter) encode(encoder *yaml.Encoder, doc *yaml.Node) error {
rNode := &yaml.RNode{}
rNode.SetYNode(doc)
str, err := rNode.String()
if err != nil {
return errors.Wrap(err)
}
if json.Valid([]byte(str)) {
je := json.NewEncoder(w.Writer)
je.SetIndent("", " ")
return errors.Wrap(je.Encode(rNode))
}
return encoder.Encode(doc)
}

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

@@ -0,0 +1,35 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package kio contains libraries for reading and writing collections of Resources.
//
// Reading Resources
//
// Resources are Read using a kio.Reader function. Examples:
// [kio.LocalPackageReader{}, kio.ByteReader{}]
//
// Resources read using a LocalPackageReader will have annotations applied so they can be
// written back to the files they were read from.
//
// Modifying Resources
//
// Resources are modified using a kio.Filter. The kio.Filter accepts a collection of
// Resources as input, and returns a new collection as output.
// It is recommended to use the yaml package for manipulating individual Resources in
// the collection.
//
// Writing Resources
//
// Resources are Read using a kio.Reader function. Examples:
// [kio.LocalPackageWriter{}, kio.ByteWriter{}]
//
// ReadWriters
//
// It is preferred to use a ReadWriter when reading and writing from / to the same source.
//
// Building Pipelines
//
// The preferred way to transforms a collection of Resources is to use kio.Pipeline to Read,
// Modify and Write the collection of Resources. Pipeline will automatically sequentially
// invoke the Read, Modify, Write steps, returning and error immediately on any failure.
package kio

View File

@@ -0,0 +1,199 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filters
import (
"fmt"
"sort"
"strings"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Filters are the list of known filters for unmarshalling a filter into a concrete
// implementation.
var Filters = map[string]func() kio.Filter{
"FileSetter": func() kio.Filter { return &FileSetter{} },
"FormatFilter": func() kio.Filter { return &FormatFilter{} },
"GrepFilter": func() kio.Filter { return GrepFilter{} },
"MatchModifier": func() kio.Filter { return &MatchModifyFilter{} },
"Modifier": func() kio.Filter { return &Modifier{} },
}
// filter wraps a kio.filter so that it can be unmarshalled from yaml.
type KFilter struct {
kio.Filter
}
func (t KFilter) MarshalYAML() (interface{}, error) {
return t.Filter, nil
}
func (t *KFilter) UnmarshalYAML(unmarshal func(interface{}) error) error {
i := map[string]interface{}{}
if err := unmarshal(i); err != nil {
return err
}
meta := &yaml.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 %v: may be one of: [%s]",
meta, strings.Join(knownFilters, ","))
}
t.Filter = filter()
return unmarshal(t.Filter)
}
// Modifier modifies the input Resources by invoking the provided pipeline.
// Modifier will return any Resources for which the pipeline does not return an error.
type Modifier struct {
Kind string `yaml:"kind,omitempty"`
Filters yaml.YFilters `yaml:"pipeline,omitempty"`
}
var _ kio.Filter = &Modifier{}
func (f Modifier) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range input {
if _, err := input[i].Pipe(f.Filters.Filters()...); err != nil {
return nil, err
}
}
return input, nil
}
type MatchModifyFilter struct {
Kind string `yaml:"kind,omitempty"`
MatchFilters []yaml.YFilters `yaml:"match,omitempty"`
ModifyFilters yaml.YFilters `yaml:"modify,omitempty"`
}
var _ kio.Filter = &MatchModifyFilter{}
func (f MatchModifyFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
var matches = input
var err error
for _, filter := range f.MatchFilters {
matches, err = MatchFilter{Filters: filter}.Filter(matches)
if err != nil {
return nil, err
}
}
_, err = Modifier{Filters: f.ModifyFilters}.Filter(matches)
if err != nil {
return nil, err
}
return input, nil
}
type MatchFilter struct {
Kind string `yaml:"kind,omitempty"`
Filters yaml.YFilters `yaml:"pipeline,omitempty"`
}
var _ kio.Filter = &MatchFilter{}
func (f MatchFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
var output []*yaml.RNode
for i := range input {
if v, err := input[i].Pipe(f.Filters.Filters()...); err != nil {
return nil, err
} else if v == nil {
continue
}
output = append(output, input[i])
}
return output, nil
}
type FilenameFmtVerb string
const (
// KindFmt substitutes kind
KindFmt FilenameFmtVerb = "%k"
// NameFmt substitutes metadata.name
NameFmt FilenameFmtVerb = "%n"
// NamespaceFmt substitutes metdata.namespace
NamespaceFmt FilenameFmtVerb = "%s"
)
// FileSetter sets the file name and mode annotations on Resources.
type FileSetter struct {
Kind string `yaml:"kind,omitempty"`
// FilenamePattern is the pattern to use for generating filenames. FilenameFmtVerb
// FielnameFmtVerbs may be specified to substitute Resource metadata into the filename.
FilenamePattern string `yaml:"filenamePattern,omitempty"`
// Mode is the filemode to write.
Mode string `yaml:"mode,omitempty"`
// Override will override the existing filename if it is set on the pattern.
// Otherwise the existing filename is kept.
Override bool `yaml:"override,omitempty"`
}
var _ kio.Filter = &FileSetter{}
const DefaultFilenamePattern = "%n_%k.yaml"
func (f *FileSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
if f.Mode == "" {
f.Mode = fmt.Sprintf("%d", 0600)
}
if f.FilenamePattern == "" {
f.FilenamePattern = DefaultFilenamePattern
}
resources := map[string][]*yaml.RNode{}
for i := range input {
m, err := input[i].GetMeta()
if err != nil {
return nil, err
}
file := f.FilenamePattern
file = strings.ReplaceAll(file, string(KindFmt), strings.ToLower(m.Kind))
file = strings.ReplaceAll(file, string(NameFmt), strings.ToLower(m.Name))
file = strings.ReplaceAll(file, string(NamespaceFmt), strings.ToLower(m.Namespace))
if _, found := m.Annotations[kioutil.PathAnnotation]; !found || f.Override {
if _, err := input[i].Pipe(yaml.SetAnnotation(kioutil.PathAnnotation, file)); err != nil {
return nil, err
}
}
resources[file] = append(resources[file], input[i])
}
var output []*yaml.RNode
for i := range resources {
if err := kioutil.SortNodes(resources[i]); err != nil {
return nil, err
}
for j := range resources[i] {
if _, err := resources[i][j].Pipe(
yaml.SetAnnotation(kioutil.IndexAnnotation, fmt.Sprintf("%d", j))); err != nil {
return nil, err
}
output = append(output, resources[i][j])
}
}
return output, nil
}

314
vendor/sigs.k8s.io/kustomize/kyaml/kio/filters/fmtr.go generated vendored Normal file
View File

@@ -0,0 +1,314 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package yamlfmt contains libraries for formatting yaml files containing
// Kubernetes Resource configuration.
//
// Yaml files are formatted by:
// - Sorting fields and map values
// - Sorting unordered lists for whitelisted types
// - Applying a canonical yaml Style
//
// Fields are ordered using a relative ordering applied to commonly
// encountered Resource fields. All Resources, including non-builtin
// Resources such as CRDs, share the same field precedence.
//
// Fields that do not appear in the explicit ordering are ordered
// lexicographically.
//
// A subset of well known known unordered lists are sorted by element field
// values.
package filters
import (
"bytes"
"fmt"
"io"
"sort"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/openapi"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type FormattingStrategy = string
const (
// NoFmtAnnotation determines if the resource should be formatted.
FmtAnnotation string = "config.kubernetes.io/formatting"
// FmtStrategyStandard means the resource will be formatted according
// to the default rules.
FmtStrategyStandard FormattingStrategy = "standard"
// FmtStrategyNone means the resource will not be formatted.
FmtStrategyNone FormattingStrategy = "none"
)
// FormatInput returns the formatted input.
func FormatInput(input io.Reader) (*bytes.Buffer, error) {
buff := &bytes.Buffer{}
err := kio.Pipeline{
Inputs: []kio.Reader{&kio.ByteReader{Reader: input}},
Filters: []kio.Filter{FormatFilter{}},
Outputs: []kio.Writer{kio.ByteWriter{Writer: buff}},
}.Execute()
return buff, err
}
// FormatFileOrDirectory reads the file or directory and formats each file's
// contents by writing it back to the file.
func FormatFileOrDirectory(path string) error {
return kio.Pipeline{
Inputs: []kio.Reader{kio.LocalPackageReader{
PackagePath: path,
}},
Filters: []kio.Filter{FormatFilter{}},
Outputs: []kio.Writer{kio.LocalPackageWriter{PackagePath: path}},
}.Execute()
}
type FormatFilter struct {
Process func(n *yaml.Node) error
UseSchema bool
}
var _ kio.Filter = FormatFilter{}
func (f FormatFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range slice {
fmtStrategy, err := getFormattingStrategy(slice[i])
if err != nil {
return nil, err
}
if fmtStrategy == FmtStrategyNone {
continue
}
kindNode, err := slice[i].Pipe(yaml.Get("kind"))
if err != nil {
return nil, err
}
if kindNode == nil {
continue
}
apiVersionNode, err := slice[i].Pipe(yaml.Get("apiVersion"))
if err != nil {
return nil, err
}
if apiVersionNode == nil {
continue
}
kind, apiVersion := kindNode.YNode().Value, apiVersionNode.YNode().Value
var s *openapi.ResourceSchema
if f.UseSchema {
s = openapi.SchemaForResourceType(yaml.TypeMeta{APIVersion: apiVersion, Kind: kind})
} else {
s = nil
}
err = (&formatter{apiVersion: apiVersion, kind: kind, process: f.Process}).
fmtNode(slice[i].YNode(), "", s)
if err != nil {
return nil, err
}
}
return slice, nil
}
// getFormattingStrategy looks for the formatting annotation to determine
// which strategy should be used for formatting. The default is standard
// if no annotation is found.
func getFormattingStrategy(node *yaml.RNode) (FormattingStrategy, error) {
value, err := node.Pipe(yaml.GetAnnotation(FmtAnnotation))
if err != nil || value == nil {
return FmtStrategyStandard, err
}
fmtStrategy := value.YNode().Value
switch fmtStrategy {
case FmtStrategyStandard:
return FmtStrategyStandard, nil
case FmtStrategyNone:
return FmtStrategyNone, nil
default:
return "", fmt.Errorf(
"formatting annotation has illegal value %s", fmtStrategy)
}
}
type formatter struct {
apiVersion string
kind string
process func(n *yaml.Node) error
}
// fmtNode recursively formats the Document Contents.
// See: https://godoc.org/gopkg.in/yaml.v3#Node
func (f *formatter) fmtNode(n *yaml.Node, path string, schema *openapi.ResourceSchema) error {
if n.Kind == yaml.ScalarNode && schema != nil && schema.Schema != nil {
// ensure values that are interpreted as non-string values (e.g. "true")
// are properly quoted
yaml.FormatNonStringStyle(n, *schema.Schema)
}
// sort the order of mapping fields
if n.Kind == yaml.MappingNode {
sort.Sort(sortedMapContents(*n))
}
// sort the order of sequence elements if it is whitelisted
if n.Kind == yaml.SequenceNode {
if yaml.WhitelistedListSortKinds.Has(f.kind) &&
yaml.WhitelistedListSortApis.Has(f.apiVersion) {
if sortField, found := yaml.WhitelistedListSortFields[path]; found {
sort.Sort(sortedSeqContents{Node: *n, sortField: sortField})
}
}
}
// format the Content
for i := range n.Content {
// MappingNode are structured as having their fields as Content,
// with the field-key and field-value alternating. e.g. Even elements
// are the keys and odd elements are the values
isFieldKey := n.Kind == yaml.MappingNode && i%2 == 0
isFieldValue := n.Kind == yaml.MappingNode && i%2 == 1
isElement := n.Kind == yaml.SequenceNode
// run the process callback on the node if it has been set
// don't process keys: their format should be fixed
if f.process != nil && !isFieldKey {
if err := f.process(n.Content[i]); err != nil {
return err
}
}
// get the schema for this Node
p := path
var s *openapi.ResourceSchema
switch {
case isFieldValue:
// if the node is a field, lookup the schema using the field name
p = fmt.Sprintf("%s.%s", path, n.Content[i-1].Value)
if schema != nil {
s = schema.Field(n.Content[i-1].Value)
}
case isElement:
// if the node is a list element, lookup the schema for the array items
if schema != nil {
s = schema.Elements()
}
}
// format the node using the schema
err := f.fmtNode(n.Content[i], p, s)
if err != nil {
return err
}
}
return nil
}
// sortedMapContents sorts the Contents field of a MappingNode by the field names using a statically
// defined field precedence, and falling back on lexicographical sorting
type sortedMapContents yaml.Node
func (s sortedMapContents) Len() int {
return len(s.Content) / 2
}
func (s sortedMapContents) Swap(i, j int) {
// yaml MappingNode Contents are a list of field names followed by
// field values, rather than a list of field <name, value> pairs.
// increment.
//
// e.g. ["field1Name", "field1Value", "field2Name", "field2Value"]
iFieldNameIndex := i * 2
jFieldNameIndex := j * 2
iFieldValueIndex := iFieldNameIndex + 1
jFieldValueIndex := jFieldNameIndex + 1
// swap field names
s.Content[iFieldNameIndex], s.Content[jFieldNameIndex] =
s.Content[jFieldNameIndex], s.Content[iFieldNameIndex]
// swap field values
s.Content[iFieldValueIndex], s.Content[jFieldValueIndex] = s.
Content[jFieldValueIndex], s.Content[iFieldValueIndex]
}
func (s sortedMapContents) Less(i, j int) bool {
iFieldNameIndex := i * 2
jFieldNameIndex := j * 2
iFieldName := s.Content[iFieldNameIndex].Value
jFieldName := s.Content[jFieldNameIndex].Value
// order by their precedence values looked up from the index
iOrder, foundI := yaml.FieldOrder[iFieldName]
jOrder, foundJ := yaml.FieldOrder[jFieldName]
if foundI && foundJ {
return iOrder < jOrder
}
// known fields come before unknown fields
if foundI {
return true
}
if foundJ {
return false
}
// neither field is known, sort them lexicographically
return iFieldName < jFieldName
}
// sortedSeqContents sorts the Contents field of a SequenceNode by the value of
// the elements sortField.
// e.g. it will sort spec.template.spec.containers by the value of the container `name` field
type sortedSeqContents struct {
yaml.Node
sortField string
}
func (s sortedSeqContents) Len() int {
return len(s.Content)
}
func (s sortedSeqContents) Swap(i, j int) {
s.Content[i], s.Content[j] = s.Content[j], s.Content[i]
}
func (s sortedSeqContents) Less(i, j int) bool {
// primitive lists -- sort by the element's primitive values
if s.sortField == "" {
iValue := s.Content[i].Value
jValue := s.Content[j].Value
return iValue < jValue
}
// map lists -- sort by the element's sortField values
var iValue, jValue string
for a := range s.Content[i].Content {
if a%2 != 0 {
continue // not a fieldNameIndex
}
// locate the index of the sortField field
if s.Content[i].Content[a].Value == s.sortField {
// a is the yaml node for the field key, a+1 is the node for the field value
iValue = s.Content[i].Content[a+1].Value
}
}
for a := range s.Content[j].Content {
if a%2 != 0 {
continue // not a fieldNameIndex
}
// locate the index of the sortField field
if s.Content[j].Content[a].Value == s.sortField {
// a is the yaml node for the field key, a+1 is the node for the field value
jValue = s.Content[j].Content[a+1].Value
}
}
// compare the field values
return iValue < jValue
}

117
vendor/sigs.k8s.io/kustomize/kyaml/kio/filters/grep.go generated vendored Normal file
View File

@@ -0,0 +1,117 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filters
import (
"regexp"
"strings"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type GrepType int
const (
Regexp GrepType = 1 << iota
GreaterThanEq
GreaterThan
LessThan
LessThanEq
)
// GrepFilter filters RNodes with a matching field
type GrepFilter struct {
Path []string `yaml:"path,omitempty"`
Value string `yaml:"value,omitempty"`
MatchType GrepType `yaml:"matchType,omitempty"`
InvertMatch bool `yaml:"invertMatch,omitempty"`
Compare func(a, b string) (int, error)
}
var _ kio.Filter = GrepFilter{}
func (f GrepFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
// compile the regular expression 1 time if we are matching using regex
var reg *regexp.Regexp
var err error
if f.MatchType == Regexp || f.MatchType == 0 {
reg, err = regexp.Compile(f.Value)
if err != nil {
return nil, err
}
}
var output kio.ResourceNodeSlice
for i := range input {
node := input[i]
val, err := node.Pipe(&yaml.PathMatcher{Path: f.Path})
if err != nil {
return nil, err
}
if val == nil || len(val.Content()) == 0 {
if f.InvertMatch {
output = append(output, input[i])
}
continue
}
found := false
err = val.VisitElements(func(elem *yaml.RNode) error {
// get the value
var str string
if f.MatchType == Regexp {
style := elem.YNode().Style
defer func() { elem.YNode().Style = style }()
elem.YNode().Style = yaml.FlowStyle
str, err = elem.String()
if err != nil {
return err
}
str = strings.TrimSpace(strings.ReplaceAll(str, `"`, ""))
} else {
// if not regexp, then it needs to parse into a quantity and comments will
// break that
str = elem.YNode().Value
if str == "" {
return nil
}
}
if f.MatchType == Regexp || f.MatchType == 0 {
if reg.MatchString(str) {
found = true
}
return nil
}
comp, err := f.Compare(str, f.Value)
if err != nil {
return err
}
if f.MatchType == GreaterThan && comp > 0 {
found = true
}
if f.MatchType == GreaterThanEq && comp >= 0 {
found = true
}
if f.MatchType == LessThan && comp < 0 {
found = true
}
if f.MatchType == LessThanEq && comp <= 0 {
found = true
}
return nil
})
if err != nil {
return nil, err
}
if found == f.InvertMatch {
continue
}
output = append(output, input[i])
}
return output, nil
}

View File

@@ -0,0 +1,38 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filters
import (
"sigs.k8s.io/kustomize/kyaml/yaml"
)
const LocalConfigAnnotation = "config.kubernetes.io/local-config"
// IsLocalConfig filters Resources using the config.kubernetes.io/local-config annotation
type IsLocalConfig struct {
// IncludeLocalConfig will include local-config if set to true
IncludeLocalConfig bool `yaml:"includeLocalConfig,omitempty"`
// ExcludeNonLocalConfig will exclude non local-config if set to true
ExcludeNonLocalConfig bool `yaml:"excludeNonLocalConfig,omitempty"`
}
// Filter implements kio.Filter
func (c *IsLocalConfig) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) {
var out []*yaml.RNode
for i := range inputs {
meta, err := inputs[i].GetMeta()
if err != nil {
return nil, err
}
_, local := meta.Annotations[LocalConfigAnnotation]
if local && c.IncludeLocalConfig {
out = append(out, inputs[i])
} else if !local && !c.ExcludeNonLocalConfig {
out = append(out, inputs[i])
}
}
return out, nil
}

View File

@@ -0,0 +1,86 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package merge contains libraries for merging Resources and Patches
package filters
import (
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/merge2"
)
// MergeFilter merges Resources with the Group/Version/Kind/Namespace/Name together using
// a 2-way merge strategy.
//
// - Fields set to null in the source will be cleared from the destination
// - Fields with matching keys will be merged recursively
// - Lists with an associative key (e.g. name) will have their elements merged using the key
// - List without an associative key will have the dest list replaced by the source list
type MergeFilter struct {
Reverse bool
}
var _ kio.Filter = MergeFilter{}
type mergeKey struct {
apiVersion string
kind string
namespace string
name string
}
// MergeFilter implements kio.Filter by merging Resources with the same G/V/K/NS/N
func (c MergeFilter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
// invert the merge precedence
if c.Reverse {
for i, j := 0, len(input)-1; i < j; i, j = i+1, j-1 {
input[i], input[j] = input[j], input[i]
}
}
// index the Resources by G/V/K/NS/N
index := map[mergeKey][]*yaml.RNode{}
// retain the original ordering
var order []mergeKey
for i := range input {
meta, err := input[i].GetMeta()
if err != nil {
return nil, err
}
key := mergeKey{
apiVersion: meta.APIVersion,
kind: meta.Kind,
namespace: meta.Namespace,
name: meta.Name,
}
if _, found := index[key]; !found {
order = append(order, key)
}
index[key] = append(index[key], input[i])
}
// merge each of the G/V/K/NS/N lists
var output []*yaml.RNode
var err error
for _, k := range order {
var merged *yaml.RNode
resources := index[k]
for i := range resources {
patch := resources[i]
if merged == nil {
// first resources, don't merge it
merged = resources[i]
} else {
merged, err = merge2.Merge(patch, merged, yaml.MergeOptions{
ListIncreaseDirection: yaml.MergeOptionsListPrepend,
})
if err != nil {
return nil, err
}
}
}
output = append(output, merged)
}
return output, nil
}

View File

@@ -0,0 +1,203 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filters
import (
"fmt"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
"sigs.k8s.io/kustomize/kyaml/yaml/merge3"
)
const (
mergeSourceAnnotation = "config.kubernetes.io/merge-source"
mergeSourceOriginal = "original"
mergeSourceUpdated = "updated"
mergeSourceDest = "dest"
)
// Merge3 performs a 3-way merge on the original, updated, and destination packages.
type Merge3 struct {
OriginalPath string
UpdatedPath string
DestPath string
MatchFilesGlob []string
// MergeOnPath will use the relative filepath as part of the merge key.
// This may be necessary if the directory contains multiple copies of
// the same resource, or resources patches.
MergeOnPath bool
}
func (m Merge3) Merge() error {
// Read the destination package. The ReadWriter will take take of deleting files
// for removed resources.
var inputs []kio.Reader
dest := &kio.LocalPackageReadWriter{
PackagePath: m.DestPath,
MatchFilesGlob: m.MatchFilesGlob,
SetAnnotations: map[string]string{mergeSourceAnnotation: mergeSourceDest},
}
inputs = append(inputs, dest)
// Read the original package
inputs = append(inputs, kio.LocalPackageReader{
PackagePath: m.OriginalPath,
MatchFilesGlob: m.MatchFilesGlob,
SetAnnotations: map[string]string{mergeSourceAnnotation: mergeSourceOriginal},
})
// Read the updated package
inputs = append(inputs, kio.LocalPackageReader{
PackagePath: m.UpdatedPath,
MatchFilesGlob: m.MatchFilesGlob,
SetAnnotations: map[string]string{mergeSourceAnnotation: mergeSourceUpdated},
})
return kio.Pipeline{
Inputs: inputs,
Filters: []kio.Filter{m},
Outputs: []kio.Writer{dest},
}.Execute()
}
// Filter combines Resources with the same GVK + N + NS into tuples, and then merges them
func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
// index the nodes by their identity
tl := tuples{mergeOnPath: m.MergeOnPath}
for i := range nodes {
if err := tl.add(nodes[i]); err != nil {
return nil, err
}
}
// iterate over the inputs, merging as needed
var output []*yaml.RNode
for i := range tl.list {
t := tl.list[i]
switch {
case t.original == nil && t.updated == nil && t.dest != nil:
// added locally -- keep dest
output = append(output, t.dest)
case t.original == nil && t.updated != nil && t.dest == nil:
// added in the update -- add update
output = append(output, t.updated)
case t.original != nil && t.updated == nil:
// deleted in the update
// don't include the resource in the output
case t.original != nil && t.dest == nil:
// deleted locally
// don't include the resource in the output
default:
// dest and updated are non-nil -- merge them
node, err := t.merge()
if err != nil {
return nil, err
}
if node != nil {
output = append(output, node)
}
}
}
return output, nil
}
// tuples combines nodes with the same GVK + N + NS
type tuples struct {
list []*tuple
// mergeOnPath if set to true will use the resource filepath
// as part of the merge key
mergeOnPath bool
}
// isSameResource returns true if meta1 and meta2 are for the same logic resource
func (ts *tuples) isSameResource(meta1, meta2 yaml.ResourceMeta) bool {
if meta1.Name != meta2.Name {
return false
}
if meta1.Namespace != meta2.Namespace {
return false
}
if meta1.APIVersion != meta2.APIVersion {
return false
}
if meta1.Kind != meta2.Kind {
return false
}
if ts.mergeOnPath {
// directories may contain multiple copies of a resource with the same
// name, namespace, apiVersion and kind -- e.g. kustomize patches, or
// multiple environments
// mergeOnPath configures the merge logic to use the path as part of the
// resource key
if meta1.Annotations[kioutil.PathAnnotation] != meta2.Annotations[kioutil.PathAnnotation] {
return false
}
}
return true
}
// add adds a node to the list, combining it with an existing matching Resource if found
func (ts *tuples) add(node *yaml.RNode) error {
nodeMeta, err := node.GetMeta()
if err != nil {
return err
}
for i := range ts.list {
t := ts.list[i]
if ts.isSameResource(t.meta, nodeMeta) {
return t.add(node)
}
}
t := &tuple{meta: nodeMeta}
if err := t.add(node); err != nil {
return err
}
ts.list = append(ts.list, t)
return nil
}
// tuple wraps an original, updated, and dest tuple for a given Resource
type tuple struct {
meta yaml.ResourceMeta
original *yaml.RNode
updated *yaml.RNode
dest *yaml.RNode
}
// add sets the corresponding tuple field for the node
func (t *tuple) add(node *yaml.RNode) error {
meta, err := node.GetMeta()
if err != nil {
return err
}
switch meta.Annotations[mergeSourceAnnotation] {
case mergeSourceDest:
if t.dest != nil {
return fmt.Errorf("dest source already specified")
}
t.dest = node
case mergeSourceOriginal:
if t.original != nil {
return fmt.Errorf("original source already specified")
}
t.original = node
case mergeSourceUpdated:
if t.updated != nil {
return fmt.Errorf("updated source already specified")
}
t.updated = node
default:
return fmt.Errorf("no source annotation for Resource")
}
return nil
}
// merge performs a 3-way merge on the tuple
func (t *tuple) merge() (*yaml.RNode, error) {
return merge3.Merge(t.dest, t.original, t.updated)
}

View File

@@ -0,0 +1,4 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filters

View File

@@ -0,0 +1,32 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package filters
import (
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type StripCommentsFilter struct{}
var _ kio.Filter = StripCommentsFilter{}
func (f StripCommentsFilter) Filter(slice []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range slice {
stripComments(slice[i].YNode())
}
return slice, nil
}
func stripComments(node *yaml.Node) {
if node == nil {
return
}
node.HeadComment = ""
node.LineComment = ""
node.FootComment = ""
for i := range node.Content {
stripComments(node.Content[i])
}
}

View File

@@ -0,0 +1,100 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kio
import (
"os"
"path/filepath"
"strings"
"github.com/monochromegane/go-gitignore"
"sigs.k8s.io/kustomize/kyaml/ext"
)
// ignoreFilesMatcher handles `.krmignore` files, which allows for ignoring
// files or folders in a package. The format of this file is a subset of the
// gitignore format, with recursive patterns (like a/**/c) not supported. If a
// file or folder matches any of the patterns in the .krmignore file for the
// package, it will be excluded.
//
// It works as follows:
//
// * It will look for .krmignore file in the top folder and on the top level
// of any subpackages. Subpackages are defined by the presence of a Krmfile
// in the folder.
// * `.krmignore` files only cover files and folders for the package in which
// it is defined. So ignore patterns defined in a parent package does not
// affect which files are ignored from a subpackage.
// * An ignore pattern can not ignore a subpackage. So even if the parent
// package contains a pattern that ignores the directory foo, if foo is a
// subpackage, it will still be included if the IncludeSubpackages property
// is set to true
type ignoreFilesMatcher struct {
matchers []matcher
}
// readIgnoreFile checks whether there is a .krmignore file in the path, and
// if it is, reads it in and turns it into a matcher. If we can't find a file,
// we just add a matcher that match nothing.
func (i *ignoreFilesMatcher) readIgnoreFile(path string) error {
i.verifyPath(path)
m, err := gitignore.NewGitIgnore(filepath.Join(path, ext.IgnoreFileName()))
if err != nil {
if os.IsNotExist(err) {
i.matchers = append(i.matchers, matcher{
matcher: gitignore.DummyIgnoreMatcher(false),
basePath: path,
})
return nil
}
return err
}
i.matchers = append(i.matchers, matcher{
matcher: m,
basePath: path,
})
return nil
}
// verifyPath checks whether the top matcher on the stack
// is correct for the provided filepath. Matchers are removed once
// we encounter a filepath that is not a subpath of the basepath for
// the matcher.
func (i *ignoreFilesMatcher) verifyPath(path string) {
for j := len(i.matchers) - 1; j >= 0; j-- {
matcher := i.matchers[j]
if strings.HasPrefix(path, matcher.basePath) || path == matcher.basePath {
i.matchers = i.matchers[:j+1]
return
}
}
}
// matchFile checks whether the file given by the provided path matches
// any of the patterns in the .krmignore file for the package.
func (i *ignoreFilesMatcher) matchFile(path string) bool {
if len(i.matchers) == 0 {
return false
}
i.verifyPath(filepath.Dir(path))
return i.matchers[len(i.matchers)-1].matcher.Match(path, false)
}
// matchFile checks whether the directory given by the provided path matches
// any of the patterns in the .krmignore file for the package.
func (i *ignoreFilesMatcher) matchDir(path string) bool {
if len(i.matchers) == 0 {
return false
}
i.verifyPath(path)
return i.matchers[len(i.matchers)-1].matcher.Match(path, true)
}
// matcher wraps the gitignore matcher and the path to the folder
// where the file was found.
type matcher struct {
matcher gitignore.IgnoreMatcher
basePath string
}

149
vendor/sigs.k8s.io/kustomize/kyaml/kio/kio.go generated vendored Normal file
View File

@@ -0,0 +1,149 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
// Package kio contains low-level libraries for reading, modifying and writing
// Resource Configuration and packages.
package kio
import (
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Reader reads ResourceNodes. Analogous to io.Reader.
type Reader interface {
Read() ([]*yaml.RNode, error)
}
// ResourceNodeSlice is a collection of ResourceNodes.
// While ResourceNodeSlice has no inherent constraints on ordering or uniqueness, specific
// Readers, Filters or Writers may have constraints.
type ResourceNodeSlice []*yaml.RNode
var _ Reader = ResourceNodeSlice{}
func (o ResourceNodeSlice) Read() ([]*yaml.RNode, error) {
return o, nil
}
// Writer writes ResourceNodes. Analogous to io.Writer.
type Writer interface {
Write([]*yaml.RNode) error
}
// WriterFunc implements a Writer as a function.
type WriterFunc func([]*yaml.RNode) error
func (fn WriterFunc) Write(o []*yaml.RNode) error {
return fn(o)
}
// ReaderWriter implements both Reader and Writer interfaces
type ReaderWriter interface {
Reader
Writer
}
// Filter modifies a collection of Resource Configuration by returning the modified slice.
// When possible, Filters should be serializable to yaml so that they can be described
// as either data or code.
//
// Analogous to http://www.linfo.org/filters.html
type Filter interface {
Filter([]*yaml.RNode) ([]*yaml.RNode, error)
}
// FilterFunc implements a Filter as a function.
type FilterFunc func([]*yaml.RNode) ([]*yaml.RNode, error)
func (fn FilterFunc) Filter(o []*yaml.RNode) ([]*yaml.RNode, error) {
return fn(o)
}
// Pipeline reads Resource Configuration from a set of Inputs, applies some
// transformation filters, and writes the results to a set of Outputs.
//
// Analogous to http://www.linfo.org/pipes.html
type Pipeline struct {
// Inputs provide sources for Resource Configuration to be read.
Inputs []Reader `yaml:"inputs,omitempty"`
// Filters are transformations applied to the Resource Configuration.
// They are applied in the order they are specified.
// Analogous to http://www.linfo.org/filters.html
Filters []Filter `yaml:"filters,omitempty"`
// Outputs are where the transformed Resource Configuration is written.
Outputs []Writer `yaml:"outputs,omitempty"`
// ContinueOnEmptyResult configures what happens when a filter in the pipeline
// returns an empty result.
// If it is false (default), subsequent filters will be skipped and the result
// will be returned immediately. This is useful as an optimization when you
// know that subsequent filters will not alter the empty result.
// If it is true, the empty result will be provided as input to the next
// filter in the list. This is useful when subsequent functions in the
// pipeline may generate new resources.
ContinueOnEmptyResult bool `yaml:"continueOnEmptyResult,omitempty"`
}
// Execute executes each step in the sequence, returning immediately after encountering
// any error as part of the Pipeline.
func (p Pipeline) Execute() error {
return p.ExecuteWithCallback(nil)
}
// PipelineExecuteCallbackFunc defines a callback function that will be called each time a step in the pipeline succeeds.
type PipelineExecuteCallbackFunc = func(op Filter)
// ExecuteWithCallback executes each step in the sequence, returning immediately after encountering
// any error as part of the Pipeline. The callback will be called each time a step succeeds.
func (p Pipeline) ExecuteWithCallback(callback PipelineExecuteCallbackFunc) error {
var result []*yaml.RNode
// read from the inputs
for _, i := range p.Inputs {
nodes, err := i.Read()
if err != nil {
return errors.Wrap(err)
}
result = append(result, nodes...)
}
// apply operations
var err error
for i := range p.Filters {
op := p.Filters[i]
if callback != nil {
callback(op)
}
result, err = op.Filter(result)
// TODO (issue 2872): This len(result) == 0 should be removed and empty result list should be
// handled by outputs. However currently some writer like LocalPackageReadWriter
// will clear the output directory and which will cause unpredictable results
if len(result) == 0 && !p.ContinueOnEmptyResult || err != nil {
return errors.Wrap(err)
}
}
// write to the outputs
for _, o := range p.Outputs {
if err := o.Write(result); err != nil {
return errors.Wrap(err)
}
}
return nil
}
// FilterAll runs the yaml.Filter against all inputs
func FilterAll(filter yaml.Filter) Filter {
return FilterFunc(func(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
for i := range nodes {
_, err := filter.Filter(nodes[i])
if err != nil {
return nil, errors.Wrap(err)
}
}
return nodes, nil
})
}

View File

@@ -0,0 +1,233 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kioutil
import (
"fmt"
"path"
"sort"
"strconv"
"strings"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type AnnotationKey = string
const (
// IndexAnnotation records the index of a specific resource in a file or input stream.
IndexAnnotation AnnotationKey = "config.kubernetes.io/index"
// PathAnnotation records the path to the file the Resource was read from
PathAnnotation AnnotationKey = "config.kubernetes.io/path"
)
func GetFileAnnotations(rn *yaml.RNode) (string, string, error) {
meta, err := rn.GetMeta()
if err != nil {
return "", "", err
}
path := meta.Annotations[PathAnnotation]
index := meta.Annotations[IndexAnnotation]
return path, index, nil
}
// ErrorIfMissingAnnotation validates the provided annotations are present on the given resources
func ErrorIfMissingAnnotation(nodes []*yaml.RNode, keys ...AnnotationKey) error {
for _, key := range keys {
for _, node := range nodes {
val, err := node.Pipe(yaml.GetAnnotation(key))
if err != nil {
return errors.Wrap(err)
}
if val == nil {
return errors.Errorf("missing annotation %s", key)
}
}
}
return nil
}
// CreatePathAnnotationValue creates a default path annotation value for a Resource.
// The path prefix will be dir.
func CreatePathAnnotationValue(dir string, m yaml.ResourceMeta) string {
filename := fmt.Sprintf("%s_%s.yaml", strings.ToLower(m.Kind), m.Name)
return path.Join(dir, m.Namespace, filename)
}
// DefaultPathAndIndexAnnotation sets a default path or index value on any nodes missing the
// annotation
func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
counts := map[string]int{}
// check each node for the path annotation
for i := range nodes {
m, err := nodes[i].GetMeta()
if err != nil {
return err
}
// calculate the max index in each file in case we are appending
if p, found := m.Annotations[PathAnnotation]; found {
// record the max indexes into each file
if i, found := m.Annotations[IndexAnnotation]; found {
index, _ := strconv.Atoi(i)
if index > counts[p] {
counts[p] = index
}
}
// has the path annotation already -- do nothing
continue
}
// set a path annotation on the Resource
path := CreatePathAnnotationValue(dir, m)
if err := nodes[i].PipeE(yaml.SetAnnotation(PathAnnotation, path)); err != nil {
return err
}
}
// set the index annotations
for i := range nodes {
m, err := nodes[i].GetMeta()
if err != nil {
return err
}
if _, found := m.Annotations[IndexAnnotation]; found {
continue
}
p := m.Annotations[PathAnnotation]
// set an index annotation on the Resource
c := counts[p]
counts[p] = c + 1
if err := nodes[i].PipeE(
yaml.SetAnnotation(IndexAnnotation, fmt.Sprintf("%d", c))); err != nil {
return err
}
}
return nil
}
// DefaultPathAnnotation sets a default path annotation on any Reources
// missing it.
func DefaultPathAnnotation(dir string, nodes []*yaml.RNode) error {
// check each node for the path annotation
for i := range nodes {
m, err := nodes[i].GetMeta()
if err != nil {
return err
}
if _, found := m.Annotations[PathAnnotation]; found {
// has the path annotation already -- do nothing
continue
}
// set a path annotation on the Resource
path := CreatePathAnnotationValue(dir, m)
if err := nodes[i].PipeE(yaml.SetAnnotation(PathAnnotation, path)); err != nil {
return err
}
}
return nil
}
// Map invokes fn for each element in nodes.
func Map(nodes []*yaml.RNode, fn func(*yaml.RNode) (*yaml.RNode, error)) ([]*yaml.RNode, error) {
var returnNodes []*yaml.RNode
for i := range nodes {
n, err := fn(nodes[i])
if err != nil {
return nil, errors.Wrap(err)
}
if n != nil {
returnNodes = append(returnNodes, n)
}
}
return returnNodes, nil
}
func MapMeta(nodes []*yaml.RNode, fn func(*yaml.RNode, yaml.ResourceMeta) (*yaml.RNode, error)) (
[]*yaml.RNode, error) {
var returnNodes []*yaml.RNode
for i := range nodes {
meta, err := nodes[i].GetMeta()
if err != nil {
return nil, errors.Wrap(err)
}
n, err := fn(nodes[i], meta)
if err != nil {
return nil, errors.Wrap(err)
}
if n != nil {
returnNodes = append(returnNodes, n)
}
}
return returnNodes, nil
}
// SortNodes sorts nodes in place:
// - by PathAnnotation annotation
// - by IndexAnnotation annotation
func SortNodes(nodes []*yaml.RNode) error {
var err error
// use stable sort to keep ordering of equal elements
sort.SliceStable(nodes, func(i, j int) bool {
if err != nil {
return false
}
var iMeta, jMeta yaml.ResourceMeta
if iMeta, _ = nodes[i].GetMeta(); err != nil {
return false
}
if jMeta, _ = nodes[j].GetMeta(); err != nil {
return false
}
iValue := iMeta.Annotations[PathAnnotation]
jValue := jMeta.Annotations[PathAnnotation]
if iValue != jValue {
return iValue < jValue
}
iValue = iMeta.Annotations[IndexAnnotation]
jValue = jMeta.Annotations[IndexAnnotation]
// put resource config without an index first
if iValue == jValue {
return false
}
if iValue == "" {
return true
}
if jValue == "" {
return false
}
// sort by index
var iIndex, jIndex int
iIndex, err = strconv.Atoi(iValue)
if err != nil {
err = fmt.Errorf("unable to parse config.kubernetes.io/index %s :%v", iValue, err)
return false
}
jIndex, err = strconv.Atoi(jValue)
if err != nil {
err = fmt.Errorf("unable to parse config.kubernetes.io/index %s :%v", jValue, err)
return false
}
if iIndex != jIndex {
return iIndex < jIndex
}
// elements are equal
return false
})
return errors.Wrap(err)
}

326
vendor/sigs.k8s.io/kustomize/kyaml/kio/pkgio_reader.go generated vendored Normal file
View File

@@ -0,0 +1,326 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kio
import (
"fmt"
"os"
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/sets"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// requiredResourcePackageAnnotations are annotations that are required to write resources back to
// files.
var requiredResourcePackageAnnotations = []string{kioutil.IndexAnnotation, kioutil.PathAnnotation}
// PackageBuffer implements Reader and Writer, storing Resources in a local field.
type PackageBuffer struct {
Nodes []*yaml.RNode
}
func (r *PackageBuffer) Read() ([]*yaml.RNode, error) {
return r.Nodes, nil
}
func (r *PackageBuffer) Write(nodes []*yaml.RNode) error {
r.Nodes = nodes
return nil
}
// LocalPackageReadWriter reads and writes Resources from / to a local directory.
// When writing, LocalPackageReadWriter will delete files if all of the Resources from
// that file have been removed from the output.
type LocalPackageReadWriter struct {
Kind string `yaml:"kind,omitempty"`
KeepReaderAnnotations bool `yaml:"keepReaderAnnotations,omitempty"`
// PackagePath is the path to the package directory.
PackagePath string `yaml:"path,omitempty"`
// PackageFileName is the name of file containing package metadata.
// It will be used to identify package.
PackageFileName string `yaml:"packageFileName,omitempty"`
// MatchFilesGlob configures Read to only read Resources from files matching any of the
// provided patterns.
// Defaults to ["*.yaml", "*.yml"] if empty. To match all files specify ["*"].
MatchFilesGlob []string `yaml:"matchFilesGlob,omitempty"`
// IncludeSubpackages will configure Read to read Resources from subpackages.
// Subpackages are identified by presence of PackageFileName.
IncludeSubpackages bool `yaml:"includeSubpackages,omitempty"`
// ErrorIfNonResources will configure Read to throw an error if yaml missing missing
// apiVersion or kind is read.
ErrorIfNonResources bool `yaml:"errorIfNonResources,omitempty"`
// OmitReaderAnnotations will cause the reader to skip annotating Resources with the file
// path and mode.
OmitReaderAnnotations bool `yaml:"omitReaderAnnotations,omitempty"`
// SetAnnotations are annotations to set on the Resources as they are read.
SetAnnotations map[string]string `yaml:"setAnnotations,omitempty"`
// NoDeleteFiles if set to true, LocalPackageReadWriter won't delete any files
NoDeleteFiles bool `yaml:"noDeleteFiles,omitempty"`
files sets.String
// FileSkipFunc is a function which returns true if reader should ignore
// the file
FileSkipFunc LocalPackageSkipFileFunc
}
func (r *LocalPackageReadWriter) Read() ([]*yaml.RNode, error) {
nodes, err := LocalPackageReader{
PackagePath: r.PackagePath,
MatchFilesGlob: r.MatchFilesGlob,
IncludeSubpackages: r.IncludeSubpackages,
ErrorIfNonResources: r.ErrorIfNonResources,
SetAnnotations: r.SetAnnotations,
PackageFileName: r.PackageFileName,
FileSkipFunc: r.FileSkipFunc,
}.Read()
if err != nil {
return nil, errors.Wrap(err)
}
// keep track of all the files
if !r.NoDeleteFiles {
r.files, err = r.getFiles(nodes)
if err != nil {
return nil, errors.Wrap(err)
}
}
return nodes, nil
}
func (r *LocalPackageReadWriter) Write(nodes []*yaml.RNode) error {
newFiles, err := r.getFiles(nodes)
if err != nil {
return errors.Wrap(err)
}
var clear []string
for k := range r.SetAnnotations {
clear = append(clear, k)
}
err = LocalPackageWriter{
PackagePath: r.PackagePath,
ClearAnnotations: clear,
KeepReaderAnnotations: r.KeepReaderAnnotations,
}.Write(nodes)
if err != nil {
return errors.Wrap(err)
}
deleteFiles := r.files.Difference(newFiles)
for f := range deleteFiles {
if err = os.Remove(filepath.Join(r.PackagePath, f)); err != nil {
return errors.Wrap(err)
}
}
return nil
}
func (r *LocalPackageReadWriter) getFiles(nodes []*yaml.RNode) (sets.String, error) {
val := sets.String{}
for _, n := range nodes {
path, _, err := kioutil.GetFileAnnotations(n)
if err != nil {
return nil, errors.Wrap(err)
}
val.Insert(path)
}
return val, nil
}
// LocalPackageSkipFileFunc is a function which returns true if the file
// in the package should be ignored by reader.
// relPath is an OS specific relative path
type LocalPackageSkipFileFunc func(relPath string) bool
// LocalPackageReader reads ResourceNodes from a local package.
type LocalPackageReader struct {
Kind string `yaml:"kind,omitempty"`
// PackagePath is the path to the package directory.
PackagePath string `yaml:"path,omitempty"`
// PackageFileName is the name of file containing package metadata.
// It will be used to identify package.
PackageFileName string `yaml:"packageFileName,omitempty"`
// MatchFilesGlob configures Read to only read Resources from files matching any of the
// provided patterns.
// Defaults to ["*.yaml", "*.yml"] if empty. To match all files specify ["*"].
MatchFilesGlob []string `yaml:"matchFilesGlob,omitempty"`
// IncludeSubpackages will configure Read to read Resources from subpackages.
// Subpackages are identified by presence of PackageFileName.
IncludeSubpackages bool `yaml:"includeSubpackages,omitempty"`
// ErrorIfNonResources will configure Read to throw an error if yaml missing missing
// apiVersion or kind is read.
ErrorIfNonResources bool `yaml:"errorIfNonResources,omitempty"`
// OmitReaderAnnotations will cause the reader to skip annotating Resources with the file
// path and mode.
OmitReaderAnnotations bool `yaml:"omitReaderAnnotations,omitempty"`
// SetAnnotations are annotations to set on the Resources as they are read.
SetAnnotations map[string]string `yaml:"setAnnotations,omitempty"`
// FileSkipFunc is a function which returns true if reader should ignore
// the file
FileSkipFunc LocalPackageSkipFileFunc
}
var _ Reader = LocalPackageReader{}
var DefaultMatch = []string{"*.yaml", "*.yml"}
var JSONMatch = []string{"*.json"}
var MatchAll = append(DefaultMatch, JSONMatch...)
// Read reads the Resources.
func (r LocalPackageReader) Read() ([]*yaml.RNode, error) {
if r.PackagePath == "" {
return nil, fmt.Errorf("must specify package path")
}
// use slash for path
r.PackagePath = filepath.ToSlash(r.PackagePath)
if len(r.MatchFilesGlob) == 0 {
r.MatchFilesGlob = DefaultMatch
}
var operand ResourceNodeSlice
var pathRelativeTo string
var err error
ignoreFilesMatcher := &ignoreFilesMatcher{}
r.PackagePath, err = filepath.Abs(r.PackagePath)
if err != nil {
return nil, errors.Wrap(err)
}
err = filepath.Walk(r.PackagePath, func(
path string, info os.FileInfo, err error) error {
if err != nil {
return errors.Wrap(err)
}
// is this the user specified path?
if path == r.PackagePath {
if info.IsDir() {
// skip the root package directory, but check for a
// .krmignore file first.
pathRelativeTo = r.PackagePath
return ignoreFilesMatcher.readIgnoreFile(path)
}
// user specified path is a file rather than a directory.
// make its path relative to its parent so it can be written to another file.
pathRelativeTo = filepath.Dir(r.PackagePath)
}
// check if we should skip the directory or file
if info.IsDir() {
return r.shouldSkipDir(path, ignoreFilesMatcher)
}
// get the relative path to file within the package so we can write the files back out
// to another location.
relPath, err := filepath.Rel(pathRelativeTo, path)
if err != nil {
return errors.WrapPrefixf(err, pathRelativeTo)
}
if match, err := r.shouldSkipFile(path, relPath, ignoreFilesMatcher); err != nil {
return err
} else if match {
// skip this file
return nil
}
r.initReaderAnnotations(relPath, info)
nodes, err := r.readFile(path, info)
if err != nil {
return errors.WrapPrefixf(err, path)
}
operand = append(operand, nodes...)
return nil
})
return operand, err
}
// readFile reads the ResourceNodes from a file
func (r *LocalPackageReader) readFile(path string, _ os.FileInfo) ([]*yaml.RNode, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
rr := &ByteReader{
DisableUnwrapping: true,
Reader: f,
OmitReaderAnnotations: r.OmitReaderAnnotations,
SetAnnotations: r.SetAnnotations,
}
return rr.Read()
}
// shouldSkipFile returns true if the file should be skipped
func (r *LocalPackageReader) shouldSkipFile(path, relPath string, matcher *ignoreFilesMatcher) (bool, error) {
// check if the file is covered by a .krmignore file.
if matcher.matchFile(path) {
return true, nil
}
if r.FileSkipFunc != nil && r.FileSkipFunc(relPath) {
return true, nil
}
// check if the files are in scope
for _, g := range r.MatchFilesGlob {
if match, err := filepath.Match(g, filepath.Base(path)); err != nil {
return true, errors.Wrap(err)
} else if match {
return false, nil
}
}
return true, nil
}
// initReaderAnnotations adds the LocalPackageReader Annotations to r.SetAnnotations
func (r *LocalPackageReader) initReaderAnnotations(path string, _ os.FileInfo) {
if r.SetAnnotations == nil {
r.SetAnnotations = map[string]string{}
}
if !r.OmitReaderAnnotations {
r.SetAnnotations[kioutil.PathAnnotation] = path
}
}
// shouldSkipDir returns a filepath.SkipDir if the directory should be skipped
func (r *LocalPackageReader) shouldSkipDir(path string, matcher *ignoreFilesMatcher) error {
if matcher.matchDir(path) {
return filepath.SkipDir
}
if r.PackageFileName == "" {
return nil
}
// check if this is a subpackage
_, err := os.Stat(filepath.Join(path, r.PackageFileName))
if os.IsNotExist(err) {
return nil
} else if err != nil {
return errors.Wrap(err)
}
if !r.IncludeSubpackages {
return filepath.SkipDir
}
return matcher.readIgnoreFile(path)
}

152
vendor/sigs.k8s.io/kustomize/kyaml/kio/pkgio_writer.go generated vendored Normal file
View File

@@ -0,0 +1,152 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kio
import (
"fmt"
"os"
"path/filepath"
"strings"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// LocalPackageWriter writes ResourceNodes to a filesystem
type LocalPackageWriter struct {
Kind string `yaml:"kind,omitempty"`
// PackagePath is the path to the package directory.
PackagePath string `yaml:"path,omitempty"`
// KeepReaderAnnotations if set will retain the annotations set by LocalPackageReader
KeepReaderAnnotations bool `yaml:"keepReaderAnnotations,omitempty"`
// ClearAnnotations will clear annotations before writing the resources
ClearAnnotations []string `yaml:"clearAnnotations,omitempty"`
}
var _ Writer = LocalPackageWriter{}
func (r LocalPackageWriter) Write(nodes []*yaml.RNode) error {
// set the path and index annotations if they are missing
if err := kioutil.DefaultPathAndIndexAnnotation("", nodes); err != nil {
return err
}
if s, err := os.Stat(r.PackagePath); err != nil {
return err
} else if !s.IsDir() {
// if the user specified input isn't a directory, the package is the directory of the
// target
r.PackagePath = filepath.Dir(r.PackagePath)
}
// setup indexes for writing Resources back to files
if err := r.errorIfMissingRequiredAnnotation(nodes); err != nil {
return err
}
outputFiles, err := r.indexByFilePath(nodes)
if err != nil {
return err
}
for k := range outputFiles {
if err = kioutil.SortNodes(outputFiles[k]); err != nil {
return errors.Wrap(err)
}
}
if !r.KeepReaderAnnotations {
r.ClearAnnotations = append(r.ClearAnnotations, kioutil.PathAnnotation)
}
// validate outputs before writing any
for path := range outputFiles {
outputPath := filepath.Join(r.PackagePath, path)
if st, err := os.Stat(outputPath); !os.IsNotExist(err) {
if err != nil {
return errors.Wrap(err)
}
if st.IsDir() {
return fmt.Errorf("config.kubernetes.io/path cannot be a directory: %s", path)
}
}
err = os.MkdirAll(filepath.Dir(outputPath), 0700)
if err != nil {
return errors.Wrap(err)
}
}
// write files
for path := range outputFiles {
outputPath := filepath.Join(r.PackagePath, path)
err = os.MkdirAll(filepath.Dir(filepath.Join(r.PackagePath, path)), 0700)
if err != nil {
return errors.Wrap(err)
}
f, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(0600))
if err != nil {
return errors.Wrap(err)
}
if err := func() error {
defer f.Close()
w := ByteWriter{
Writer: f,
KeepReaderAnnotations: r.KeepReaderAnnotations,
ClearAnnotations: r.ClearAnnotations,
}
if err = w.Write(outputFiles[path]); err != nil {
return errors.Wrap(err)
}
return nil
}(); err != nil {
return errors.Wrap(err)
}
}
return nil
}
func (r LocalPackageWriter) errorIfMissingRequiredAnnotation(nodes []*yaml.RNode) error {
for i := range nodes {
for _, s := range requiredResourcePackageAnnotations {
key, err := nodes[i].Pipe(yaml.GetAnnotation(s))
if err != nil {
return errors.Wrap(err)
}
if key == nil || key.YNode() == nil || key.YNode().Value == "" {
return errors.Errorf(
"resources must be annotated with %s to be written to files", s)
}
}
}
return nil
}
func (r LocalPackageWriter) indexByFilePath(nodes []*yaml.RNode) (map[string][]*yaml.RNode, error) {
outputFiles := map[string][]*yaml.RNode{}
for i := range nodes {
// parse the file write path
node := nodes[i]
value, err := node.Pipe(yaml.GetAnnotation(kioutil.PathAnnotation))
if err != nil {
// this should never happen if errorIfMissingRequiredAnnotation was run
return nil, errors.Wrap(err)
}
path := value.YNode().Value
outputFiles[path] = append(outputFiles[path], node)
if filepath.IsAbs(path) {
return nil, errors.Errorf("package paths may not be absolute paths")
}
if strings.Contains(filepath.Clean(path), "..") {
return nil, fmt.Errorf("resource must be written under package %s: %s",
r.PackagePath, filepath.Clean(path))
}
}
return outputFiles, nil
}

55
vendor/sigs.k8s.io/kustomize/kyaml/kio/testing.go generated vendored Normal file
View File

@@ -0,0 +1,55 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kio
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
)
// Setup creates directories and files for testing
type Setup struct {
// root is the tmp directory
Root string
}
// setupDirectories creates directories for reading test configuration from
func SetupDirectories(t *testing.T, dirs ...string) Setup {
d, err := ioutil.TempDir("", "kyaml-test")
if !assert.NoError(t, err) {
assert.FailNow(t, err.Error())
}
err = os.Chdir(d)
if !assert.NoError(t, err) {
assert.FailNow(t, err.Error())
}
for _, s := range dirs {
err = os.MkdirAll(s, 0700)
if !assert.NoError(t, err) {
assert.FailNow(t, err.Error())
}
}
return Setup{Root: d}
}
// writeFile writes a file under the test directory
func (s Setup) WriteFile(t *testing.T, path string, value []byte) {
err := os.MkdirAll(filepath.Dir(filepath.Join(s.Root, path)), 0700)
if !assert.NoError(t, err) {
assert.FailNow(t, err.Error())
}
err = ioutil.WriteFile(filepath.Join(s.Root, path), value, 0600)
if !assert.NoError(t, err) {
assert.FailNow(t, err.Error())
}
}
// clean deletes the test config
func (s Setup) Clean() {
os.RemoveAll(s.Root)
}

514
vendor/sigs.k8s.io/kustomize/kyaml/kio/tree.go generated vendored Normal file
View File

@@ -0,0 +1,514 @@
// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0
package kio
import (
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/xlab/treeprint"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
type TreeStructure string
const (
// TreeStructurePackage configures TreeWriter to generate the tree structure off of the
// Resources packages.
TreeStructurePackage TreeStructure = "directory"
// TreeStructureOwners configures TreeWriter to generate the tree structure off of the
// Resource owners.
TreeStructureGraph TreeStructure = "owners"
)
var GraphStructures = []string{string(TreeStructureGraph), string(TreeStructurePackage)}
// TreeWriter prints the package structured as a tree.
// TODO(pwittrock): test this package better. it is lower-risk since it is only
// used for printing rather than updating or editing.
type TreeWriter struct {
Writer io.Writer
Root string
Fields []TreeWriterField
Structure TreeStructure
OpenAPIFileName string
}
// TreeWriterField configures a Resource field to be included in the tree
type TreeWriterField struct {
yaml.PathMatcher
Name string
SubName string
}
func (p TreeWriter) packageStructure(nodes []*yaml.RNode) error {
indexByPackage := p.index(nodes)
// create the new tree
tree := treeprint.New()
tree.SetValue(p.Root)
// add each package to the tree
treeIndex := map[string]treeprint.Tree{}
keys := p.sort(indexByPackage)
for _, pkg := range keys {
// create a branch for this package -- search for the parent package and create
// the branch under it -- requires that the keys are sorted
branch := tree
for parent, subTree := range treeIndex {
if strings.HasPrefix(pkg, parent) {
// found a package whose path is a prefix to our own, use this
// package if a closer one isn't found
branch = subTree
// don't break, continue searching for more closely related ancestors
}
}
// create a new branch for the package
createOk := pkg != "." // special edge case logic for tree on current working dir
if createOk {
branch = branch.AddBranch(branchName(p.Root, pkg, p.OpenAPIFileName))
}
// cache the branch for this package
treeIndex[pkg] = branch
// print each resource in the package
for i := range indexByPackage[pkg] {
var err error
if _, err = p.doResource(indexByPackage[pkg][i], "", branch); err != nil {
return err
}
}
}
_, err := io.WriteString(p.Writer, tree.String())
return err
}
// branchName takes the root directory and relative path to the directory
// and returns the branch name
func branchName(root, dirRelPath, openAPIFileName string) string {
name := filepath.Base(dirRelPath)
_, err := os.Stat(filepath.Join(root, dirRelPath, openAPIFileName))
if !os.IsNotExist(err) {
// add Pkg: prefix indicating that it is a separate package as it has
// openAPIFile
return fmt.Sprintf("Pkg: %s", name)
}
return name
}
// Write writes the ascii tree to p.Writer
func (p TreeWriter) Write(nodes []*yaml.RNode) error {
switch p.Structure {
case TreeStructurePackage:
return p.packageStructure(nodes)
case TreeStructureGraph:
return p.graphStructure(nodes)
}
// If any resource has an owner reference, default to the graph structure. Otherwise, use package structure.
for _, node := range nodes {
if owners, _ := node.Pipe(yaml.Lookup("metadata", "ownerReferences")); owners != nil {
return p.graphStructure(nodes)
}
}
return p.packageStructure(nodes)
}
// node wraps a tree node, and any children nodes
type node struct {
p TreeWriter
*yaml.RNode
children []*node
}
func (a node) Len() int { return len(a.children) }
func (a node) Swap(i, j int) { a.children[i], a.children[j] = a.children[j], a.children[i] }
func (a node) Less(i, j int) bool {
return compareNodes(a.children[i].RNode, a.children[j].RNode)
}
// Tree adds this node to the root
func (a node) Tree(root treeprint.Tree) error {
sort.Sort(a)
branch := root
var err error
// generate a node for the Resource
if a.RNode != nil {
branch, err = a.p.doResource(a.RNode, "Resource", root)
if err != nil {
return err
}
}
// attach children to the branch
for _, n := range a.children {
if err := n.Tree(branch); err != nil {
return err
}
}
return nil
}
// graphStructure writes the tree using owners for structure
func (p TreeWriter) graphStructure(nodes []*yaml.RNode) error {
resourceToOwner := map[string]*node{}
root := &node{}
// index each of the nodes by their owner
for _, n := range nodes {
ownerVal, err := ownerToString(n)
if err != nil {
return err
}
var owner *node
if ownerVal == "" {
// no owner -- attach to the root
owner = root
} else {
// owner found -- attach to the owner
var found bool
owner, found = resourceToOwner[ownerVal]
if !found {
// initialize the owner if not found
resourceToOwner[ownerVal] = &node{p: p}
owner = resourceToOwner[ownerVal]
}
}
nodeVal, err := nodeToString(n)
if err != nil {
return err
}
val, found := resourceToOwner[nodeVal]
if !found {
// initialize the node if not found -- may have already been initialized if it
// is the owner of another node
resourceToOwner[nodeVal] = &node{p: p}
val = resourceToOwner[nodeVal]
}
val.RNode = n
owner.children = append(owner.children, val)
}
for k, v := range resourceToOwner {
if v.RNode == nil {
return fmt.Errorf(
"owner '%s' not found in input, but found as an owner of input objects", k)
}
}
// print the tree
tree := treeprint.New()
if err := root.Tree(tree); err != nil {
return err
}
_, err := io.WriteString(p.Writer, tree.String())
return err
}
// nodeToString generates a string to identify the node -- matches ownerToString format
func nodeToString(node *yaml.RNode) (string, error) {
meta, err := node.GetMeta()
if err != nil {
return "", err
}
return fmt.Sprintf("%s %s/%s", meta.Kind, meta.Namespace, meta.Name), nil
}
// ownerToString generate a string to identify the owner -- matches nodeToString format
func ownerToString(node *yaml.RNode) (string, error) {
meta, err := node.GetMeta()
if err != nil {
return "", err
}
namespace := meta.Namespace
owners, err := node.Pipe(yaml.Lookup("metadata", "ownerReferences"))
if err != nil {
return "", err
}
if owners == nil {
return "", nil
}
elements, err := owners.Elements()
if err != nil {
return "", err
}
if len(elements) == 0 {
return "", err
}
owner := elements[0]
var kind, name string
if value := owner.Field("kind"); !value.IsNilOrEmpty() {
kind = value.Value.YNode().Value
}
if value := owner.Field("name"); !value.IsNilOrEmpty() {
name = value.Value.YNode().Value
}
return fmt.Sprintf("%s %s/%s", kind, namespace, name), nil
}
// index indexes the Resources by their package
func (p TreeWriter) index(nodes []*yaml.RNode) map[string][]*yaml.RNode {
// index the ResourceNodes by package
indexByPackage := map[string][]*yaml.RNode{}
for i := range nodes {
meta, err := nodes[i].GetMeta()
if err != nil || meta.Kind == "" {
// not a resource
continue
}
pkg := filepath.Dir(meta.Annotations[kioutil.PathAnnotation])
indexByPackage[pkg] = append(indexByPackage[pkg], nodes[i])
}
return indexByPackage
}
func compareNodes(i, j *yaml.RNode) bool {
metai, _ := i.GetMeta()
metaj, _ := j.GetMeta()
pi := metai.Annotations[kioutil.PathAnnotation]
pj := metaj.Annotations[kioutil.PathAnnotation]
// compare file names
if filepath.Base(pi) != filepath.Base(pj) {
return filepath.Base(pi) < filepath.Base(pj)
}
// compare namespace
if metai.Namespace != metaj.Namespace {
return metai.Namespace < metaj.Namespace
}
// compare name
if metai.Name != metaj.Name {
return metai.Name < metaj.Name
}
// compare kind
if metai.Kind != metaj.Kind {
return metai.Kind < metaj.Kind
}
// compare apiVersion
if metai.APIVersion != metaj.APIVersion {
return metai.APIVersion < metaj.APIVersion
}
return true
}
// sort sorts the Resources in the index in display order and returns the ordered
// keys for the index
//
// Packages are sorted by package name
// Resources within a package are sorted by: [filename, namespace, name, kind, apiVersion]
func (p TreeWriter) sort(indexByPackage map[string][]*yaml.RNode) []string {
var keys []string
for k := range indexByPackage {
pkgNodes := indexByPackage[k]
sort.Slice(pkgNodes, func(i, j int) bool { return compareNodes(pkgNodes[i], pkgNodes[j]) })
keys = append(keys, k)
}
// return the package names sorted lexicographically
sort.Strings(keys)
return keys
}
func (p TreeWriter) doResource(leaf *yaml.RNode, metaString string, branch treeprint.Tree) (treeprint.Tree, error) {
meta, _ := leaf.GetMeta()
if metaString == "" {
path := meta.Annotations[kioutil.PathAnnotation]
path = filepath.Base(path)
metaString = path
}
value := fmt.Sprintf("%s %s", meta.Kind, meta.Name)
if len(meta.Namespace) > 0 {
value = fmt.Sprintf("%s %s/%s", meta.Kind, meta.Namespace, meta.Name)
}
fields, err := p.getFields(leaf)
if err != nil {
return nil, err
}
n := branch.AddMetaBranch(metaString, value)
for i := range fields {
field := fields[i]
// do leaf node
if len(field.matchingElementsAndFields) == 0 {
n.AddNode(fmt.Sprintf("%s: %s", field.name, field.value))
continue
}
// do nested nodes
b := n.AddBranch(field.name)
for j := range field.matchingElementsAndFields {
elem := field.matchingElementsAndFields[j]
b := b.AddBranch(elem.name)
for k := range elem.matchingElementsAndFields {
field := elem.matchingElementsAndFields[k]
b.AddNode(fmt.Sprintf("%s: %s", field.name, field.value))
}
}
}
return n, nil
}
// getFields looks up p.Fields from leaf and structures them into treeFields.
// TODO(pwittrock): simplify this function
func (p TreeWriter) getFields(leaf *yaml.RNode) (treeFields, error) {
fieldsByName := map[string]*treeField{}
// index nested and non-nested fields
for i := range p.Fields {
f := p.Fields[i]
seq, err := leaf.Pipe(&f)
if err != nil {
return nil, err
}
if seq == nil {
continue
}
if fieldsByName[f.Name] == nil {
fieldsByName[f.Name] = &treeField{name: f.Name}
}
// non-nested field -- add directly to the treeFields list
if f.SubName == "" {
// non-nested field -- only 1 element
val, err := yaml.String(seq.Content()[0], yaml.Trim, yaml.Flow)
if err != nil {
return nil, err
}
fieldsByName[f.Name].value = val
continue
}
// nested-field -- create a parent elem, and index by the 'match' value
if fieldsByName[f.Name].subFieldByMatch == nil {
fieldsByName[f.Name].subFieldByMatch = map[string]treeFields{}
}
index := fieldsByName[f.Name].subFieldByMatch
for j := range seq.Content() {
elem := seq.Content()[j]
matches := f.Matches[elem]
str, err := yaml.String(elem, yaml.Trim, yaml.Flow)
if err != nil {
return nil, err
}
// map the field by the name of the element
// index the subfields by the matching element so we can put all the fields for the
// same element under the same branch
matchKey := strings.Join(matches, "/")
index[matchKey] = append(index[matchKey], &treeField{name: f.SubName, value: str})
}
}
// iterate over collection of all queried fields in the Resource
for _, field := range fieldsByName {
// iterate over collection of elements under the field -- indexed by element name
for match, subFields := range field.subFieldByMatch {
// create a new element for this collection of fields
// note: we will convert name to an index later, but keep the match for sorting
elem := &treeField{name: match}
field.matchingElementsAndFields = append(field.matchingElementsAndFields, elem)
// iterate over collection of queried fields for the element
for i := range subFields {
// add to the list of fields for this element
elem.matchingElementsAndFields = append(elem.matchingElementsAndFields, subFields[i])
}
}
// clear this cached data
field.subFieldByMatch = nil
}
// put the fields in a list so they are ordered
fieldList := treeFields{}
for _, v := range fieldsByName {
fieldList = append(fieldList, v)
}
// sort the fields
sort.Sort(fieldList)
for i := range fieldList {
field := fieldList[i]
// sort the elements under this field
sort.Sort(field.matchingElementsAndFields)
for i := range field.matchingElementsAndFields {
element := field.matchingElementsAndFields[i]
// sort the elements under a list field by their name
sort.Sort(element.matchingElementsAndFields)
// set the name of the element to its index
element.name = fmt.Sprintf("%d", i)
}
}
return fieldList, nil
}
// treeField wraps a field node
type treeField struct {
// name is the name of the node
name string
// value is the value of the node -- may be empty
value string
// matchingElementsAndFields is a slice of fields that go under this as a branch
matchingElementsAndFields treeFields
// subFieldByMatch caches matchingElementsAndFields indexed by the name of the matching elem
subFieldByMatch map[string]treeFields
}
// treeFields wraps a slice of treeField so they can be sorted
type treeFields []*treeField
func (nodes treeFields) Len() int { return len(nodes) }
func (nodes treeFields) Less(i, j int) bool {
iIndex, iFound := yaml.FieldOrder[nodes[i].name]
jIndex, jFound := yaml.FieldOrder[nodes[j].name]
if iFound && jFound {
return iIndex < jIndex
}
if iFound {
return true
}
if jFound {
return false
}
if nodes[i].name != nodes[j].name {
return nodes[i].name < nodes[j].name
}
if nodes[i].value != nodes[j].value {
return nodes[i].value < nodes[j].value
}
return false
}
func (nodes treeFields) Swap(i, j int) { nodes[i], nodes[j] = nodes[j], nodes[i] }