...

Source file src/sigs.k8s.io/cli-utils/pkg/manifestreader/common.go

Documentation: sigs.k8s.io/cli-utils/pkg/manifestreader

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package manifestreader
     5  
     6  import (
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"strings"
    11  
    12  	"k8s.io/apimachinery/pkg/api/meta"
    13  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    14  	"k8s.io/apimachinery/pkg/runtime/schema"
    15  	"sigs.k8s.io/cli-utils/pkg/inventory"
    16  	"sigs.k8s.io/cli-utils/pkg/object"
    17  	"sigs.k8s.io/kustomize/kyaml/kio/filters"
    18  	"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
    19  	"sigs.k8s.io/kustomize/kyaml/yaml"
    20  )
    21  
    22  // UnknownTypesError captures information about unknown types encountered.
    23  type UnknownTypesError struct {
    24  	GroupVersionKinds []schema.GroupVersionKind
    25  }
    26  
    27  func (e *UnknownTypesError) Error() string {
    28  	var gvks []string
    29  	for _, gvk := range e.GroupVersionKinds {
    30  		gvks = append(gvks, fmt.Sprintf("%s/%s/%s",
    31  			gvk.Group, gvk.Version, gvk.Kind))
    32  	}
    33  	return fmt.Sprintf("unknown resource types: %s", strings.Join(gvks, ","))
    34  }
    35  
    36  // NamespaceMismatchError is returned if all resources must be in a specific
    37  // namespace, and resources are found using other namespaces.
    38  type NamespaceMismatchError struct {
    39  	RequiredNamespace string
    40  	Namespace         string
    41  }
    42  
    43  func (e *NamespaceMismatchError) Error() string {
    44  	return fmt.Sprintf("found namespace %q, but all resources must be in namespace %q",
    45  		e.Namespace, e.RequiredNamespace)
    46  }
    47  
    48  // SetNamespaces verifies that every namespaced resource has the namespace
    49  // set, and if one does not, it will set the namespace to the provided
    50  // defaultNamespace.
    51  // This implementation will check each resource (that doesn't already have
    52  // the namespace set) on whether it is namespace or cluster scoped. It does
    53  // this by first checking the RESTMapper, and it there is not match there,
    54  // it will look for CRDs in the provided Unstructureds.
    55  func SetNamespaces(mapper meta.RESTMapper, objs []*unstructured.Unstructured,
    56  	defaultNamespace string, enforceNamespace bool) error {
    57  	var crdObjs []*unstructured.Unstructured
    58  
    59  	// find any crds in the set of resources.
    60  	for _, obj := range objs {
    61  		if object.IsCRD(obj) {
    62  			crdObjs = append(crdObjs, obj)
    63  		}
    64  	}
    65  
    66  	var unknownGVKs []schema.GroupVersionKind
    67  	for _, obj := range objs {
    68  		// Exclude any inventory objects here since we don't want to change
    69  		// their namespace.
    70  		if inventory.IsInventoryObject(obj) {
    71  			continue
    72  		}
    73  
    74  		// Look up the scope of the resource so we know if the resource
    75  		// should have a namespace set or not.
    76  		scope, err := object.LookupResourceScope(obj, crdObjs, mapper)
    77  		if err != nil {
    78  			var unknownTypeError *object.UnknownTypeError
    79  			if errors.As(err, &unknownTypeError) {
    80  				// If no scope was found, just add the resource type to the list
    81  				// of unknown types.
    82  				unknownGVKs = append(unknownGVKs, unknownTypeError.GroupVersionKind)
    83  				continue
    84  			} else {
    85  				// If something went wrong when looking up the scope, just
    86  				// give up.
    87  				return err
    88  			}
    89  		}
    90  
    91  		switch scope {
    92  		case meta.RESTScopeNamespace:
    93  			if obj.GetNamespace() == "" {
    94  				obj.SetNamespace(defaultNamespace)
    95  			} else {
    96  				ns := obj.GetNamespace()
    97  				if enforceNamespace && ns != defaultNamespace {
    98  					return &NamespaceMismatchError{
    99  						Namespace:         ns,
   100  						RequiredNamespace: defaultNamespace,
   101  					}
   102  				}
   103  			}
   104  		case meta.RESTScopeRoot:
   105  			if ns := obj.GetNamespace(); ns != "" {
   106  				return fmt.Errorf("resource is cluster-scoped but has a non-empty namespace %q", ns)
   107  			}
   108  		default:
   109  			return fmt.Errorf("unknown RESTScope %q", scope.Name())
   110  		}
   111  	}
   112  	if len(unknownGVKs) > 0 {
   113  		return &UnknownTypesError{
   114  			GroupVersionKinds: unknownGVKs,
   115  		}
   116  	}
   117  	return nil
   118  }
   119  
   120  // FilterLocalConfig returns a new slice of Unstructured where all resources
   121  // with the LocalConfig annotation is filtered out.
   122  func FilterLocalConfig(objs []*unstructured.Unstructured) []*unstructured.Unstructured {
   123  	var filteredObjs []*unstructured.Unstructured
   124  	for _, obj := range objs {
   125  		// Ignoring the value of the LocalConfigAnnotation here. This is to be
   126  		// consistent with the behavior in the kyaml library:
   127  		// https://github.com/kubernetes-sigs/kustomize/blob/30b58e90a39485bc5724b2278651c5d26b815cb2/kyaml/kio/filters/local.go#L29
   128  		if _, found := obj.GetAnnotations()[filters.LocalConfigAnnotation]; !found {
   129  			filteredObjs = append(filteredObjs, obj)
   130  		}
   131  	}
   132  	return filteredObjs
   133  }
   134  
   135  // RemoveAnnotations removes the specified kioutil annotations from the resource.
   136  func RemoveAnnotations(n *yaml.RNode, annotations ...kioutil.AnnotationKey) error {
   137  	for _, a := range annotations {
   138  		err := n.PipeE(yaml.ClearAnnotation(a))
   139  		if err != nil {
   140  			return err
   141  		}
   142  	}
   143  	return nil
   144  }
   145  
   146  // KyamlNodeToUnstructured take a resource represented as a kyaml RNode and
   147  // turns it into an Unstructured object.
   148  func KyamlNodeToUnstructured(n *yaml.RNode) (*unstructured.Unstructured, error) {
   149  	b, err := n.MarshalJSON()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  
   154  	var m map[string]interface{}
   155  	err = json.Unmarshal(b, &m)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	return &unstructured.Unstructured{
   161  		Object: m,
   162  	}, nil
   163  }
   164  

View as plain text