sigs.k8s.io/cluster-api@v1.7.1/util/yaml/yaml.go (about)

     1  /*
     2  Copyright 2019 The Kubernetes 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 yaml implements yaml utility functions.
    18  package yaml
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"io"
    24  	"os"
    25  	"strings"
    26  
    27  	"github.com/MakeNowJust/heredoc"
    28  	"github.com/pkg/errors"
    29  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    31  	"k8s.io/apimachinery/pkg/runtime"
    32  	"k8s.io/apimachinery/pkg/runtime/schema"
    33  	"k8s.io/apimachinery/pkg/runtime/serializer/streaming"
    34  	apiyaml "k8s.io/apimachinery/pkg/util/yaml"
    35  	"k8s.io/client-go/kubernetes/scheme"
    36  	"sigs.k8s.io/yaml"
    37  
    38  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    39  	"sigs.k8s.io/cluster-api/util"
    40  )
    41  
    42  // ExtractClusterReferences returns the references in a Cluster object.
    43  func ExtractClusterReferences(out *ParseOutput, c *clusterv1.Cluster) (res []*unstructured.Unstructured) {
    44  	if c.Spec.InfrastructureRef == nil {
    45  		return nil
    46  	}
    47  	if obj := out.FindUnstructuredReference(c.Spec.InfrastructureRef); obj != nil {
    48  		res = append(res, obj)
    49  	}
    50  	return
    51  }
    52  
    53  // ExtractMachineReferences returns the references in a Machine object.
    54  func ExtractMachineReferences(out *ParseOutput, m *clusterv1.Machine) (res []*unstructured.Unstructured) {
    55  	if obj := out.FindUnstructuredReference(&m.Spec.InfrastructureRef); obj != nil {
    56  		res = append(res, obj)
    57  	}
    58  	if m.Spec.Bootstrap.ConfigRef != nil {
    59  		if obj := out.FindUnstructuredReference(m.Spec.Bootstrap.ConfigRef); obj != nil {
    60  			res = append(res, obj)
    61  		}
    62  	}
    63  	return
    64  }
    65  
    66  // ParseOutput is the output given from the Parse function.
    67  type ParseOutput struct {
    68  	Clusters            []*clusterv1.Cluster
    69  	Machines            []*clusterv1.Machine
    70  	MachineSets         []*clusterv1.MachineSet
    71  	MachineDeployments  []*clusterv1.MachineDeployment
    72  	UnstructuredObjects []*unstructured.Unstructured
    73  }
    74  
    75  // Add adds the other ParseOutput slices to this instance.
    76  func (p *ParseOutput) Add(o *ParseOutput) *ParseOutput {
    77  	p.Clusters = append(p.Clusters, o.Clusters...)
    78  	p.Machines = append(p.Machines, o.Machines...)
    79  	p.MachineSets = append(p.MachineSets, o.MachineSets...)
    80  	p.MachineDeployments = append(p.MachineDeployments, o.MachineDeployments...)
    81  	p.UnstructuredObjects = append(p.UnstructuredObjects, o.UnstructuredObjects...)
    82  	return p
    83  }
    84  
    85  // FindUnstructuredReference takes in an ObjectReference and tries to find an Unstructured object.
    86  func (p *ParseOutput) FindUnstructuredReference(ref *corev1.ObjectReference) *unstructured.Unstructured {
    87  	for _, obj := range p.UnstructuredObjects {
    88  		if obj.GroupVersionKind() == ref.GroupVersionKind() &&
    89  			ref.Namespace == obj.GetNamespace() &&
    90  			ref.Name == obj.GetName() {
    91  			return obj
    92  		}
    93  	}
    94  	return nil
    95  }
    96  
    97  // ParseInput is an input struct for the Parse function.
    98  type ParseInput struct {
    99  	File string
   100  }
   101  
   102  // Parse extracts runtime objects from a file.
   103  func Parse(input ParseInput) (*ParseOutput, error) {
   104  	output := &ParseOutput{}
   105  
   106  	// Open the input file.
   107  	reader, err := os.Open(input.File)
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	// Create a new decoder.
   113  	decoder := NewYAMLDecoder(reader)
   114  	defer decoder.Close()
   115  
   116  	for {
   117  		u := &unstructured.Unstructured{}
   118  		_, gvk, err := decoder.Decode(nil, u)
   119  		if errors.Is(err, io.EOF) {
   120  			break
   121  		}
   122  		if runtime.IsNotRegisteredError(err) {
   123  			continue
   124  		}
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  
   129  		switch gvk.Kind {
   130  		case "Cluster":
   131  			obj := &clusterv1.Cluster{}
   132  			if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil {
   133  				return nil, errors.Wrapf(err, "cannot convert object to %s", gvk.Kind)
   134  			}
   135  			output.Clusters = append(output.Clusters, obj)
   136  		case "Machine":
   137  			obj := &clusterv1.Machine{}
   138  			if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil {
   139  				return nil, errors.Wrapf(err, "cannot convert object to %s", gvk.Kind)
   140  			}
   141  			output.Machines = append(output.Machines, obj)
   142  		case "MachineSet":
   143  			obj := &clusterv1.MachineSet{}
   144  			if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil {
   145  				return nil, errors.Wrapf(err, "cannot convert object to %s", gvk.Kind)
   146  			}
   147  			output.MachineSets = append(output.MachineSets, obj)
   148  		case "MachineDeployment":
   149  			obj := &clusterv1.MachineDeployment{}
   150  			if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, obj); err != nil {
   151  				return nil, errors.Wrapf(err, "cannot convert object to %s", gvk.Kind)
   152  			}
   153  			output.MachineDeployments = append(output.MachineDeployments, obj)
   154  		default:
   155  			output.UnstructuredObjects = append(output.UnstructuredObjects, u)
   156  		}
   157  	}
   158  
   159  	return output, nil
   160  }
   161  
   162  type yamlDecoder struct {
   163  	reader  *apiyaml.YAMLReader
   164  	decoder runtime.Decoder
   165  	close   func() error
   166  }
   167  
   168  func (d *yamlDecoder) Decode(defaults *schema.GroupVersionKind, into runtime.Object) (runtime.Object, *schema.GroupVersionKind, error) {
   169  	for {
   170  		doc, err := d.reader.Read()
   171  		if err != nil {
   172  			return nil, nil, err
   173  		}
   174  
   175  		//  Skip over empty documents, i.e. a leading `---`
   176  		if len(bytes.TrimSpace(doc)) == 0 {
   177  			continue
   178  		}
   179  
   180  		return d.decoder.Decode(doc, defaults, into)
   181  	}
   182  }
   183  
   184  func (d *yamlDecoder) Close() error {
   185  	return d.close()
   186  }
   187  
   188  // NewYAMLDecoder returns a new streaming Decoded that supports YAML.
   189  func NewYAMLDecoder(r io.ReadCloser) streaming.Decoder {
   190  	return &yamlDecoder{
   191  		reader:  apiyaml.NewYAMLReader(bufio.NewReader(r)),
   192  		decoder: scheme.Codecs.UniversalDeserializer(),
   193  		close:   r.Close,
   194  	}
   195  }
   196  
   197  // ToUnstructured takes a YAML and converts it to a list of Unstructured objects.
   198  func ToUnstructured(rawyaml []byte) ([]unstructured.Unstructured, error) {
   199  	var ret []unstructured.Unstructured
   200  
   201  	reader := apiyaml.NewYAMLReader(bufio.NewReader(bytes.NewReader(rawyaml)))
   202  	count := 1
   203  	for {
   204  		// Read one YAML document at a time, until io.EOF is returned
   205  		b, err := reader.Read()
   206  		if err != nil {
   207  			if errors.Is(err, io.EOF) {
   208  				break
   209  			}
   210  			return nil, errors.Wrapf(err, "failed to read yaml")
   211  		}
   212  		if len(b) == 0 {
   213  			break
   214  		}
   215  
   216  		var m map[string]interface{}
   217  		if err := yaml.Unmarshal(b, &m); err != nil {
   218  			return nil, errors.Wrapf(err, "failed to unmarshal the %s yaml document: %q", util.Ordinalize(count), string(b))
   219  		}
   220  
   221  		var u unstructured.Unstructured
   222  		u.SetUnstructuredContent(m)
   223  
   224  		// Ignore empty objects.
   225  		// Empty objects are generated if there are weird things in manifest files like e.g. two --- in a row without a yaml doc in the middle
   226  		if u.Object == nil {
   227  			continue
   228  		}
   229  
   230  		ret = append(ret, u)
   231  		count++
   232  	}
   233  
   234  	return ret, nil
   235  }
   236  
   237  // JoinYaml takes a list of YAML files and join them ensuring
   238  // each YAML that the yaml separator goes on a new line by adding \n where necessary.
   239  func JoinYaml(yamls ...[]byte) []byte {
   240  	var yamlSeparator = []byte("---")
   241  
   242  	var cr = []byte("\n")
   243  	var b [][]byte //nolint:prealloc
   244  	for _, y := range yamls {
   245  		if !bytes.HasPrefix(y, cr) {
   246  			y = append(cr, y...)
   247  		}
   248  		if !bytes.HasSuffix(y, cr) {
   249  			y = append(y, cr...)
   250  		}
   251  		b = append(b, y)
   252  	}
   253  
   254  	r := bytes.Join(b, yamlSeparator)
   255  	r = bytes.TrimPrefix(r, cr)
   256  	r = bytes.TrimSuffix(r, cr)
   257  
   258  	return r
   259  }
   260  
   261  // FromUnstructured takes a list of Unstructured objects and converts it into a YAML.
   262  func FromUnstructured(objs []unstructured.Unstructured) ([]byte, error) {
   263  	var ret [][]byte //nolint:prealloc
   264  	for _, o := range objs {
   265  		content, err := yaml.Marshal(o.UnstructuredContent())
   266  		if err != nil {
   267  			return nil, errors.Wrapf(err, "failed to marshal yaml for %s, %s/%s", o.GroupVersionKind(), o.GetNamespace(), o.GetName())
   268  		}
   269  		ret = append(ret, content)
   270  	}
   271  
   272  	return JoinYaml(ret...), nil
   273  }
   274  
   275  // Raw returns un-indented yaml string; it also remove the first empty line, if any.
   276  // While writing yaml, always use space instead of tabs for indentation.
   277  func Raw(raw string) string {
   278  	return strings.TrimPrefix(heredoc.Doc(raw), "\n")
   279  }