github.com/argoproj/argo-cd/v3@v3.2.1/util/kube/kube.go (about)

     1  package kube
     2  
     3  import (
     4  	"fmt"
     5  	"regexp"
     6  
     7  	"github.com/argoproj/gitops-engine/pkg/utils/kube"
     8  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
     9  	"k8s.io/apimachinery/pkg/runtime/schema"
    10  
    11  	"github.com/argoproj/argo-cd/v3/common"
    12  )
    13  
    14  var resourceNamePattern = regexp.MustCompile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$")
    15  
    16  // IsValidResourceName returns true if given string a valid Kubernetes resource name
    17  func IsValidResourceName(name string) bool {
    18  	return len(name) < 64 && resourceNamePattern.MatchString(name)
    19  }
    20  
    21  // SetAppInstanceLabel the recommended app.kubernetes.io/instance label against an unstructured object
    22  // Uses the legacy labeling if environment variable is set
    23  func SetAppInstanceLabel(target *unstructured.Unstructured, key, val string) error {
    24  	labels, err := nestedNullableStringMap(target.Object, "metadata", "labels")
    25  	if err != nil {
    26  		return fmt.Errorf("failed to get labels from target object %s %s/%s: %w", target.GroupVersionKind().String(), target.GetNamespace(), target.GetName(), err)
    27  	}
    28  	if labels == nil {
    29  		labels = make(map[string]string)
    30  	}
    31  	labels[key] = val
    32  	target.SetLabels(labels)
    33  	if key != common.LabelKeyLegacyApplicationName {
    34  		// we no longer label the pod template sub resources in v0.11
    35  		return nil
    36  	}
    37  
    38  	gvk := schema.FromAPIVersionAndKind(target.GetAPIVersion(), target.GetKind())
    39  	// special case for deployment and job types: make sure that derived replicaset, and pod has
    40  	// the application label
    41  	switch gvk.Group {
    42  	case "apps", "extensions":
    43  		switch gvk.Kind {
    44  		case kube.DeploymentKind, kube.ReplicaSetKind, kube.StatefulSetKind, kube.DaemonSetKind:
    45  			templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
    46  			if err != nil {
    47  				return err
    48  			}
    49  			if !ok || templateLabels == nil {
    50  				templateLabels = make(map[string]any)
    51  			}
    52  			templateLabels[key] = val
    53  			err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
    54  			if err != nil {
    55  				return err
    56  			}
    57  			// The following is a workaround for issue #335. In API version extensions/v1beta1 or
    58  			// apps/v1beta1, if a spec omits spec.selector then k8s will default the
    59  			// spec.selector.matchLabels to match spec.template.metadata.labels. This means Argo CD
    60  			// labels can potentially make their way into spec.selector.matchLabels, which is a bad
    61  			// thing. The following logic prevents this behavior.
    62  			switch target.GetAPIVersion() {
    63  			case "apps/v1beta1", "extensions/v1beta1":
    64  				selector, _, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "selector")
    65  				if err != nil {
    66  					return err
    67  				}
    68  				if len(selector) == 0 {
    69  					// If we get here, user did not set spec.selector in their manifest. We do not want
    70  					// our Argo CD labels to get defaulted by kubernetes, so we explicitly set the labels
    71  					// for them (minus the Argo CD labels).
    72  					delete(templateLabels, key)
    73  					err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "selector", "matchLabels")
    74  					if err != nil {
    75  						return err
    76  					}
    77  				}
    78  			}
    79  		}
    80  	case "batch":
    81  		if gvk.Kind == kube.JobKind {
    82  			templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
    83  			if err != nil {
    84  				return err
    85  			}
    86  			if !ok || templateLabels == nil {
    87  				templateLabels = make(map[string]any)
    88  			}
    89  			templateLabels[key] = val
    90  			err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
    91  			if err != nil {
    92  				return err
    93  			}
    94  		}
    95  	}
    96  	return nil
    97  }
    98  
    99  // SetAppInstanceAnnotation the recommended app.kubernetes.io/instance annotation against an unstructured object
   100  // Uses the legacy labeling if environment variable is set
   101  func SetAppInstanceAnnotation(target *unstructured.Unstructured, key, val string) error {
   102  	annotations, err := nestedNullableStringMap(target.Object, "metadata", "annotations")
   103  	if err != nil {
   104  		return fmt.Errorf("failed to get annotations from target object %s %s/%s: %w", target.GroupVersionKind().String(), target.GetNamespace(), target.GetName(), err)
   105  	}
   106  
   107  	if annotations == nil {
   108  		annotations = make(map[string]string)
   109  	}
   110  	annotations[key] = val
   111  	target.SetAnnotations(annotations)
   112  	return nil
   113  }
   114  
   115  // GetAppInstanceAnnotation returns the application instance name from annotation
   116  func GetAppInstanceAnnotation(un *unstructured.Unstructured, key string) (string, error) {
   117  	annotations, err := nestedNullableStringMap(un.Object, "metadata", "annotations")
   118  	if err != nil {
   119  		return "", fmt.Errorf("failed to get annotations from target object %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
   120  	}
   121  	if annotations != nil {
   122  		return annotations[key], nil
   123  	}
   124  	return "", nil
   125  }
   126  
   127  // GetAppInstanceLabel returns the application instance name from labels
   128  func GetAppInstanceLabel(un *unstructured.Unstructured, key string) (string, error) {
   129  	labels, err := nestedNullableStringMap(un.Object, "metadata", "labels")
   130  	if err != nil {
   131  		return "", fmt.Errorf("failed to get labels for %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
   132  	}
   133  	if labels != nil {
   134  		return labels[key], nil
   135  	}
   136  	return "", nil
   137  }
   138  
   139  // RemoveLabel removes label with the specified name
   140  func RemoveLabel(un *unstructured.Unstructured, key string) error {
   141  	labels, err := nestedNullableStringMap(un.Object, "metadata", "labels")
   142  	if err != nil {
   143  		return fmt.Errorf("failed to get labels for %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
   144  	}
   145  	if labels == nil {
   146  		return nil
   147  	}
   148  
   149  	for k := range labels {
   150  		if k == key {
   151  			delete(labels, k)
   152  			if len(labels) == 0 {
   153  				un.SetLabels(nil)
   154  			} else {
   155  				un.SetLabels(labels)
   156  			}
   157  			break
   158  		}
   159  	}
   160  	return nil
   161  }
   162  
   163  // RemoveAnnotation removes annotation with the specified name
   164  func RemoveAnnotation(un *unstructured.Unstructured, key string) error {
   165  	annotations, err := nestedNullableStringMap(un.Object, "metadata", "annotations")
   166  	if err != nil {
   167  		return fmt.Errorf("failed to get annotations for %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
   168  	}
   169  	if annotations == nil {
   170  		return nil
   171  	}
   172  
   173  	for k := range annotations {
   174  		if k == key {
   175  			delete(annotations, k)
   176  			if len(annotations) == 0 {
   177  				un.SetAnnotations(nil)
   178  			} else {
   179  				un.SetAnnotations(annotations)
   180  			}
   181  			break
   182  		}
   183  	}
   184  	return nil
   185  }
   186  
   187  // nestedNullableStringMap returns a copy of map[string]string value of a nested field.
   188  // Returns false if value is not found and an error if not one of map[string]any or nil, or contains non-string values in the map.
   189  func nestedNullableStringMap(obj map[string]any, fields ...string) (map[string]string, error) {
   190  	var m map[string]string
   191  	val, found, err := unstructured.NestedFieldNoCopy(obj, fields...)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	if found && val != nil {
   196  		m, _, err = unstructured.NestedStringMap(obj, fields...)
   197  	}
   198  	return m, err
   199  }