...

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

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

     1  // Copyright 2021 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  //
     4  
     5  package dependson
     6  
     7  import (
     8  	"fmt"
     9  	"strings"
    10  
    11  	"k8s.io/apimachinery/pkg/runtime/schema"
    12  	"sigs.k8s.io/cli-utils/pkg/object"
    13  )
    14  
    15  const (
    16  	// Number of fields for a cluster-scoped depends-on object value. Example:
    17  	//   rbac.authorization.k8s.io/ClusterRole/my-cluster-role-name
    18  	numFieldsClusterScoped = 3
    19  	// Number of fields for a namespace-scoped depends-on object value. Example:
    20  	//   apps/namespaces/my-namespace/Deployment/my-deployment-name
    21  	numFieldsNamespacedScoped = 5
    22  	// Used to separate multiple depends-on objects.
    23  	annotationSeparator = ","
    24  	// Used to separate the fields for a depends-on object value.
    25  	fieldSeparator  = "/"
    26  	namespacesField = "namespaces"
    27  )
    28  
    29  // FormatDependencySet formats the passed dependency set as a string.
    30  //
    31  // Object references are separated by ','.
    32  //
    33  // Returns the formatted DependencySet or an error if unable to format.
    34  func FormatDependencySet(depSet DependencySet) (string, error) {
    35  	var dependsOnStr string
    36  	for i, depObj := range depSet {
    37  		if i > 0 {
    38  			dependsOnStr += annotationSeparator
    39  		}
    40  		objStr, err := FormatObjMetadata(depObj)
    41  		if err != nil {
    42  			return "", fmt.Errorf("failed to format object metadata (index: %d): %w", i, err)
    43  		}
    44  		dependsOnStr += objStr
    45  	}
    46  	return dependsOnStr, nil
    47  }
    48  
    49  // ParseDependencySet parses the passed string as a set of object
    50  // references.
    51  //
    52  // Object references are separated by ','.
    53  //
    54  // Returns the parsed DependencySet or an error if unable to parse.
    55  func ParseDependencySet(depsStr string) (DependencySet, error) {
    56  	objs := DependencySet{}
    57  	for i, objStr := range strings.Split(depsStr, annotationSeparator) {
    58  		obj, err := ParseObjMetadata(objStr)
    59  		if err != nil {
    60  			return objs, fmt.Errorf("failed to parse object reference (index: %d): %w", i, err)
    61  		}
    62  		objs = append(objs, obj)
    63  	}
    64  	return objs, nil
    65  }
    66  
    67  // FormatObjMetadata formats the passed object metadata as a string.
    68  //
    69  // Object references can have either three fields (cluster-scoped object) or
    70  // five fields (namespace-scoped object).
    71  //
    72  // Fields are separated by '/'.
    73  //
    74  // Examples:
    75  //
    76  //	Cluster-Scoped: <group>/<kind>/<name> (3 fields)
    77  //	Namespaced: <group>/namespaces/<namespace>/<kind>/<name> (5 fields)
    78  //
    79  // Group and namespace may be empty, but name and kind may not.
    80  //
    81  // Returns the formatted ObjMetadata string or an error if unable to format.
    82  func FormatObjMetadata(obj object.ObjMetadata) (string, error) {
    83  	gk := obj.GroupKind
    84  	// group and namespace are allowed to be empty, but name and kind are not
    85  	if gk.Kind == "" {
    86  		return "", fmt.Errorf("invalid object metadata: kind is empty")
    87  	}
    88  	if obj.Name == "" {
    89  		return "", fmt.Errorf("invalid object metadata: name is empty")
    90  	}
    91  	if obj.Namespace != "" {
    92  		return fmt.Sprintf("%s/namespaces/%s/%s/%s", gk.Group, obj.Namespace, gk.Kind, obj.Name), nil
    93  	}
    94  	return fmt.Sprintf("%s/%s/%s", gk.Group, gk.Kind, obj.Name), nil
    95  }
    96  
    97  // ParseObjMetadata parses the passed string as a object metadata.
    98  //
    99  // Object references can have either three fields (cluster-scoped object) or
   100  // five fields (namespace-scoped object).
   101  //
   102  // Fields are separated by '/'.
   103  //
   104  // Examples:
   105  //
   106  //	Cluster-Scoped: <group>/<kind>/<name> (3 fields)
   107  //	Namespaced: <group>/namespaces/<namespace>/<kind>/<name> (5 fields)
   108  //
   109  // Group and namespace may be empty, but name and kind may not.
   110  //
   111  // Returns the parsed ObjMetadata or an error if unable to parse.
   112  func ParseObjMetadata(objStr string) (object.ObjMetadata, error) {
   113  	var obj object.ObjMetadata
   114  	var group, kind, namespace, name string
   115  	objStr = strings.TrimSpace(objStr)
   116  	fields := strings.Split(objStr, fieldSeparator)
   117  
   118  	if len(fields) != numFieldsClusterScoped && len(fields) != numFieldsNamespacedScoped {
   119  		return obj, fmt.Errorf("expected %d or %d fields, found %d: %q",
   120  			numFieldsClusterScoped, numFieldsNamespacedScoped, len(fields), objStr)
   121  	}
   122  
   123  	group = fields[0]
   124  	if len(fields) == 3 {
   125  		kind = fields[1]
   126  		name = fields[2]
   127  	} else {
   128  		if fields[1] != namespacesField {
   129  			return obj, fmt.Errorf("missing %q field: %q", namespacesField, objStr)
   130  		}
   131  		namespace = fields[2]
   132  		kind = fields[3]
   133  		name = fields[4]
   134  	}
   135  
   136  	id := object.ObjMetadata{
   137  		Namespace: namespace,
   138  		Name:      name,
   139  		GroupKind: schema.GroupKind{
   140  			Group: group,
   141  			Kind:  kind,
   142  		},
   143  	}
   144  	return id, nil
   145  }
   146  

View as plain text