github.com/argoproj/argo-cd/v2@v2.10.5/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/v2/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]interface{})
    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  		switch gvk.Kind {
    82  		case kube.JobKind:
    83  			templateLabels, ok, err := unstructured.NestedMap(target.UnstructuredContent(), "spec", "template", "metadata", "labels")
    84  			if err != nil {
    85  				return err
    86  			}
    87  			if !ok || templateLabels == nil {
    88  				templateLabels = make(map[string]interface{})
    89  			}
    90  			templateLabels[key] = val
    91  			err = unstructured.SetNestedMap(target.UnstructuredContent(), templateLabels, "spec", "template", "metadata", "labels")
    92  			if err != nil {
    93  				return err
    94  			}
    95  		}
    96  	}
    97  	return nil
    98  }
    99  
   100  // SetAppInstanceAnnotation the recommended app.kubernetes.io/instance annotation against an unstructured object
   101  // Uses the legacy labeling if environment variable is set
   102  func SetAppInstanceAnnotation(target *unstructured.Unstructured, key, val string) error {
   103  	annotations, _, err := nestedNullableStringMap(target.Object, "metadata", "annotations")
   104  	if err != nil {
   105  		return fmt.Errorf("failed to get annotations from target object %s %s/%s: %w", target.GroupVersionKind().String(), target.GetNamespace(), target.GetName(), err)
   106  	}
   107  
   108  	if annotations == nil {
   109  		annotations = make(map[string]string)
   110  	}
   111  	annotations[key] = val
   112  	target.SetAnnotations(annotations)
   113  	return nil
   114  }
   115  
   116  // GetAppInstanceAnnotation returns the application instance name from annotation
   117  func GetAppInstanceAnnotation(un *unstructured.Unstructured, key string) (string, error) {
   118  	annotations, _, err := nestedNullableStringMap(un.Object, "metadata", "annotations")
   119  	if err != nil {
   120  		return "", fmt.Errorf("failed to get annotations from target object %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
   121  	}
   122  	if annotations != nil {
   123  		return annotations[key], nil
   124  	}
   125  	return "", nil
   126  }
   127  
   128  // GetAppInstanceLabel returns the application instance name from labels
   129  func GetAppInstanceLabel(un *unstructured.Unstructured, key string) (string, error) {
   130  	labels, _, err := nestedNullableStringMap(un.Object, "metadata", "labels")
   131  	if err != nil {
   132  		return "", fmt.Errorf("failed to get labels for %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
   133  	}
   134  	if labels != nil {
   135  		return labels[key], nil
   136  	}
   137  	return "", nil
   138  }
   139  
   140  // RemoveLabel removes label with the specified name
   141  func RemoveLabel(un *unstructured.Unstructured, key string) error {
   142  	labels, _, err := nestedNullableStringMap(un.Object, "metadata", "labels")
   143  	if err != nil {
   144  		return fmt.Errorf("failed to get labels for %s %s/%s: %w", un.GroupVersionKind().String(), un.GetNamespace(), un.GetName(), err)
   145  	}
   146  	if labels == nil {
   147  		return nil
   148  	}
   149  
   150  	for k := range labels {
   151  		if k == key {
   152  			delete(labels, k)
   153  			if len(labels) == 0 {
   154  				un.SetLabels(nil)
   155  			} else {
   156  				un.SetLabels(labels)
   157  			}
   158  			break
   159  		}
   160  	}
   161  	return nil
   162  }
   163  
   164  // nestedNullableStringMap returns a copy of map[string]string value of a nested field.
   165  // Returns false if value is not found and an error if not one of map[string]interface{} or nil, or contains non-string values in the map.
   166  func nestedNullableStringMap(obj map[string]interface{}, fields ...string) (map[string]string, bool, error) {
   167  	var m map[string]string
   168  	val, found, err := unstructured.NestedFieldNoCopy(obj, fields...)
   169  	if err != nil {
   170  		return nil, found, err
   171  	}
   172  	if found && val != nil {
   173  		return unstructured.NestedStringMap(obj, fields...)
   174  	}
   175  	return m, found, err
   176  }