Skip to content

Commit

Permalink
refinements and clarifications to the validation of names
Browse files Browse the repository at this point in the history
closes #5220
  • Loading branch information
shawkins authored and manusa committed Sep 18, 2023
1 parent f134cc3 commit 00e7c55
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* Fix #5377: added a createOr and unlock function to provide a straight-forward replacement for createOrReplace.
* Fix #5388: [crd-generator] Generate deterministic CRDs
* Fix #5257: Add ErrorStreamMessage and StatusStreamMessage to ease mocking of pods/exec requests
* Fix #5220: refinements and clarifications to the validation of names

#### Dependency Upgrade
* Fix #5373: Gradle base API based on v8.2.1
Expand All @@ -22,6 +23,7 @@
* Fix #5343: Removed `io.fabric8.kubernetes.model.annotation.PrinterColumn`, use `io.fabric8.crd.generator.annotation.PrinterColumn`
* Fix #5368: ListOptions parameter ordering is now alphabetical. If you are using non-crud mocking for lists with options, you may need to update your parameter order.
* Fix #5391: Removed the vertx-uri-template dependency from the vertx client, if you need that for your application, then introduce your own dependency.
* Fix #5220: KubernetesResourceUtil.isValidLabelOrAnnotation has been deprecated because the rules for labels and annotations are different

### 6.8.1 (2023-08-14)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ public class KubernetesResourceUtil {
private KubernetesResourceUtil() {
}

public static final Pattern KUBERNETES_SUBDOMAIN_REGEX = Pattern
.compile("[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*");
public static final Pattern KUBERNETES_DNS1123_LABEL_REGEX = Pattern.compile("[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?");
public static final Pattern KUBERNETES_KEY_REGEX = Pattern
.compile("(" + KUBERNETES_SUBDOMAIN_REGEX.toString() + "/)?[a-z0-9]([-_.a-z0-9]{0,61}[a-z0-9])?");

private static final Pattern INVALID_LABEL_CHARS_PATTERN = Pattern.compile("[^-A-Za-z0-9]+");
private static final String DEFAULT_CONTAINER_IMAGE_REGISTRY_SECRET_NAME = "container-image-registry-secret";

Expand Down Expand Up @@ -249,7 +254,11 @@ public static Map<String, String> getOrCreateAnnotations(HasMetadata entity) {
}

/**
* Returns an identifier from the given string that can be used as resource name.
* Returns an identifier from the given string that can be used as resource, label key/value, or annotation key
* in accordance to RFC 1123 Label Names.
* <p>
* Note that this is more restrictive than necessary for most resources and label/annotation keys. It will truncate the name
* if necessary, which may affect uniqueness.
*
* @param name which needs to be sanitized
* @return sanitized name
Expand Down Expand Up @@ -324,7 +333,15 @@ public static ObjectMeta getOrCreateMetadata(HasMetadata entity) {
}

/**
* Validates name of Kubernetes Resource name, label or annotation based on Kubernetes regex
* Validates name of a Kubernetes Resource name, label key/value or annotation key based on RFC 1123 Label Names.
* <p>
* Note: this is more restrictive than what is allowed for annotation names or most resource names, however some resource
* types have additional restrictions on their names.
* <p>
* Refer to <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/names/">Kubernetes Naming
* Conventions</a>
* <p>
* See also {@link #isValidKey(String)} and {@link #isValidSubdomainName(String)}
*
* @param name Name of resource/label/annotation
* @return returns a boolean value indicating whether it's valid or not
Expand All @@ -333,19 +350,62 @@ public static boolean isValidName(String name) {
return Utils.isNotNullOrEmpty(name) && KUBERNETES_DNS1123_LABEL_REGEX.matcher(name).matches();
}

/**
* Validates annotation or label key.
*
* @param key the annotation or label key
* @return returns a boolean value indicating whether it's valid or not
*/
public static boolean isValidKey(String key) {
return Utils.isNotNullOrEmpty(key) && key.length() < 254 && KUBERNETES_KEY_REGEX.matcher(key).matches();
}

/**
* Validates name of an resource according to DNS Subdomain rules.
* <p>
* Refer to <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/names/">Kubernetes Naming
* Conventions</a>
*
* @param name Name of annotation
* @return returns a boolean value indicating whether it's valid or not
*/
public static boolean isValidSubdomainName(String name) {
return Utils.isNotNullOrEmpty(name) && name.length() < 254 && KUBERNETES_SUBDOMAIN_REGEX.matcher(name).matches();
}

/**
* Validates labels/annotations of Kubernetes resources
*
* @param map Label/Annotation of resource
* @return returns a boolean value indicating whether it's valid or not
*
* @see #areLabelsValid(Map) {@link #areAnnotationsValid(Map)}
*
* @deprecated the rules are different for label and annotation values
*/
@Deprecated
public static boolean isValidLabelOrAnnotation(Map<String, String> map) {
for (Map.Entry<String, String> entry : map.entrySet()) {
if (!(isValidName(entry.getKey()) && isValidName(entry.getValue()))) {
return false;
}
}
return true;
return areLabelsValid(map);
}

/**
* Checks the given map keys and values for validity as labels.
*
* @param map labels
* @return returns a boolean value indicating whether it's valid or not
*/
public static boolean areLabelsValid(Map<String, String> map) {
return map.entrySet().stream().allMatch(e -> isValidKey(e.getKey()) && isValidName(e.getValue()));
}

/**
* Checks the given map keys validity as annotations.
*
* @param map annotations
* @return returns a boolean value indicating whether it's valid or not
*/
public static boolean areAnnotationsValid(Map<String, String> map) {
return map.keySet().stream().allMatch(KubernetesResourceUtil::isValidKey);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,34 @@ void testNullSafeOperationsForAnnotations() {
void testNames() {
assertTrue(KubernetesResourceUtil.isValidName(KubernetesResourceUtil.getName(configMap1)));
assertFalse(KubernetesResourceUtil.isValidName("test.invalid.name"));
assertTrue(KubernetesResourceUtil.isValidLabelOrAnnotation(KubernetesResourceUtil.getOrCreateAnnotations(configMap1)));
assertTrue(KubernetesResourceUtil.areLabelsValid(KubernetesResourceUtil.getOrCreateAnnotations(configMap1)));
assertFalse(KubernetesResourceUtil
.isValidLabelOrAnnotation(Collections.singletonMap("NoUppercaseOrSpecialCharsLike=Equals", "bar")));
.areLabelsValid(Collections.singletonMap("NoUppercaseOrSpecialCharsLike=Equals", "bar")));

assertTrue(KubernetesResourceUtil.isValidName(KubernetesResourceUtil.sanitizeName("test.invalid.name")));
assertTrue(KubernetesResourceUtil.isValidName(KubernetesResourceUtil.sanitizeName("90notcool-n@me")));
assertTrue(KubernetesResourceUtil.isValidName(
KubernetesResourceUtil.sanitizeName("90notcool-n@me_______waytoooooooooolooooooooongand should be shorten for sure")));
}

@Test
void testSubdomainValidation() {
assertTrue(KubernetesResourceUtil.isValidSubdomainName("a.b"));
assertFalse(KubernetesResourceUtil.isValidSubdomainName("a..b"));
}

@Test
void testKeyValidation() {
assertTrue(KubernetesResourceUtil.isValidKey("domain.prefix.io/label.key"));
assertFalse(KubernetesResourceUtil.isValidKey("domain.prefix.io?label.key"));
}

@Test
void testAnnotationValidation() {
assertTrue(
KubernetesResourceUtil.areAnnotationsValid(Collections.singletonMap("simple-key", "Something not valid for a label!")));
}

@Test
void testSortEventListBasedOnTimestamp() {
// Given
Expand Down

0 comments on commit 00e7c55

Please sign in to comment.