Upgrade k8s package verison (#5358)

* upgrade k8s package version

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

* Script upgrade and code formatting.

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>

Signed-off-by: hongzhouzi <hongzhouzi@kubesphere.io>
This commit is contained in:
hongzhouzi
2022-11-15 14:56:38 +08:00
committed by GitHub
parent 5f91c1663a
commit 44167aa47a
3106 changed files with 321340 additions and 172080 deletions

View File

@@ -7,6 +7,7 @@ import (
"bytes"
"fmt"
"io"
"regexp"
"sort"
"strings"
@@ -17,7 +18,7 @@ import (
const (
ResourceListKind = "ResourceList"
ResourceListAPIVersion = "config.kubernetes.io/v1alpha1"
ResourceListAPIVersion = "config.kubernetes.io/v1"
)
// ByteReadWriter reads from an input and writes to an output.
@@ -36,9 +37,19 @@ type ByteReadWriter struct {
// the Resources, otherwise they will be cleared.
KeepReaderAnnotations bool
// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent bool
// Style is a style that is set on the Resource Node Document.
Style yaml.Style
// WrapBareSeqNode wraps the bare sequence node document with map node,
// kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this option enables wrapping such bare
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool
FunctionConfig *yaml.RNode
Results *yaml.RNode
@@ -52,14 +63,16 @@ func (rw *ByteReadWriter) Read() ([]*yaml.RNode, error) {
b := &ByteReader{
Reader: rw.Reader,
OmitReaderAnnotations: rw.OmitReaderAnnotations,
PreserveSeqIndent: rw.PreserveSeqIndent,
WrapBareSeqNode: rw.WrapBareSeqNode,
}
val, err := b.Read()
rw.Results = b.Results
if rw.FunctionConfig == nil {
rw.FunctionConfig = b.FunctionConfig
}
rw.Results = b.Results
if !rw.NoWrap {
if !rw.NoWrap && rw.WrappingKind == "" {
rw.WrappingAPIVersion = b.WrappingAPIVersion
rw.WrappingKind = b.WrappingKind
}
@@ -67,15 +80,18 @@ func (rw *ByteReadWriter) Read() ([]*yaml.RNode, error) {
}
func (rw *ByteReadWriter) Write(nodes []*yaml.RNode) error {
return ByteWriter{
w := ByteWriter{
Writer: rw.Writer,
KeepReaderAnnotations: rw.KeepReaderAnnotations,
Style: rw.Style,
FunctionConfig: rw.FunctionConfig,
Results: rw.Results,
WrappingAPIVersion: rw.WrappingAPIVersion,
WrappingKind: rw.WrappingKind,
}.Write(nodes)
}
if !rw.NoWrap {
w.WrappingAPIVersion = rw.WrappingAPIVersion
w.WrappingKind = rw.WrappingKind
}
return w.Write(nodes)
}
// ParseAll reads all of the inputs into resources
@@ -89,6 +105,7 @@ func ParseAll(inputs ...string) ([]*yaml.RNode, error) {
func FromBytes(bs []byte) ([]*yaml.RNode, error) {
return (&ByteReader{
OmitReaderAnnotations: true,
AnchorsAweigh: true,
Reader: bytes.NewBuffer(bs),
}).Read()
}
@@ -108,9 +125,12 @@ type ByteReader struct {
Reader io.Reader
// OmitReaderAnnotations will configures Read to skip setting the config.kubernetes.io/index
// annotation on Resources as they are Read.
// and internal.config.kubernetes.io/seqindent annotations on Resources as they are Read.
OmitReaderAnnotations bool
// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent 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
@@ -129,11 +149,57 @@ type ByteReader struct {
// WrappingKind is set by Read(), and is the kind of the object that
// the read objects were originally wrapped in.
WrappingKind string
// WrapBareSeqNode wraps the bare sequence node document with map node,
// kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this option enables wrapping such bare
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool
// AnchorsAweigh set to true attempts to replace all YAML anchor aliases
// with their definitions (anchor values) immediately after the read.
AnchorsAweigh bool
}
var _ Reader = &ByteReader{}
// splitDocuments returns a slice of all documents contained in a YAML string. Multiple documents can be divided by the
// YAML document separator (---). It allows for white space and comments to be after the separator on the same line,
// but will return an error if anything else is on the line.
func splitDocuments(s string) ([]string, error) {
docs := make([]string, 0)
if len(s) > 0 {
// The YAML document separator is any line that starts with ---
yamlSeparatorRegexp := regexp.MustCompile(`\n---.*\n`)
// Find all separators, check them for invalid content, and append each document to docs
separatorLocations := yamlSeparatorRegexp.FindAllStringIndex(s, -1)
prev := 0
for i := range separatorLocations {
loc := separatorLocations[i]
separator := s[loc[0]:loc[1]]
// If the next non-whitespace character on the line following the separator is not a comment, return an error
trimmedContentAfterSeparator := strings.TrimSpace(separator[4:])
if len(trimmedContentAfterSeparator) > 0 && trimmedContentAfterSeparator[0] != '#' {
return nil, errors.Errorf("invalid document separator: %s", strings.TrimSpace(separator))
}
docs = append(docs, s[prev:loc[0]])
prev = loc[1]
}
docs = append(docs, s[prev:])
}
return docs, nil
}
func (r *ByteReader) Read() ([]*yaml.RNode, error) {
if r.PreserveSeqIndent && r.OmitReaderAnnotations {
return nil, errors.Errorf(`"PreserveSeqIndent" option adds a reader annotation, please set "OmitReaderAnnotations" to false`)
}
output := ResourceNodeSlice{}
// by manually splitting resources -- otherwise the decoder will get the Resource
@@ -144,8 +210,12 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
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")
// Replace the ending \r\n (line ending used in windows) with \n and then split it into multiple YAML documents
// if it contains document separators (---)
values, err := splitDocuments(strings.ReplaceAll(input.String(), "\r\n", "\n"))
if err != nil {
return nil, errors.Wrap(err)
}
index := 0
for i := range values {
@@ -155,10 +225,11 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
values[i] += "\n"
}
decoder := yaml.NewDecoder(bytes.NewBufferString(values[i]))
node, err := r.decode(index, decoder)
node, err := r.decode(values[i], index, decoder)
if err == io.EOF {
continue
}
if err != nil {
return nil, errors.Wrap(err)
}
@@ -206,17 +277,24 @@ func (r *ByteReader) Read() ([]*yaml.RNode, error) {
// increment the index annotation value
index++
}
if r.AnchorsAweigh {
for _, n := range output {
if err = n.DeAnchor(); err != nil {
return nil, err
}
}
}
return output, nil
}
func (r *ByteReader) decode(index int, decoder *yaml.Decoder) (*yaml.RNode, error) {
func (r *ByteReader) decode(originalYAML string, 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)
return nil, errors.WrapPrefixf(err, "MalformedYAMLError")
}
if yaml.IsYNodeEmptyDoc(node) {
@@ -227,11 +305,34 @@ func (r *ByteReader) decode(index int, decoder *yaml.Decoder) (*yaml.RNode, erro
// sort the annotations by key so the output Resources is consistent (otherwise the
// annotations will be in a random order)
n := yaml.NewRNode(node)
// check if it is a bare sequence node and wrap it with a yaml.BareSeqNodeWrappingKey
if r.WrapBareSeqNode && node.Kind == yaml.DocumentNode && len(node.Content) > 0 &&
node.Content[0] != nil && node.Content[0].Kind == yaml.SequenceNode {
wrappedNode := yaml.NewRNode(&yaml.Node{
Kind: yaml.MappingNode,
})
wrappedNode.PipeE(yaml.SetField(yaml.BareSeqNodeWrappingKey, n))
n = wrappedNode
}
if r.SetAnnotations == nil {
r.SetAnnotations = map[string]string{}
}
if !r.OmitReaderAnnotations {
err := kioutil.CopyLegacyAnnotations(n)
if err != nil {
return nil, err
}
r.SetAnnotations[kioutil.IndexAnnotation] = fmt.Sprintf("%d", index)
r.SetAnnotations[kioutil.LegacyIndexAnnotation] = fmt.Sprintf("%d", index)
if r.PreserveSeqIndent {
// derive and add the seqindent annotation
seqIndentStyle := yaml.DeriveSeqIndentStyle(originalYAML)
if seqIndentStyle != "" {
r.SetAnnotations[kioutil.SeqIndentAnnotation] = seqIndentStyle
}
}
}
var keys []string
for k := range r.SetAnnotations {
@@ -244,5 +345,5 @@ func (r *ByteReader) decode(index int, decoder *yaml.Decoder) (*yaml.RNode, erro
return nil, errors.Wrap(err)
}
}
return yaml.NewRNode(node), nil
return n, nil
}

View File

@@ -6,13 +6,17 @@ package kio
import (
"encoding/json"
"io"
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
// Writer writes ResourceNodes to bytes.
// ByteWriter writes ResourceNodes to bytes. Generally YAML encoding will be used but in the special
// case of writing a single, bare yaml.RNode that has a kioutil.PathAnnotation indicating that the
// target is a JSON file JSON encoding is used. See shouldJSONEncodeSingleBareNode below for more
// information.
type ByteWriter struct {
// Writer is where ResourceNodes are encoded.
Writer io.Writer
@@ -47,16 +51,24 @@ type ByteWriter struct {
var _ Writer = ByteWriter{}
func (w ByteWriter) Write(nodes []*yaml.RNode) error {
yaml.DoSerializationHacksOnNodes(nodes)
func (w ByteWriter) Write(inputNodes []*yaml.RNode) error {
// Copy the nodes to prevent writer from mutating the original nodes.
nodes := copyRNodes(inputNodes)
if w.Sort {
if err := kioutil.SortNodes(nodes); err != nil {
return errors.Wrap(err)
}
}
encoder := yaml.NewEncoder(w.Writer)
defer encoder.Close()
// Even though we use the this value further down we must check this before removing annotations
jsonEncodeSingleBareNode := w.shouldJSONEncodeSingleBareNode(nodes)
// store seqindent annotation value for each node in order to set the encoder indentation
var seqIndentsForNodes []string
for i := range nodes {
seqIndentsForNodes = append(seqIndentsForNodes, nodes[i].GetAnnotations()[kioutil.SeqIndentAnnotation])
}
for i := range nodes {
// clean resources by removing annotations set by the Reader
if !w.KeepReaderAnnotations {
@@ -64,6 +76,15 @@ func (w ByteWriter) Write(nodes []*yaml.RNode) error {
if err != nil {
return errors.Wrap(err)
}
_, err = nodes[i].Pipe(yaml.ClearAnnotation(kioutil.LegacyIndexAnnotation))
if err != nil {
return errors.Wrap(err)
}
_, err = nodes[i].Pipe(yaml.ClearAnnotation(kioutil.SeqIndentAnnotation))
if err != nil {
return errors.Wrap(err)
}
}
for _, a := range w.ClearAnnotations {
_, err := nodes[i].Pipe(yaml.ClearAnnotation(a))
@@ -81,11 +102,24 @@ func (w ByteWriter) Write(nodes []*yaml.RNode) error {
}
}
if jsonEncodeSingleBareNode {
encoder := json.NewEncoder(w.Writer)
encoder.SetIndent("", " ")
return errors.Wrap(encoder.Encode(nodes[0]))
}
encoder := yaml.NewEncoder(w.Writer)
defer encoder.Close()
// don't wrap the elements
if w.WrappingKind == "" {
for i := range nodes {
if err := w.encode(encoder, nodes[i].Document()); err != nil {
return err
if seqIndentsForNodes[i] == string(yaml.WideSequenceStyle) {
encoder.DefaultSeqIndent()
} else {
encoder.CompactSeqIndent()
}
if err := encoder.Encode(upWrapBareSequenceNode(nodes[i].Document())); err != nil {
return errors.Wrap(err)
}
}
return nil
@@ -118,23 +152,47 @@ func (w ByteWriter) Write(nodes []*yaml.RNode) error {
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)
}
func copyRNodes(in []*yaml.RNode) []*yaml.RNode {
out := make([]*yaml.RNode, len(in))
for i := range in {
out[i] = in[i].Copy()
}
return out
}
// shouldJSONEncodeSingleBareNode determines if nodes contain a single node that should not be
// wrapped and has a JSON file extension, which in turn means that the node should be JSON encoded.
// Note 1: this must be checked before any annotations to avoid losing information about the target
// filename extension.
// Note 2: JSON encoding should only be used for single, unwrapped nodes because multiple unwrapped
// nodes cannot be represented in JSON (no multi doc support). Furthermore, the typical use
// cases for wrapping nodes would likely not include later writing the whole wrapper to a
// .json file, i.e. there is no point risking any edge case information loss e.g. comments
// disappearing, that could come from JSON encoding the whole wrapper just to ensure that
// one (or all nodes) can be read as JSON.
func (w ByteWriter) shouldJSONEncodeSingleBareNode(nodes []*yaml.RNode) bool {
if w.WrappingKind == "" && len(nodes) == 1 {
if path, _, _ := kioutil.GetFileAnnotations(nodes[0]); path != "" {
filename := filepath.Base(path)
for _, glob := range JSONMatch {
if match, _ := filepath.Match(glob, filename); match {
return true
}
}
}
}
return false
}
// upWrapBareSequenceNode unwraps the bare sequence nodes wrapped by yaml.BareSeqNodeWrappingKey
func upWrapBareSequenceNode(node *yaml.Node) *yaml.Node {
rNode := yaml.NewRNode(node)
seqNode, err := rNode.Pipe(yaml.Lookup(yaml.BareSeqNodeWrappingKey))
if err == nil && !seqNode.IsNilOrEmpty() {
return seqNode.YNode()
}
return node
}

View File

@@ -165,6 +165,10 @@ func (f *FileSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
resources := map[string][]*yaml.RNode{}
for i := range input {
if err := kioutil.CopyLegacyAnnotations(input[i]); err != nil {
return nil, err
}
m, err := input[i].GetMeta()
if err != nil {
return nil, err
@@ -178,6 +182,9 @@ func (f *FileSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
if _, err := input[i].Pipe(yaml.SetAnnotation(kioutil.PathAnnotation, file)); err != nil {
return nil, err
}
if _, err := input[i].Pipe(yaml.SetAnnotation(kioutil.LegacyPathAnnotation, file)); err != nil {
return nil, err
}
}
resources[file] = append(resources[file], input[i])
}
@@ -192,6 +199,10 @@ func (f *FileSetter) Filter(input []*yaml.RNode) ([]*yaml.RNode, error) {
yaml.SetAnnotation(kioutil.IndexAnnotation, fmt.Sprintf("%d", j))); err != nil {
return nil, err
}
if _, err := resources[i][j].Pipe(
yaml.SetAnnotation(kioutil.LegacyIndexAnnotation, fmt.Sprintf("%d", j))); err != nil {
return nil, err
}
output = append(output, resources[i][j])
}
}

View File

@@ -19,17 +19,55 @@ const (
mergeSourceDest = "dest"
)
// ResourceMatcher interface is used to match two resources based on IsSameResource implementation
// This is the way to group same logical resources in upstream, local and origin for merge
// The default way to group them is using GVKNN similar to how kubernetes server identifies resources
// Users of this library might have their own interpretation of grouping similar resources
// for e.g. if consumer adds a name-prefix to local resource, it should not be treated as new resource
// for updates etc.
// Hence, the callers of this library may pass different implementation for IsSameResource
type ResourceMatcher interface {
IsSameResource(node1, node2 *yaml.RNode) bool
}
// ResourceMergeStrategy is the return type from the Handle function in the
// ResourceHandler interface. It determines which version of a resource should
// be included in the output (if any).
type ResourceMergeStrategy int
const (
// Merge means the output to dest should be the 3-way merge of original,
// updated and dest.
Merge ResourceMergeStrategy = iota
// KeepDest means the version of the resource in dest should be the output.
KeepDest
// KeepUpdated means the version of the resource in updated should be the
// output.
KeepUpdated
// KeepOriginal means the version of the resource in original should be the
// output.
KeepOriginal
// Skip means the resource should not be included in the output.
Skip
)
// ResourceHandler interface is used to determine what should be done for a
// resource once the versions in original, updated and dest has been
// identified based on the ResourceMatcher. This allows users to customize
// what should be the result in dest if a resource has been deleted from
// upstream.
type ResourceHandler interface {
Handle(original, updated, dest *yaml.RNode) (ResourceMergeStrategy, error)
}
// 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
Matcher ResourceMatcher
Handler ResourceHandler
}
func (m Merge3) Merge() error {
@@ -67,7 +105,16 @@ func (m Merge3) Merge() error {
// 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}
matcher := m.Matcher
if matcher == nil {
matcher = &DefaultGVKNNMatcher{MergeOnPath: true}
}
handler := m.Handler
if handler == nil {
handler = &DefaultResourceHandler{}
}
tl := tuples{matcher: matcher}
for i := range nodes {
if err := tl.add(nodes[i]); err != nil {
return nil, err
@@ -78,21 +125,12 @@ func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
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
strategy, err := handler.Handle(t.original, t.updated, t.dest)
if err != nil {
return nil, err
}
switch strategy {
case Merge:
node, err := t.merge()
if err != nil {
return nil, err
@@ -100,6 +138,14 @@ func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
if node != nil {
output = append(output, node)
}
case KeepDest:
output = append(output, t.dest)
case KeepUpdated:
output = append(output, t.updated)
case KeepOriginal:
output = append(output, t.original)
case Skip:
// do nothing
}
}
return output, nil
@@ -109,13 +155,41 @@ func (m Merge3) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
type tuples struct {
list []*tuple
// mergeOnPath if set to true will use the resource filepath
// as part of the merge key
mergeOnPath bool
// matcher matches the resources for merge
matcher ResourceMatcher
}
// isSameResource returns true if meta1 and meta2 are for the same logic resource
func (ts *tuples) isSameResource(meta1, meta2 yaml.ResourceMeta) bool {
// DefaultGVKNNMatcher holds the default matching of resources implementation based on
// Group, Version, Kind, Name and Namespace of the resource
type DefaultGVKNNMatcher struct {
// 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
}
// IsSameResource returns true if metadata of node1 and metadata of node2 belongs to same logical resource
func (dm *DefaultGVKNNMatcher) IsSameResource(node1, node2 *yaml.RNode) bool {
if node1 == nil || node2 == nil {
return false
}
if err := kioutil.CopyLegacyAnnotations(node1); err != nil {
return false
}
if err := kioutil.CopyLegacyAnnotations(node2); err != nil {
return false
}
meta1, err := node1.GetMeta()
if err != nil {
return false
}
meta2, err := node2.GetMeta()
if err != nil {
return false
}
if meta1.Name != meta2.Name {
return false
}
@@ -128,7 +202,7 @@ func (ts *tuples) isSameResource(meta1, meta2 yaml.ResourceMeta) bool {
if meta1.Kind != meta2.Kind {
return false
}
if ts.mergeOnPath {
if dm.MergeOnPath {
// directories may contain multiple copies of a resource with the same
// name, namespace, apiVersion and kind -- e.g. kustomize patches, or
// multiple environments
@@ -143,17 +217,13 @@ func (ts *tuples) isSameResource(meta1, meta2 yaml.ResourceMeta) bool {
// 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) {
if ts.matcher.IsSameResource(addedNode(t), node) {
return t.add(node)
}
}
t := &tuple{meta: nodeMeta}
t := &tuple{}
if err := t.add(node); err != nil {
return err
}
@@ -161,9 +231,19 @@ func (ts *tuples) add(node *yaml.RNode) error {
return nil
}
// addedNode returns one on the existing added nodes in the tuple
func addedNode(t *tuple) *yaml.RNode {
if t.updated != nil {
return t.updated
}
if t.original != nil {
return t.original
}
return t.dest
}
// 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
@@ -178,17 +258,17 @@ func (t *tuple) add(node *yaml.RNode) error {
switch meta.Annotations[mergeSourceAnnotation] {
case mergeSourceDest:
if t.dest != nil {
return fmt.Errorf("dest source already specified")
return duplicateError("local", meta.Annotations[kioutil.PathAnnotation])
}
t.dest = node
case mergeSourceOriginal:
if t.original != nil {
return fmt.Errorf("original source already specified")
return duplicateError("original upstream", meta.Annotations[kioutil.PathAnnotation])
}
t.original = node
case mergeSourceUpdated:
if t.updated != nil {
return fmt.Errorf("updated source already specified")
return duplicateError("updated upstream", meta.Annotations[kioutil.PathAnnotation])
}
t.updated = node
default:
@@ -201,3 +281,37 @@ func (t *tuple) add(node *yaml.RNode) error {
func (t *tuple) merge() (*yaml.RNode, error) {
return merge3.Merge(t.dest, t.original, t.updated)
}
// duplicateError returns duplicate resources error
func duplicateError(source, filePath string) error {
return fmt.Errorf(`found duplicate %q resources in file %q, please refer to "update" documentation for the fix`, source, filePath)
}
// DefaultResourceHandler is the default implementation of the ResourceHandler
// interface. It uses the following rules:
// * Keep dest if resource only exists in dest.
// * Keep updated if resource added in updated.
// * Delete dest if updated has been deleted.
// * Don't add the resource back if removed from dest.
// * Otherwise merge.
type DefaultResourceHandler struct{}
func (*DefaultResourceHandler) Handle(original, updated, dest *yaml.RNode) (ResourceMergeStrategy, error) {
switch {
case original == nil && updated == nil && dest != nil:
// added locally -- keep dest
return KeepDest, nil
case updated != nil && dest == nil:
// added in the update -- add update
return KeepUpdated, nil
case original != nil && updated == nil:
// deleted in the update
return Skip, nil
case original != nil && dest == nil:
// deleted locally
return Skip, nil
default:
// dest and updated are non-nil -- merge them
return Merge, nil
}
}

View File

@@ -4,12 +4,14 @@
package kio
import (
"errors"
"os"
"path/filepath"
"strings"
"github.com/monochromegane/go-gitignore"
gitignore "github.com/monochromegane/go-gitignore"
"sigs.k8s.io/kustomize/kyaml/ext"
"sigs.k8s.io/kustomize/kyaml/filesys"
)
// ignoreFilesMatcher handles `.krmignore` files, which allows for ignoring
@@ -32,6 +34,7 @@ import (
// is set to true
type ignoreFilesMatcher struct {
matchers []matcher
fs filesys.FileSystemOrOnDisk
}
// readIgnoreFile checks whether there is a .krmignore file in the path, and
@@ -39,9 +42,9 @@ type ignoreFilesMatcher struct {
// 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()))
f, err := i.fs.Open(filepath.Join(path, ext.IgnoreFileName()))
if err != nil {
if os.IsNotExist(err) {
if errors.Is(err, os.ErrNotExist) {
i.matchers = append(i.matchers, matcher{
matcher: gitignore.DummyIgnoreMatcher(false),
basePath: path,
@@ -50,8 +53,10 @@ func (i *ignoreFilesMatcher) readIgnoreFile(path string) error {
}
return err
}
defer f.Close()
i.matchers = append(i.matchers, matcher{
matcher: m,
matcher: gitignore.NewGitIgnoreFromReader(path, f),
basePath: path,
})
return nil

View File

@@ -6,7 +6,11 @@
package kio
import (
"fmt"
"strconv"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -53,6 +57,13 @@ type Filter interface {
Filter([]*yaml.RNode) ([]*yaml.RNode, error)
}
// TrackableFilter is an extension of Filter which is also capable of tracking
// which fields were mutated by the filter.
type TrackableFilter interface {
Filter
WithMutationTracker(func(key, value, tag string, node *yaml.RNode))
}
// FilterFunc implements a Filter as a function.
type FilterFunc func([]*yaml.RNode) ([]*yaml.RNode, error)
@@ -111,8 +122,14 @@ func (p Pipeline) ExecuteWithCallback(callback PipelineExecuteCallbackFunc) erro
}
// apply operations
var err error
for i := range p.Filters {
// Not all RNodes passed through kio.Pipeline have metadata nor should
// they all be required to.
nodeAnnos, err := PreprocessResourcesForInternalAnnotationMigration(result)
if err != nil {
return err
}
op := p.Filters[i]
if callback != nil {
callback(op)
@@ -124,6 +141,13 @@ func (p Pipeline) ExecuteWithCallback(callback PipelineExecuteCallbackFunc) erro
if len(result) == 0 && !p.ContinueOnEmptyResult || err != nil {
return errors.Wrap(err)
}
// If either the internal annotations for path, index, and id OR the legacy
// annotations for path, index, and id are changed, we have to update the other.
err = ReconcileInternalAnnotations(result, nodeAnnos)
if err != nil {
return err
}
}
// write to the outputs
@@ -147,3 +171,277 @@ func FilterAll(filter yaml.Filter) Filter {
return nodes, nil
})
}
// PreprocessResourcesForInternalAnnotationMigration returns a mapping from id to all
// internal annotations, so that we can use it to reconcile the annotations
// later. This is necessary because currently both internal-prefixed annotations
// and legacy annotations are currently supported, and a change to one must be
// reflected in the other if needed.
func PreprocessResourcesForInternalAnnotationMigration(result []*yaml.RNode) (map[string]map[string]string, error) {
idToAnnosMap := make(map[string]map[string]string)
for i := range result {
idStr := strconv.Itoa(i)
err := result[i].PipeE(yaml.SetAnnotation(kioutil.InternalAnnotationsMigrationResourceIDAnnotation, idStr))
if err != nil {
return nil, err
}
idToAnnosMap[idStr] = kioutil.GetInternalAnnotations(result[i])
if err = kioutil.CopyLegacyAnnotations(result[i]); err != nil {
return nil, err
}
meta, _ := result[i].GetMeta()
if err = checkMismatchedAnnos(meta.Annotations); err != nil {
return nil, err
}
}
return idToAnnosMap, nil
}
func checkMismatchedAnnos(annotations map[string]string) error {
path := annotations[kioutil.PathAnnotation]
index := annotations[kioutil.IndexAnnotation]
id := annotations[kioutil.IdAnnotation]
legacyPath := annotations[kioutil.LegacyPathAnnotation]
legacyIndex := annotations[kioutil.LegacyIndexAnnotation]
legacyId := annotations[kioutil.LegacyIdAnnotation]
// if prior to running the functions, the legacy and internal annotations differ,
// throw an error as we cannot infer the user's intent.
if path != "" && legacyPath != "" && path != legacyPath {
return fmt.Errorf("resource input to function has mismatched legacy and internal path annotations")
}
if index != "" && legacyIndex != "" && index != legacyIndex {
return fmt.Errorf("resource input to function has mismatched legacy and internal index annotations")
}
if id != "" && legacyId != "" && id != legacyId {
return fmt.Errorf("resource input to function has mismatched legacy and internal id annotations")
}
return nil
}
type nodeAnnotations struct {
path string
index string
id string
}
// ReconcileInternalAnnotations reconciles the annotation format for path, index and id annotations.
// It will ensure the output annotation format matches the format in the input. e.g. if the input
// format uses the legacy format and the output will be converted to the legacy format if it's not.
func ReconcileInternalAnnotations(result []*yaml.RNode, nodeAnnosMap map[string]map[string]string) error {
useInternal, useLegacy, err := determineAnnotationsFormat(nodeAnnosMap)
if err != nil {
return err
}
for i := range result {
// if only one annotation is set, set the other.
err = missingInternalOrLegacyAnnotations(result[i])
if err != nil {
return err
}
// we must check to see if the function changed either the new internal annotations
// or the old legacy annotations. If one is changed, the change must be reflected
// in the other.
err = checkAnnotationsAltered(result[i], nodeAnnosMap)
if err != nil {
return err
}
// We invoke determineAnnotationsFormat to find out if the original annotations
// use the internal or (and) the legacy format. We format the resources to
// make them consistent with original format.
err = formatInternalAnnotations(result[i], useInternal, useLegacy)
if err != nil {
return err
}
// if the annotations are still somehow out of sync, throw an error
meta, _ := result[i].GetMeta()
err = checkMismatchedAnnos(meta.Annotations)
if err != nil {
return err
}
if _, err = result[i].Pipe(yaml.ClearAnnotation(kioutil.InternalAnnotationsMigrationResourceIDAnnotation)); err != nil {
return err
}
}
return nil
}
// determineAnnotationsFormat determines if the resources are using one of the internal and legacy annotation format or both of them.
func determineAnnotationsFormat(nodeAnnosMap map[string]map[string]string) (bool, bool, error) {
var useInternal, useLegacy bool
var err error
if len(nodeAnnosMap) == 0 {
return true, true, nil
}
var internal, legacy *bool
for _, annos := range nodeAnnosMap {
_, foundPath := annos[kioutil.PathAnnotation]
_, foundIndex := annos[kioutil.IndexAnnotation]
_, foundId := annos[kioutil.IdAnnotation]
_, foundLegacyPath := annos[kioutil.LegacyPathAnnotation]
_, foundLegacyIndex := annos[kioutil.LegacyIndexAnnotation]
_, foundLegacyId := annos[kioutil.LegacyIdAnnotation]
if !(foundPath || foundIndex || foundId || foundLegacyPath || foundLegacyIndex || foundLegacyId) {
continue
}
foundOneOf := foundPath || foundIndex || foundId
if internal == nil {
f := foundOneOf
internal = &f
}
if (foundOneOf && !*internal) || (!foundOneOf && *internal) {
err = fmt.Errorf("the annotation formatting in the input resources is not consistent")
return useInternal, useLegacy, err
}
foundOneOf = foundLegacyPath || foundLegacyIndex || foundLegacyId
if legacy == nil {
f := foundOneOf
legacy = &f
}
if (foundOneOf && !*legacy) || (!foundOneOf && *legacy) {
err = fmt.Errorf("the annotation formatting in the input resources is not consistent")
return useInternal, useLegacy, err
}
}
if internal != nil {
useInternal = *internal
}
if legacy != nil {
useLegacy = *legacy
}
return useInternal, useLegacy, err
}
func missingInternalOrLegacyAnnotations(rn *yaml.RNode) error {
if err := missingInternalOrLegacyAnnotation(rn, kioutil.PathAnnotation, kioutil.LegacyPathAnnotation); err != nil {
return err
}
if err := missingInternalOrLegacyAnnotation(rn, kioutil.IndexAnnotation, kioutil.LegacyIndexAnnotation); err != nil {
return err
}
if err := missingInternalOrLegacyAnnotation(rn, kioutil.IdAnnotation, kioutil.LegacyIdAnnotation); err != nil {
return err
}
return nil
}
func missingInternalOrLegacyAnnotation(rn *yaml.RNode, newKey string, legacyKey string) error {
meta, _ := rn.GetMeta()
annotations := meta.Annotations
value := annotations[newKey]
legacyValue := annotations[legacyKey]
if value == "" && legacyValue == "" {
// do nothing
return nil
}
if value == "" {
// new key is not set, copy from legacy key
if err := rn.PipeE(yaml.SetAnnotation(newKey, legacyValue)); err != nil {
return err
}
} else if legacyValue == "" {
// legacy key is not set, copy from new key
if err := rn.PipeE(yaml.SetAnnotation(legacyKey, value)); err != nil {
return err
}
}
return nil
}
func checkAnnotationsAltered(rn *yaml.RNode, nodeAnnosMap map[string]map[string]string) error {
meta, _ := rn.GetMeta()
annotations := meta.Annotations
// get the resource's current path, index, and ids from the new annotations
internal := nodeAnnotations{
path: annotations[kioutil.PathAnnotation],
index: annotations[kioutil.IndexAnnotation],
id: annotations[kioutil.IdAnnotation],
}
// get the resource's current path, index, and ids from the legacy annotations
legacy := nodeAnnotations{
path: annotations[kioutil.LegacyPathAnnotation],
index: annotations[kioutil.LegacyIndexAnnotation],
id: annotations[kioutil.LegacyIdAnnotation],
}
rid := annotations[kioutil.InternalAnnotationsMigrationResourceIDAnnotation]
originalAnnotations, found := nodeAnnosMap[rid]
if !found {
return nil
}
originalPath, found := originalAnnotations[kioutil.PathAnnotation]
if !found {
originalPath = originalAnnotations[kioutil.LegacyPathAnnotation]
}
if originalPath != "" {
switch {
case originalPath != internal.path && originalPath != legacy.path && internal.path != legacy.path:
return fmt.Errorf("resource input to function has mismatched legacy and internal path annotations")
case originalPath != internal.path:
if _, err := rn.Pipe(yaml.SetAnnotation(kioutil.LegacyPathAnnotation, internal.path)); err != nil {
return err
}
case originalPath != legacy.path:
if _, err := rn.Pipe(yaml.SetAnnotation(kioutil.PathAnnotation, legacy.path)); err != nil {
return err
}
}
}
originalIndex, found := originalAnnotations[kioutil.IndexAnnotation]
if !found {
originalIndex = originalAnnotations[kioutil.LegacyIndexAnnotation]
}
if originalIndex != "" {
switch {
case originalIndex != internal.index && originalIndex != legacy.index && internal.index != legacy.index:
return fmt.Errorf("resource input to function has mismatched legacy and internal index annotations")
case originalIndex != internal.index:
if _, err := rn.Pipe(yaml.SetAnnotation(kioutil.LegacyIndexAnnotation, internal.index)); err != nil {
return err
}
case originalIndex != legacy.index:
if _, err := rn.Pipe(yaml.SetAnnotation(kioutil.IndexAnnotation, legacy.index)); err != nil {
return err
}
}
}
return nil
}
func formatInternalAnnotations(rn *yaml.RNode, useInternal, useLegacy bool) error {
if !useInternal {
if err := rn.PipeE(yaml.ClearAnnotation(kioutil.IdAnnotation)); err != nil {
return err
}
if err := rn.PipeE(yaml.ClearAnnotation(kioutil.PathAnnotation)); err != nil {
return err
}
if err := rn.PipeE(yaml.ClearAnnotation(kioutil.IndexAnnotation)); err != nil {
return err
}
}
if !useLegacy {
if err := rn.PipeE(yaml.ClearAnnotation(kioutil.LegacyIdAnnotation)); err != nil {
return err
}
if err := rn.PipeE(yaml.ClearAnnotation(kioutil.LegacyPathAnnotation)); err != nil {
return err
}
if err := rn.PipeE(yaml.ClearAnnotation(kioutil.LegacyIndexAnnotation)); err != nil {
return err
}
}
return nil
}

View File

@@ -17,21 +17,99 @@ import (
type AnnotationKey = string
const (
// internalPrefix is the prefix given to internal annotations that are used
// internally by the orchestrator
internalPrefix string = "internal.config.kubernetes.io/"
// IndexAnnotation records the index of a specific resource in a file or input stream.
IndexAnnotation AnnotationKey = "config.kubernetes.io/index"
IndexAnnotation AnnotationKey = internalPrefix + "index"
// PathAnnotation records the path to the file the Resource was read from
PathAnnotation AnnotationKey = "config.kubernetes.io/path"
PathAnnotation AnnotationKey = internalPrefix + "path"
// SeqIndentAnnotation records the sequence nodes indentation of the input resource
SeqIndentAnnotation AnnotationKey = internalPrefix + "seqindent"
// IdAnnotation records the id of the resource to map inputs to outputs
IdAnnotation AnnotationKey = internalPrefix + "id"
// Deprecated: Use IndexAnnotation instead.
LegacyIndexAnnotation AnnotationKey = "config.kubernetes.io/index"
// Deprecated: use PathAnnotation instead.
LegacyPathAnnotation AnnotationKey = "config.kubernetes.io/path"
// Deprecated: use IdAnnotation instead.
LegacyIdAnnotation = "config.k8s.io/id"
// InternalAnnotationsMigrationResourceIDAnnotation is used to uniquely identify
// resources during round trip to and from a function execution. We will use it
// to track the internal annotations and reconcile them if needed.
InternalAnnotationsMigrationResourceIDAnnotation = internalPrefix + "annotations-migration-resource-id"
)
func GetFileAnnotations(rn *yaml.RNode) (string, string, error) {
rm, _ := rn.GetMeta()
annotations := rm.Annotations
path, found := annotations[PathAnnotation]
if !found {
path = annotations[LegacyPathAnnotation]
}
index, found := annotations[IndexAnnotation]
if !found {
index = annotations[LegacyIndexAnnotation]
}
return path, index, nil
}
func GetIdAnnotation(rn *yaml.RNode) string {
rm, _ := rn.GetMeta()
annotations := rm.Annotations
id, found := annotations[IdAnnotation]
if !found {
id = annotations[LegacyIdAnnotation]
}
return id
}
func CopyLegacyAnnotations(rn *yaml.RNode) error {
meta, err := rn.GetMeta()
if err != nil {
return "", "", err
if err == yaml.ErrMissingMetadata {
// resource has no metadata, this should be a no-op
return nil
}
return err
}
path := meta.Annotations[PathAnnotation]
index := meta.Annotations[IndexAnnotation]
return path, index, nil
if err := copyAnnotations(meta, rn, LegacyPathAnnotation, PathAnnotation); err != nil {
return err
}
if err := copyAnnotations(meta, rn, LegacyIndexAnnotation, IndexAnnotation); err != nil {
return err
}
if err := copyAnnotations(meta, rn, LegacyIdAnnotation, IdAnnotation); err != nil {
return err
}
return nil
}
func copyAnnotations(meta yaml.ResourceMeta, rn *yaml.RNode, legacyKey string, newKey string) error {
newValue := meta.Annotations[newKey]
legacyValue := meta.Annotations[legacyKey]
if newValue != "" {
if legacyValue == "" {
if err := rn.PipeE(yaml.SetAnnotation(legacyKey, newValue)); err != nil {
return err
}
}
} else {
if legacyValue != "" {
if err := rn.PipeE(yaml.SetAnnotation(newKey, legacyValue)); err != nil {
return err
}
}
}
return nil
}
// ErrorIfMissingAnnotation validates the provided annotations are present on the given resources
@@ -64,6 +142,9 @@ func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
// check each node for the path annotation
for i := range nodes {
if err := CopyLegacyAnnotations(nodes[i]); err != nil {
return err
}
m, err := nodes[i].GetMeta()
if err != nil {
return err
@@ -88,6 +169,9 @@ func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
if err := nodes[i].PipeE(yaml.SetAnnotation(PathAnnotation, path)); err != nil {
return err
}
if err := nodes[i].PipeE(yaml.SetAnnotation(LegacyPathAnnotation, path)); err != nil {
return err
}
}
// set the index annotations
@@ -110,6 +194,10 @@ func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
yaml.SetAnnotation(IndexAnnotation, fmt.Sprintf("%d", c))); err != nil {
return err
}
if err := nodes[i].PipeE(
yaml.SetAnnotation(LegacyIndexAnnotation, fmt.Sprintf("%d", c))); err != nil {
return err
}
}
return nil
}
@@ -119,6 +207,9 @@ func DefaultPathAndIndexAnnotation(dir string, nodes []*yaml.RNode) error {
func DefaultPathAnnotation(dir string, nodes []*yaml.RNode) error {
// check each node for the path annotation
for i := range nodes {
if err := CopyLegacyAnnotations(nodes[i]); err != nil {
return err
}
m, err := nodes[i].GetMeta()
if err != nil {
return err
@@ -134,6 +225,9 @@ func DefaultPathAnnotation(dir string, nodes []*yaml.RNode) error {
if err := nodes[i].PipeE(yaml.SetAnnotation(PathAnnotation, path)); err != nil {
return err
}
if err := nodes[i].PipeE(yaml.SetAnnotation(LegacyPathAnnotation, path)); err != nil {
return err
}
}
return nil
}
@@ -182,6 +276,12 @@ func SortNodes(nodes []*yaml.RNode) error {
if err != nil {
return false
}
if err := CopyLegacyAnnotations(nodes[i]); err != nil {
return false
}
if err := CopyLegacyAnnotations(nodes[j]); err != nil {
return false
}
var iMeta, jMeta yaml.ResourceMeta
if iMeta, _ = nodes[i].GetMeta(); err != nil {
return false
@@ -231,3 +331,90 @@ func SortNodes(nodes []*yaml.RNode) error {
})
return errors.Wrap(err)
}
// CopyInternalAnnotations copies the annotations that begin with the prefix
// `internal.config.kubernetes.io` from the source RNode to the destination RNode.
// It takes a parameter exclusions, which is a list of annotation keys to ignore.
func CopyInternalAnnotations(src *yaml.RNode, dst *yaml.RNode, exclusions ...AnnotationKey) error {
srcAnnotations := GetInternalAnnotations(src)
for k, v := range srcAnnotations {
if stringSliceContains(exclusions, k) {
continue
}
if err := dst.PipeE(yaml.SetAnnotation(k, v)); err != nil {
return err
}
}
return nil
}
// ConfirmInternalAnnotationUnchanged compares the annotations of the RNodes that begin with the prefix
// `internal.config.kubernetes.io`, throwing an error if they differ. It takes a parameter exclusions,
// which is a list of annotation keys to ignore.
func ConfirmInternalAnnotationUnchanged(r1 *yaml.RNode, r2 *yaml.RNode, exclusions ...AnnotationKey) error {
r1Annotations := GetInternalAnnotations(r1)
r2Annotations := GetInternalAnnotations(r2)
// this is a map to prevent duplicates
diffAnnos := make(map[string]bool)
for k, v1 := range r1Annotations {
if stringSliceContains(exclusions, k) {
continue
}
if v2, ok := r2Annotations[k]; !ok || v1 != v2 {
diffAnnos[k] = true
}
}
for k, v2 := range r2Annotations {
if stringSliceContains(exclusions, k) {
continue
}
if v1, ok := r1Annotations[k]; !ok || v2 != v1 {
diffAnnos[k] = true
}
}
if len(diffAnnos) > 0 {
keys := make([]string, 0, len(diffAnnos))
for k := range diffAnnos {
keys = append(keys, k)
}
sort.Strings(keys)
errorString := "internal annotations differ: "
for _, key := range keys {
errorString = errorString + key + ", "
}
return errors.Errorf(errorString[0 : len(errorString)-2])
}
return nil
}
// GetInternalAnnotations returns a map of all the annotations of the provided
// RNode that satisfies one of the following: 1) begin with the prefix
// `internal.config.kubernetes.io` 2) is one of `config.kubernetes.io/path`,
// `config.kubernetes.io/index` and `config.k8s.io/id`.
func GetInternalAnnotations(rn *yaml.RNode) map[string]string {
meta, _ := rn.GetMeta()
annotations := meta.Annotations
result := make(map[string]string)
for k, v := range annotations {
if strings.HasPrefix(k, internalPrefix) || k == LegacyPathAnnotation || k == LegacyIndexAnnotation || k == LegacyIdAnnotation {
result[k] = v
}
}
return result
}
// stringSliceContains returns true if the slice has the string.
func stringSliceContains(slice []string, str string) bool {
for _, s := range slice {
if s == str {
return true
}
}
return false
}

View File

@@ -9,6 +9,7 @@ import (
"path/filepath"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/sets"
"sigs.k8s.io/kustomize/kyaml/yaml"
@@ -40,6 +41,9 @@ type LocalPackageReadWriter struct {
KeepReaderAnnotations bool `yaml:"keepReaderAnnotations,omitempty"`
// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent bool
// PackagePath is the path to the package directory.
PackagePath string `yaml:"path,omitempty"`
@@ -75,6 +79,16 @@ type LocalPackageReadWriter struct {
// FileSkipFunc is a function which returns true if reader should ignore
// the file
FileSkipFunc LocalPackageSkipFileFunc
// FileSystem can be used to mock the disk file system.
FileSystem filesys.FileSystemOrOnDisk
// WrapBareSeqNode wraps the bare sequence node document with map node,
// kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this option enables wrapping such bare
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool
}
func (r *LocalPackageReadWriter) Read() ([]*yaml.RNode, error) {
@@ -86,6 +100,9 @@ func (r *LocalPackageReadWriter) Read() ([]*yaml.RNode, error) {
SetAnnotations: r.SetAnnotations,
PackageFileName: r.PackageFileName,
FileSkipFunc: r.FileSkipFunc,
PreserveSeqIndent: r.PreserveSeqIndent,
FileSystem: r.FileSystem,
WrapBareSeqNode: r.WrapBareSeqNode,
}.Read()
if err != nil {
return nil, errors.Wrap(err)
@@ -113,13 +130,14 @@ func (r *LocalPackageReadWriter) Write(nodes []*yaml.RNode) error {
PackagePath: r.PackagePath,
ClearAnnotations: clear,
KeepReaderAnnotations: r.KeepReaderAnnotations,
FileSystem: r.FileSystem,
}.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 {
if err = r.FileSystem.RemoveAll(filepath.Join(r.PackagePath, f)); err != nil {
return errors.Wrap(err)
}
}
@@ -177,6 +195,19 @@ type LocalPackageReader struct {
// FileSkipFunc is a function which returns true if reader should ignore
// the file
FileSkipFunc LocalPackageSkipFileFunc
// PreserveSeqIndent if true adds kioutil.SeqIndentAnnotation to each resource
PreserveSeqIndent bool
// FileSystem can be used to mock the disk file system.
FileSystem filesys.FileSystemOrOnDisk
// WrapBareSeqNode wraps the bare sequence node document with map node,
// kyaml uses reader annotations to track resources, it is not possible to
// add them to bare sequence nodes, this option enables wrapping such bare
// sequence nodes into map node with key yaml.BareSeqNodeWrappingKey
// note that this wrapping is different and not related to ResourceList wrapping
WrapBareSeqNode bool
}
var _ Reader = LocalPackageReader{}
@@ -200,12 +231,15 @@ func (r LocalPackageReader) Read() ([]*yaml.RNode, error) {
var operand ResourceNodeSlice
var pathRelativeTo string
var err error
ignoreFilesMatcher := &ignoreFilesMatcher{}
r.PackagePath, err = filepath.Abs(r.PackagePath)
ignoreFilesMatcher := &ignoreFilesMatcher{
fs: r.FileSystem,
}
dir, file, err := r.FileSystem.CleanedAbs(r.PackagePath)
if err != nil {
return nil, errors.Wrap(err)
}
err = filepath.Walk(r.PackagePath, func(
r.PackagePath = filepath.Join(string(dir), file)
err = r.FileSystem.Walk(r.PackagePath, func(
path string, info os.FileInfo, err error) error {
if err != nil {
return errors.Wrap(err)
@@ -256,7 +290,7 @@ func (r LocalPackageReader) Read() ([]*yaml.RNode, error) {
// readFile reads the ResourceNodes from a file
func (r *LocalPackageReader) readFile(path string, _ os.FileInfo) ([]*yaml.RNode, error) {
f, err := os.Open(path)
f, err := r.FileSystem.Open(path)
if err != nil {
return nil, err
}
@@ -267,6 +301,8 @@ func (r *LocalPackageReader) readFile(path string, _ os.FileInfo) ([]*yaml.RNode
Reader: f,
OmitReaderAnnotations: r.OmitReaderAnnotations,
SetAnnotations: r.SetAnnotations,
PreserveSeqIndent: r.PreserveSeqIndent,
WrapBareSeqNode: r.WrapBareSeqNode,
}
return rr.Read()
}
@@ -300,6 +336,7 @@ func (r *LocalPackageReader) initReaderAnnotations(path string, _ os.FileInfo) {
}
if !r.OmitReaderAnnotations {
r.SetAnnotations[kioutil.PathAnnotation] = path
r.SetAnnotations[kioutil.LegacyPathAnnotation] = path
}
}
@@ -313,11 +350,8 @@ func (r *LocalPackageReader) shouldSkipDir(path string, matcher *ignoreFilesMatc
return nil
}
// check if this is a subpackage
_, err := os.Stat(filepath.Join(path, r.PackageFileName))
if os.IsNotExist(err) {
if !r.FileSystem.Exists(filepath.Join(path, r.PackageFileName)) {
return nil
} else if err != nil {
return errors.Wrap(err)
}
if !r.IncludeSubpackages {
return filepath.SkipDir

View File

@@ -4,12 +4,14 @@
package kio
import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"sigs.k8s.io/kustomize/kyaml/errors"
"sigs.k8s.io/kustomize/kyaml/filesys"
"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@@ -26,6 +28,9 @@ type LocalPackageWriter struct {
// ClearAnnotations will clear annotations before writing the resources
ClearAnnotations []string `yaml:"clearAnnotations,omitempty"`
// FileSystem can be used to mock the disk file system.
FileSystem filesys.FileSystemOrOnDisk
}
var _ Writer = LocalPackageWriter{}
@@ -36,9 +41,10 @@ func (r LocalPackageWriter) Write(nodes []*yaml.RNode) error {
return err
}
if s, err := os.Stat(r.PackagePath); err != nil {
return err
} else if !s.IsDir() {
if !r.FileSystem.Exists(r.PackagePath) {
return errors.WrapPrefixf(os.ErrNotExist, "could not write to %q", r.PackagePath)
}
if !r.FileSystem.IsDir(r.PackagePath) {
// if the user specified input isn't a directory, the package is the directory of the
// target
r.PackagePath = filepath.Dir(r.PackagePath)
@@ -60,50 +66,42 @@ func (r LocalPackageWriter) Write(nodes []*yaml.RNode) error {
if !r.KeepReaderAnnotations {
r.ClearAnnotations = append(r.ClearAnnotations, kioutil.PathAnnotation)
r.ClearAnnotations = append(r.ClearAnnotations, kioutil.LegacyPathAnnotation)
}
// 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)
}
if r.FileSystem.IsDir(outputPath) {
return fmt.Errorf("config.kubernetes.io/path cannot be a directory: %s", path)
}
err = os.MkdirAll(filepath.Dir(outputPath), 0700)
err = r.FileSystem.MkdirAll(filepath.Dir(outputPath))
if err != nil {
return errors.Wrap(err)
}
}
// write files
buf := bytes.NewBuffer(nil)
for path := range outputFiles {
outputPath := filepath.Join(r.PackagePath, path)
err = os.MkdirAll(filepath.Dir(filepath.Join(r.PackagePath, path)), 0700)
err = r.FileSystem.MkdirAll(filepath.Dir(filepath.Join(r.PackagePath, path)))
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 {
buf.Reset()
w := ByteWriter{
Writer: buf,
KeepReaderAnnotations: r.KeepReaderAnnotations,
ClearAnnotations: r.ClearAnnotations,
}
if err = w.Write(outputFiles[path]); 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 {
if err := r.FileSystem.WriteFile(outputPath, buf.Bytes()); err != nil {
return errors.Wrap(err)
}
}

View File

@@ -1,55 +0,0 @@
// 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)
}

View File

@@ -32,7 +32,7 @@ var GraphStructures = []string{string(TreeStructureGraph), string(TreeStructureP
// 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.
// used for printing rather than updating or editing.
type TreeWriter struct {
Writer io.Writer
Root string
@@ -49,6 +49,11 @@ type TreeWriterField struct {
}
func (p TreeWriter) packageStructure(nodes []*yaml.RNode) error {
for i := range nodes {
if err := kioutil.CopyLegacyAnnotations(nodes[i]); err != nil {
return err
}
}
indexByPackage := p.index(nodes)
// create the new tree