...

Source file src/sigs.k8s.io/cli-utils/pkg/object/unstructured.go

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

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  //
     4  
     5  package object
     6  
     7  import (
     8  	"fmt"
     9  
    10  	"k8s.io/apimachinery/pkg/api/meta"
    11  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    12  	"k8s.io/apimachinery/pkg/runtime/schema"
    13  	"sigs.k8s.io/kustomize/kyaml/kio/kioutil"
    14  )
    15  
    16  var (
    17  	namespaceGK = schema.GroupKind{Group: "", Kind: "Namespace"}
    18  	crdGK       = schema.GroupKind{Group: "apiextensions.k8s.io", Kind: "CustomResourceDefinition"}
    19  )
    20  
    21  // UnstructuredSetToObjMetadataSet converts a UnstructuredSet to a ObjMetadataSet.
    22  func UnstructuredSetToObjMetadataSet(objs UnstructuredSet) ObjMetadataSet {
    23  	objMetas := make([]ObjMetadata, len(objs))
    24  	for i, obj := range objs {
    25  		objMetas[i] = UnstructuredToObjMetadata(obj)
    26  	}
    27  	return objMetas
    28  }
    29  
    30  // UnstructuredToObjMetadata extracts the identifying information from an
    31  // Unstructured object and returns it as ObjMetadata object.
    32  func UnstructuredToObjMetadata(obj *unstructured.Unstructured) ObjMetadata {
    33  	return ObjMetadata{
    34  		Namespace: obj.GetNamespace(),
    35  		Name:      obj.GetName(),
    36  		GroupKind: obj.GroupVersionKind().GroupKind(),
    37  	}
    38  }
    39  
    40  // IsKindNamespace returns true if the passed Unstructured object is
    41  // GroupKind == Core/Namespace (no version checked); false otherwise.
    42  func IsKindNamespace(u *unstructured.Unstructured) bool {
    43  	if u == nil {
    44  		return false
    45  	}
    46  	gvk := u.GroupVersionKind()
    47  	return namespaceGK == gvk.GroupKind()
    48  }
    49  
    50  // IsNamespaced returns true if the passed Unstructured object
    51  // is namespace-scoped (not cluster-scoped); false otherwise.
    52  func IsNamespaced(u *unstructured.Unstructured) bool {
    53  	if u == nil {
    54  		return false
    55  	}
    56  	return u.GetNamespace() != ""
    57  }
    58  
    59  // IsNamespace returns true if the passed Unstructured object
    60  // is Namespace in the core (empty string) group.
    61  func IsNamespace(u *unstructured.Unstructured) bool {
    62  	if u == nil {
    63  		return false
    64  	}
    65  	gvk := u.GroupVersionKind()
    66  	// core group, any version
    67  	return gvk.Group == "" && gvk.Kind == "Namespace"
    68  }
    69  
    70  // IsCRD returns true if the passed Unstructured object has
    71  // GroupKind == Extensions/CustomResourceDefinition; false otherwise.
    72  func IsCRD(u *unstructured.Unstructured) bool {
    73  	if u == nil {
    74  		return false
    75  	}
    76  	gvk := u.GroupVersionKind()
    77  	return crdGK == gvk.GroupKind()
    78  }
    79  
    80  // GetCRDGroupKind returns the GroupKind stored in the passed
    81  // Unstructured CustomResourceDefinition and true if the passed object
    82  // is a CRD.
    83  func GetCRDGroupKind(u *unstructured.Unstructured) (schema.GroupKind, bool) {
    84  	emptyGroupKind := schema.GroupKind{Group: "", Kind: ""}
    85  	if u == nil {
    86  		return emptyGroupKind, false
    87  	}
    88  	group, found, err := unstructured.NestedString(u.Object, "spec", "group")
    89  	if found && err == nil {
    90  		kind, found, err := unstructured.NestedString(u.Object, "spec", "names", "kind")
    91  		if found && err == nil {
    92  			return schema.GroupKind{Group: group, Kind: kind}, true
    93  		}
    94  	}
    95  	return emptyGroupKind, false
    96  }
    97  
    98  // UnknownTypeError captures information about a type for which no information
    99  // could be found in the cluster or among the known CRDs.
   100  type UnknownTypeError struct {
   101  	GroupVersionKind schema.GroupVersionKind
   102  }
   103  
   104  func (e *UnknownTypeError) Error() string {
   105  	return fmt.Sprintf("unknown resource type: %q", e.GroupVersionKind.String())
   106  }
   107  
   108  // LookupResourceScope tries to look up the scope of the type of the provided
   109  // resource, looking at both the types known to the cluster (through the
   110  // RESTMapper) and the provided CRDs. If no information about the type can
   111  // be found, an UnknownTypeError wil be returned.
   112  func LookupResourceScope(u *unstructured.Unstructured, crds []*unstructured.Unstructured, mapper meta.RESTMapper) (meta.RESTScope, error) {
   113  	gvk := u.GroupVersionKind()
   114  	// First see if we can find the type (and the scope) in the cluster through
   115  	// the RESTMapper.
   116  	mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
   117  	if err == nil {
   118  		// If we find the type in the cluster, we just look up the scope there.
   119  		return mapping.Scope, nil
   120  	}
   121  	// Not finding a match is not an error here, so only error out for other
   122  	// error types.
   123  	if !meta.IsNoMatchError(err) {
   124  		return nil, err
   125  	}
   126  
   127  	// If we couldn't find the type in the cluster, check if we find a
   128  	// match in any of the provided CRDs.
   129  	for _, crd := range crds {
   130  		group, found, err := NestedField(crd.Object, "spec", "group")
   131  		if err != nil {
   132  			return nil, err
   133  		}
   134  		if !found || group == "" {
   135  			return nil, NotFound([]interface{}{"spec", "group"}, group)
   136  		}
   137  		kind, found, err := NestedField(crd.Object, "spec", "names", "kind")
   138  		if err != nil {
   139  			return nil, err
   140  		}
   141  		if !found || kind == "" {
   142  			return nil, NotFound([]interface{}{"spec", "kind"}, group)
   143  		}
   144  		if gvk.Kind != kind || gvk.Group != group {
   145  			continue
   146  		}
   147  		versionDefined, err := crdDefinesVersion(crd, gvk.Version)
   148  		if err != nil {
   149  			return nil, err
   150  		}
   151  		if !versionDefined {
   152  			return nil, &UnknownTypeError{
   153  				GroupVersionKind: gvk,
   154  			}
   155  		}
   156  		scopeName, _, err := NestedField(crd.Object, "spec", "scope")
   157  		if err != nil {
   158  			return nil, err
   159  		}
   160  		switch scopeName {
   161  		case "Namespaced":
   162  			return meta.RESTScopeNamespace, nil
   163  		case "Cluster":
   164  			return meta.RESTScopeRoot, nil
   165  		default:
   166  			return nil, Invalid([]interface{}{"spec", "scope"}, scopeName,
   167  				"expected Namespaced or Cluster")
   168  		}
   169  	}
   170  	return nil, &UnknownTypeError{
   171  		GroupVersionKind: gvk,
   172  	}
   173  }
   174  
   175  func crdDefinesVersion(crd *unstructured.Unstructured, version string) (bool, error) {
   176  	versions, found, err := NestedField(crd.Object, "spec", "versions")
   177  	if err != nil {
   178  		return false, err
   179  	}
   180  	if !found {
   181  		return false, NotFound([]interface{}{"spec", "versions"}, versions)
   182  	}
   183  	versionsSlice, ok := versions.([]interface{})
   184  	if !ok {
   185  		return false, InvalidType([]interface{}{"spec", "versions"}, versions, "[]interface{}")
   186  	}
   187  	if len(versionsSlice) == 0 {
   188  		return false, Invalid([]interface{}{"spec", "versions"}, versionsSlice, "must not be empty")
   189  	}
   190  	for i := range versionsSlice {
   191  		name, found, err := NestedField(crd.Object, "spec", "versions", i, "name")
   192  		if err != nil {
   193  			return false, err
   194  		}
   195  		if !found {
   196  			return false, NotFound([]interface{}{"spec", "versions", i, "name"}, name)
   197  		}
   198  		if name == version {
   199  			return true, nil
   200  		}
   201  	}
   202  	return false, nil
   203  }
   204  
   205  // StripKyamlAnnotations removes any path and index annotations from the
   206  // unstructured resource.
   207  func StripKyamlAnnotations(u *unstructured.Unstructured) {
   208  	annos := u.GetAnnotations()
   209  	delete(annos, kioutil.PathAnnotation)
   210  	delete(annos, kioutil.LegacyPathAnnotation) //nolint:staticcheck
   211  	delete(annos, kioutil.IndexAnnotation)
   212  	delete(annos, kioutil.LegacyIndexAnnotation) //nolint:staticcheck
   213  	u.SetAnnotations(annos)
   214  }
   215  

View as plain text