...

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

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

     1  // Copyright 2020 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  //
     4  // ObjMetadata is the minimal set of information to
     5  // uniquely identify an object. The four fields are:
     6  //
     7  //   Group/Kind (NOTE: NOT version)
     8  //   Namespace
     9  //   Name
    10  //
    11  // We specifically do not use the "version", because
    12  // the APIServer does not recognize a version as a
    13  // different resource. This metadata is used to identify
    14  // resources for pruning and teardown.
    15  
    16  package object
    17  
    18  import (
    19  	"fmt"
    20  	"strings"
    21  
    22  	rbacv1 "k8s.io/api/rbac/v1"
    23  	"k8s.io/apimachinery/pkg/api/meta"
    24  	"k8s.io/apimachinery/pkg/runtime"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  )
    27  
    28  const (
    29  	// Separates inventory fields. This string is allowable as a
    30  	// ConfigMap key, but it is not allowed as a character in
    31  	// resource name.
    32  	fieldSeparator = "_"
    33  	// Transform colons in the RBAC resource names to double
    34  	// underscore.
    35  	colonTranscoded = "__"
    36  )
    37  
    38  var (
    39  	NilObjMetadata = ObjMetadata{}
    40  )
    41  
    42  // RBACGroupKind is a map of the RBAC resources. Needed since name validation
    43  // is different than other k8s resources.
    44  var RBACGroupKind = map[schema.GroupKind]bool{
    45  	{Group: rbacv1.GroupName, Kind: "Role"}:               true,
    46  	{Group: rbacv1.GroupName, Kind: "ClusterRole"}:        true,
    47  	{Group: rbacv1.GroupName, Kind: "RoleBinding"}:        true,
    48  	{Group: rbacv1.GroupName, Kind: "ClusterRoleBinding"}: true,
    49  }
    50  
    51  // ObjMetadata organizes and stores the indentifying information
    52  // for an object. This struct (as a string) is stored in a
    53  // inventory object to keep track of sets of applied objects.
    54  type ObjMetadata struct {
    55  	Namespace string
    56  	Name      string
    57  	GroupKind schema.GroupKind
    58  }
    59  
    60  // ParseObjMetadata takes a string, splits it into its four fields,
    61  // and returns an ObjMetadata struct storing the four fields.
    62  // Example inventory string:
    63  //
    64  //	test-namespace_test-name_apps_ReplicaSet
    65  //
    66  // Returns an error if unable to parse and create the ObjMetadata struct.
    67  //
    68  // NOTE: name field can contain double underscore (__), which represents
    69  // a colon. RBAC resources can have this additional character (:) in their name.
    70  func ParseObjMetadata(s string) (ObjMetadata, error) {
    71  	// Parse first field namespace
    72  	index := strings.Index(s, fieldSeparator)
    73  	if index == -1 {
    74  		return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s)
    75  	}
    76  	namespace := s[:index]
    77  	s = s[index+1:]
    78  	// Next, parse last field kind
    79  	index = strings.LastIndex(s, fieldSeparator)
    80  	if index == -1 {
    81  		return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s)
    82  	}
    83  	kind := s[index+1:]
    84  	s = s[:index]
    85  	// Next, parse next to last field group
    86  	index = strings.LastIndex(s, fieldSeparator)
    87  	if index == -1 {
    88  		return NilObjMetadata, fmt.Errorf("unable to parse stored object metadata: %s", s)
    89  	}
    90  	group := s[index+1:]
    91  	// Finally, second field name. Name may contain colon transcoded as double underscore.
    92  	name := s[:index]
    93  	name = strings.ReplaceAll(name, colonTranscoded, ":")
    94  	// Check that there are no extra fields by search for fieldSeparator.
    95  	if strings.Contains(name, fieldSeparator) {
    96  		return NilObjMetadata, fmt.Errorf("too many fields within: %s", s)
    97  	}
    98  	// Create the ObjMetadata object from the four parsed fields.
    99  	id := ObjMetadata{
   100  		Namespace: namespace,
   101  		Name:      name,
   102  		GroupKind: schema.GroupKind{
   103  			Group: group,
   104  			Kind:  kind,
   105  		},
   106  	}
   107  	return id, nil
   108  }
   109  
   110  // Equals compares two ObjMetadata and returns true if they are equal. This does
   111  // not contain any special treatment for the extensions API group.
   112  func (o *ObjMetadata) Equals(other *ObjMetadata) bool {
   113  	if other == nil {
   114  		return false
   115  	}
   116  	return *o == *other
   117  }
   118  
   119  // String create a string version of the ObjMetadata struct. For RBAC resources,
   120  // the "name" field transcodes ":" into double underscore for valid storing
   121  // as the label of a ConfigMap.
   122  func (o ObjMetadata) String() string {
   123  	name := o.Name
   124  	if _, exists := RBACGroupKind[o.GroupKind]; exists {
   125  		name = strings.ReplaceAll(name, ":", colonTranscoded)
   126  	}
   127  	return fmt.Sprintf("%s%s%s%s%s%s%s",
   128  		o.Namespace, fieldSeparator,
   129  		name, fieldSeparator,
   130  		o.GroupKind.Group, fieldSeparator,
   131  		o.GroupKind.Kind)
   132  }
   133  
   134  // RuntimeToObjMeta extracts the object metadata information from a
   135  // runtime.Object and returns it as ObjMetadata.
   136  func RuntimeToObjMeta(obj runtime.Object) (ObjMetadata, error) {
   137  	accessor, err := meta.Accessor(obj)
   138  	if err != nil {
   139  		return NilObjMetadata, err
   140  	}
   141  	id := ObjMetadata{
   142  		Namespace: accessor.GetNamespace(),
   143  		Name:      accessor.GetName(),
   144  		GroupKind: obj.GetObjectKind().GroupVersionKind().GroupKind(),
   145  	}
   146  	return id, nil
   147  }
   148  

View as plain text