From dc0dd96b141dd0a43ad9f3cd34cce6231c233742 Mon Sep 17 00:00:00 2001 From: Romain Cambier Date: Sun, 9 Jun 2024 11:42:06 +0200 Subject: [PATCH 1/2] Implement support for custom Api Versions in informers (#639) --- .../kubernetes/client/ModelMapper.java | 39 ++++++++++++++++--- .../kubernetes/client/ModelMapperSpec.groovy | 11 ++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/kubernetes-client/src/main/java/io/micronaut/kubernetes/client/ModelMapper.java b/kubernetes-client/src/main/java/io/micronaut/kubernetes/client/ModelMapper.java index b2da5d67c..528055415 100644 --- a/kubernetes-client/src/main/java/io/micronaut/kubernetes/client/ModelMapper.java +++ b/kubernetes-client/src/main/java/io/micronaut/kubernetes/client/ModelMapper.java @@ -20,11 +20,14 @@ import io.kubernetes.client.util.Strings; import org.apache.commons.lang3.tuple.MutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; /** * This class is inspired by the implementation of the io.kubernetes.client.util.ModelMapper.java. @@ -38,12 +41,17 @@ */ public class ModelMapper { + private static final Logger LOG = LoggerFactory.getLogger(ModelMapper.class); + // Model's api-group prefix to kubernetes api-group private final Map preBuiltApiGroups = new HashMap<>(); // Model's api-version midfix to kubernetes api-version private final List preBuiltApiVersions = new ArrayList<>(); + // This allows parsing custom (not included in kubernetes core) api versions + private final Pattern customVersionParser = Pattern.compile("(V[a-z1-9]+)[A-Z]+[a-zA-Z0-9]+"); + public ModelMapper() { initApiGroupMap(); initApiVersionList(); @@ -85,17 +93,37 @@ private void initApiVersionList() { private Pair getApiGroup(String name) { return preBuiltApiGroups.entrySet().stream() .filter(e -> name.startsWith(e.getKey())) - .map(e -> new MutablePair(e.getValue(), name.substring(e.getKey().length()))) + .map(e -> new MutablePair<>(e.getValue(), name.substring(e.getKey().length()))) .findFirst() - .orElse(new MutablePair(null, name)); + .orElse(new MutablePair<>(null, name)); } private Pair getApiVersion(String name) { return preBuiltApiVersions.stream() .filter(name::startsWith) - .map(v -> new MutablePair(v.toLowerCase(), name.substring(v.length()))) + .map(v -> new MutablePair<>(v, extractKind(v, name))) .findFirst() - .orElse(new MutablePair(null, name)); + .orElseGet(() -> { + String version = tryGuessCustomApiVersion(name); + return new MutablePair<>(version, extractKind(version, name)); + }); + } + + private String extractKind(String version, String name){ + return version == null ? name : name.substring(version.length()); + } + + private String tryGuessCustomApiVersion(String name) { + var patternMatcher = customVersionParser.matcher(name); + + if (patternMatcher.matches() && patternMatcher.groupCount() == 1) { + return patternMatcher.group(1); + } + + // Warn the user to avoid wasted debug time (cfr https://github.com/micronaut-projects/micronaut-kubernetes/issues/639) + LOG.warn("Could not extract ApiVersion from entity {}", name); + + return null; } /** @@ -109,8 +137,9 @@ public GroupVersionKind getGroupVersionKindByClass(Class versionAndOther = getApiVersion(groupAndOther.getRight()); String group = Strings.nullToEmpty(groupAndOther.getLeft()); - String version = versionAndOther.getLeft(); + String version = versionAndOther.getLeft() == null ? null : versionAndOther.getLeft().toLowerCase(); String kind = versionAndOther.getRight(); + return new GroupVersionKind(group, version, kind); } } diff --git a/kubernetes-client/src/test/groovy/io/micronaut/kubernetes/client/ModelMapperSpec.groovy b/kubernetes-client/src/test/groovy/io/micronaut/kubernetes/client/ModelMapperSpec.groovy index eb301f810..816d4a433 100644 --- a/kubernetes-client/src/test/groovy/io/micronaut/kubernetes/client/ModelMapperSpec.groovy +++ b/kubernetes-client/src/test/groovy/io/micronaut/kubernetes/client/ModelMapperSpec.groovy @@ -25,4 +25,15 @@ class ModelMapperSpec extends Specification { it.kind == "ClusterRole" } } + + def "it resolves custom api versions"() { + expect: + with(mapper.getGroupVersionKindByClass(V3CustomResource)) { + it.version == "v3" + it.group == "" + it.kind == "CustomResource" + } + } + + static class V3CustomResource{} } From 36d77137998f60577c90751180d7471b3ae8ef22 Mon Sep 17 00:00:00 2001 From: Romain Cambier Date: Thu, 18 Jul 2024 21:32:36 +0200 Subject: [PATCH 2/2] Update parsing regex to rely on the kubernetes core one --- .../main/java/io/micronaut/kubernetes/client/ModelMapper.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kubernetes-client/src/main/java/io/micronaut/kubernetes/client/ModelMapper.java b/kubernetes-client/src/main/java/io/micronaut/kubernetes/client/ModelMapper.java index 528055415..33feecfed 100644 --- a/kubernetes-client/src/main/java/io/micronaut/kubernetes/client/ModelMapper.java +++ b/kubernetes-client/src/main/java/io/micronaut/kubernetes/client/ModelMapper.java @@ -50,7 +50,9 @@ public class ModelMapper { private final List preBuiltApiVersions = new ArrayList<>(); // This allows parsing custom (not included in kubernetes core) api versions - private final Pattern customVersionParser = Pattern.compile("(V[a-z1-9]+)[A-Z]+[a-zA-Z0-9]+"); + // It is based on the kubernetes core one available at https://github.com/kubernetes/apimachinery/blob/master/pkg/util/version/version.go + // and is completed to be able to proceed to extraction from a java class name + private final Pattern customVersionParser = Pattern.compile("^\\s*(V(?:[0-9]+(?:\\.[0-9]+)*)(?:[a-z0-9]*)*)[A-Z]+[a-zA-Z0-9]*$"); public ModelMapper() { initApiGroupMap();