...

Source file src/github.com/redhat-developer/odo/pkg/labels/labels.go

Documentation: github.com/redhat-developer/odo/pkg/labels

     1  package labels
     2  
     3  import (
     4  	"errors"
     5  	"regexp"
     6  	"strings"
     7  	"unicode"
     8  
     9  	dfutil "github.com/devfile/library/v2/pkg/util"
    10  	k8slabels "k8s.io/apimachinery/pkg/labels"
    11  	"k8s.io/apimachinery/pkg/util/validation"
    12  	"k8s.io/klog"
    13  
    14  	"github.com/redhat-developer/odo/pkg/version"
    15  )
    16  
    17  var _replacementMap = map[string]string{
    18  	".": "dot",
    19  	"#": "sharp",
    20  }
    21  
    22  var (
    23  	_regexpInvalidCharacters        = regexp.MustCompile(`[^a-zA-Z0-9._-]`)
    24  	_regexpStartingWithAlphanumeric = regexp.MustCompile(`^[a-z0-9A-Z]`)
    25  	_regexpEndingWithAlphanumeric   = regexp.MustCompile(`[a-z0-9A-Z]$`)
    26  )
    27  
    28  // GetLabels return labels that should be applied to every object for given component in active application
    29  // if you need labels to filter component then use GetSelector instead
    30  // Note: isPartOfComponent denotes if the label is required for a core resource(deployment, svc, pvc, pv) of a given component deployed with `odo dev`;
    31  // it is the only thing that sets it apart from the resources created via other ways (`odo deploy`, deploying resource with apply command during `odo dev`)
    32  func GetLabels(componentName string, applicationName string, runtime string, mode string, isPartOfComponent bool) map[string]string {
    33  	labels := getLabels(componentName, applicationName, mode, true, isPartOfComponent)
    34  	if runtime != "" {
    35  		// 'app.openshift.io/runtime' label added by OpenShift console is always lowercase
    36  		labels[openshiftRunTimeLabel] = strings.ToLower(sanitizeLabelValue(openshiftRunTimeLabel, runtime))
    37  	}
    38  	return labels
    39  }
    40  
    41  // sanitizeLabelValue makes sure that the value specified is a valid value for a Kubernetes label, which means:
    42  // i) must be 63 characters or fewer (can be empty), ii) unless empty, must begin and end with an alphanumeric character ([a-z0-9A-Z]),
    43  // iii) could contain dashes (-), underscores (_), dots (.), and alphanumerics between.
    44  //
    45  // As such, sanitizeLabelValue might perform the following operations (taking care of repeating the process if the result is not a valid label value):
    46  //
    47  // - replace leading or trailing characters (. with "dot" or "DOT", and # with "sharp" or "SHARP", depending on the value case)
    48  //
    49  // - replace all characters that are not dashes, underscores, dots or alphanumerics between with a dash (-)
    50  //
    51  // - truncate the overall result so that it is less than 63 characters.
    52  func sanitizeLabelValue(key, value string) string {
    53  	errs := validation.IsValidLabelValue(value)
    54  	if len(errs) == 0 {
    55  		return value
    56  	}
    57  
    58  	klog.V(4).Infof("invalid value for label %q: %q => sanitizing it: %v", key, value, strings.Join(errs, "; "))
    59  
    60  	// Return the corresponding value if is replaceable immediately
    61  	if v, ok := _replacementMap[value]; ok {
    62  		return v
    63  	}
    64  
    65  	// Replacements if it starts or ends with a non-alphanumeric character
    66  	value = replaceAllLeadingOrTrailingInvalidValues(value)
    67  
    68  	// Now replace any characters that are not dashes, dots, underscores or alphanumerics between
    69  	value = _regexpInvalidCharacters.ReplaceAllString(value, "-")
    70  
    71  	// Truncate if length > 63
    72  	if len(value) > validation.LabelValueMaxLength {
    73  		value = dfutil.TruncateString(value, validation.LabelValueMaxLength)
    74  	}
    75  
    76  	if errs = validation.IsValidLabelValue(value); len(errs) == 0 {
    77  		return value
    78  	}
    79  	return sanitizeLabelValue(key, value)
    80  }
    81  
    82  func replaceAllLeadingOrTrailingInvalidValues(value string) string {
    83  	if value == "" {
    84  		return ""
    85  	}
    86  
    87  	isAllCaseMatchingPredicate := func(p func(rune) bool, s string) bool {
    88  		for _, r := range s {
    89  			if !p(r) && unicode.IsLetter(r) {
    90  				return false
    91  			}
    92  		}
    93  		return true
    94  	}
    95  	getLabelValueReplacement := func(v, replacement string) string {
    96  		if isAllCaseMatchingPredicate(unicode.IsLower, v) {
    97  			return strings.ToLower(replacement)
    98  		}
    99  		if isAllCaseMatchingPredicate(unicode.IsUpper, v) {
   100  			return strings.ToUpper(replacement)
   101  		}
   102  		return replacement
   103  	}
   104  
   105  	if !_regexpStartingWithAlphanumeric.MatchString(value) {
   106  		vAfterFirstChar := value[1:]
   107  		var isPrefixReplaced bool
   108  		for k, val := range _replacementMap {
   109  			if strings.HasPrefix(value, k) {
   110  				value = getLabelValueReplacement(vAfterFirstChar, val) + vAfterFirstChar
   111  				isPrefixReplaced = true
   112  				break
   113  			}
   114  		}
   115  		if !isPrefixReplaced {
   116  			value = vAfterFirstChar
   117  		}
   118  		if value == "" {
   119  			return value
   120  		}
   121  	}
   122  	if !_regexpEndingWithAlphanumeric.MatchString(value) {
   123  		vBeforeLastChar := value[:len(value)-1]
   124  		var isSuffixReplaced bool
   125  		for k, val := range _replacementMap {
   126  			if strings.HasSuffix(value, k) {
   127  				value = vBeforeLastChar + getLabelValueReplacement(vBeforeLastChar, val)
   128  				isSuffixReplaced = true
   129  				break
   130  			}
   131  		}
   132  		if !isSuffixReplaced {
   133  			value = vBeforeLastChar
   134  		}
   135  	}
   136  	return value
   137  }
   138  
   139  // AddStorageInfo adds labels for storage resources
   140  func AddStorageInfo(labels map[string]string, storageName string, isSourceVolume bool) {
   141  	labels[kubernetesStorageNameLabel] = storageName
   142  	labels[componentLabel] = labels[kubernetesInstanceLabel]
   143  	labels[devfileStorageLabel] = storageName
   144  	if isSourceVolume {
   145  		labels[sourcePVCLabel] = storageName
   146  	}
   147  }
   148  
   149  func GetStorageName(labels map[string]string) string {
   150  	return labels[kubernetesStorageNameLabel]
   151  }
   152  
   153  func GetDevfileStorageName(labels map[string]string) string {
   154  	return labels[devfileStorageLabel]
   155  }
   156  
   157  func GetComponentName(labels map[string]string) string {
   158  	return labels[kubernetesInstanceLabel]
   159  }
   160  
   161  func GetAppName(labels map[string]string) string {
   162  	return labels[kubernetesPartOfLabel]
   163  }
   164  
   165  func GetManagedBy(labels map[string]string) string {
   166  	return labels[kubernetesManagedByLabel]
   167  }
   168  
   169  func GetManagedByVersion(labels map[string]string) string {
   170  	return labels[kubernetesManagedByVersionLabel]
   171  }
   172  
   173  func IsManagedByOdo(labels map[string]string) bool {
   174  	return labels[kubernetesManagedByLabel] == odoManager
   175  }
   176  
   177  func GetMode(labels map[string]string) string {
   178  	return labels[odoModeLabel]
   179  }
   180  
   181  // IsProjectTypeSetInAnnotations checks if the ProjectType annotation is set;
   182  // this function is helpful in identifying if a resource is created by odo
   183  func IsProjectTypeSetInAnnotations(annotations map[string]string) bool {
   184  	_, ok := annotations[odoProjectTypeAnnotation]
   185  	return ok
   186  }
   187  
   188  func GetProjectType(labels map[string]string, annotations map[string]string) (string, error) {
   189  	// For backwards compatibility with previously deployed components that could be non-odo, check the annotation first
   190  	// then check to see if there is a label with the project type
   191  	if typ, ok := annotations[odoProjectTypeAnnotation]; ok {
   192  		return typ, nil
   193  	}
   194  	if typ, ok := labels[odoProjectTypeAnnotation]; ok {
   195  		return typ, nil
   196  	}
   197  	return "", errors.New("component type not found in labels or annotations")
   198  }
   199  
   200  func SetProjectType(annotations map[string]string, value string) {
   201  	annotations[odoProjectTypeAnnotation] = value
   202  }
   203  
   204  func AddCommonAnnotations(annotations map[string]string) {
   205  	// Enable use of ImageStreams on OpenShift:
   206  	// https://github.com/redhat-developer/odo/issues/6376
   207  	annotations["alpha.image.policy.openshift.io/resolve-names"] = "*"
   208  }
   209  
   210  // GetSelector returns a selector string used for selection of resources which are part of the given component in given mode
   211  // Note: isPartOfComponent denotes if the selector is required for a core resource(deployment, svc, pvc, pv) of a given component deployed with `odo dev`
   212  // it is the only thing that sets it apart from the resources created via other ways (`odo deploy`, deploying resource with apply command during `odo dev`)
   213  func GetSelector(componentName string, applicationName string, mode string, isPartOfComponent bool) string {
   214  	labels := getLabels(componentName, applicationName, mode, false, isPartOfComponent)
   215  	return labels.String()
   216  }
   217  
   218  func GetNameSelector(componentName string) string {
   219  	labels := k8slabels.Set{
   220  		kubernetesInstanceLabel: componentName,
   221  	}
   222  	return labels.String()
   223  }
   224  
   225  // IsCoreComponent determines if a resource is core component (created in Dev mode and includes deployment, svc, pv, pvc, etc.)
   226  // by checking for 'component' label key.
   227  func IsCoreComponent(labels map[string]string) bool {
   228  	if _, ok := labels[componentLabel]; ok {
   229  		return true
   230  	}
   231  	return false
   232  }
   233  
   234  // getLabels return labels that should be applied to every object for given component in active application
   235  // additional labels are used only for creating object
   236  // if you are creating something use additional=true
   237  // if you need labels to filter component then use additional=false
   238  // isPartOfComponent denotes if the label is required for a core resource(deployment, svc, pvc, pv) of a given component deployed with `odo dev`
   239  // it is the only thing that sets it apart from the resources created via other ways (`odo deploy`, deploying resource with apply command during `odo dev`)
   240  func getLabels(componentName string, applicationName string, mode string, additional bool, isPartOfComponent bool) k8slabels.Set {
   241  	labels := getApplicationLabels(applicationName, additional)
   242  	labels[kubernetesInstanceLabel] = componentName
   243  	if mode != ComponentAnyMode {
   244  		labels[odoModeLabel] = mode
   245  	}
   246  	if isPartOfComponent {
   247  		labels[componentLabel] = componentName
   248  	}
   249  	return labels
   250  }
   251  
   252  // getApplicationLabels return labels that identifies given application
   253  // additional labels are used only when creating object
   254  // if you are creating something use additional=true
   255  // if you need labels to filter component then use additional=false
   256  func getApplicationLabels(application string, additional bool) k8slabels.Set {
   257  	labels := k8slabels.Set{
   258  		kubernetesPartOfLabel:    application,
   259  		kubernetesManagedByLabel: odoManager,
   260  	}
   261  	if additional {
   262  		labels[appLabel] = application
   263  		labels[kubernetesManagedByVersionLabel] = version.VERSION
   264  	}
   265  	return labels
   266  }
   267  

View as plain text