github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/manifest/manifests.go (about)

     1  /*
     2  Copyright 2019 The Skaffold Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package manifest
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"io"
    23  	"regexp"
    24  	"strings"
    25  
    26  	k8syaml "k8s.io/apimachinery/pkg/util/yaml"
    27  )
    28  
    29  // ManifestList is a list of yaml manifests.
    30  //
    31  //nolint:golint
    32  type ManifestList [][]byte
    33  
    34  // Load uses the Kubernetes `apimachinery` to split YAML content into a set of YAML documents.
    35  func Load(in io.Reader) (ManifestList, error) {
    36  	r := k8syaml.NewYAMLReader(bufio.NewReader(in))
    37  	var docs [][]byte
    38  	for {
    39  		doc, err := r.Read()
    40  		switch {
    41  		case err == io.EOF:
    42  			return ManifestList(docs), nil
    43  		case err != nil:
    44  			return nil, err
    45  		default:
    46  			docs = append(docs, doc)
    47  		}
    48  	}
    49  }
    50  
    51  func (l *ManifestList) String() string {
    52  	var str string
    53  	for i, manifest := range *l {
    54  		if i != 0 {
    55  			str += "\n---\n"
    56  		}
    57  		str += string(bytes.TrimSpace(manifest))
    58  	}
    59  	return str
    60  }
    61  
    62  // Append appends the yaml manifests defined in the given buffer.
    63  // `buf` can contain concatenated manifests without `---` separators
    64  // because `kubectl create --dry-run -oyaml` produces such output.
    65  func (l *ManifestList) Append(buf []byte) {
    66  	// If there's at most one `apiVersion` field, then append the `buf` as is.
    67  	if len(regexp.MustCompile("(?m)^apiVersion:").FindAll(buf, -1)) <= 1 {
    68  		*l = append(*l, buf)
    69  		return
    70  	}
    71  
    72  	// If there are `---` separators, then append each individual manifest as is.
    73  	parts := bytes.Split(buf, []byte("\n---\n"))
    74  	if len(parts) > 1 {
    75  		*l = append(*l, parts...)
    76  		return
    77  	}
    78  
    79  	// There are no `---` separators, let's identify each individual manifest
    80  	// based on the top level keys lexicographical order.
    81  	yaml := string(buf)
    82  
    83  	var part string
    84  	var previousKey = ""
    85  
    86  	for _, line := range strings.Split(yaml, "\n") {
    87  		// Not a top level key.
    88  		if strings.HasPrefix(line, "-") || strings.HasPrefix(line, " ") || !strings.Contains(line, ":") {
    89  			part += "\n" + line
    90  			continue
    91  		}
    92  
    93  		// Top level key.
    94  		key := line[0:strings.Index(line, ":")]
    95  		if strings.Compare(key, previousKey) > 0 {
    96  			if part != "" {
    97  				part += "\n"
    98  			}
    99  			part += line
   100  		} else {
   101  			*l = append(*l, []byte(part))
   102  			part = line
   103  		}
   104  
   105  		previousKey = key
   106  	}
   107  
   108  	*l = append(*l, []byte(part))
   109  }
   110  
   111  // Diff computes the list of manifests that have changed.
   112  func (l *ManifestList) Diff(latest ManifestList) ManifestList {
   113  	if l == nil {
   114  		return latest
   115  	}
   116  
   117  	oldManifests := map[string]bool{}
   118  	for _, oldManifest := range *l {
   119  		oldManifests[string(oldManifest)] = true
   120  	}
   121  
   122  	var updated ManifestList
   123  
   124  	for _, manifest := range latest {
   125  		if !oldManifests[string(manifest)] {
   126  			updated = append(updated, manifest)
   127  		}
   128  	}
   129  
   130  	return updated
   131  }
   132  
   133  // Reader returns a reader on the raw yaml descriptors.
   134  func (l *ManifestList) Reader() io.Reader {
   135  	return strings.NewReader(l.String())
   136  }