From 296aafd0fba5ec40b9bfb450b6020107a028a38e Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 24 May 2023 10:52:09 -0400 Subject: [PATCH] fix #4662: a few cleanups and exposing more control over the discovery --- .../client/KubernetesClientBuilder.java | 3 +- .../client/utils/KubernetesSerialization.java | 47 ++++++++++--------- .../client/utils/OpenIDConnectionUtils.java | 5 +- .../client/utils/Serialization.java | 3 ++ .../cache/ReducedStateItemStoreTest.java | 3 +- .../kubernetes/client/impl/Handlers.java | 2 +- .../client/mock/CustomResourceCrudTest.java | 3 +- .../CustomResourceCrudWithCRDContextTest.java | 4 +- .../mock/DefaultSharedIndexInformerTest.java | 3 +- 9 files changed, 36 insertions(+), 37 deletions(-) diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java index 805f5eccdbe..c450be2a436 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/KubernetesClientBuilder.java @@ -16,7 +16,6 @@ package io.fabric8.kubernetes.client; -import com.fasterxml.jackson.databind.ObjectMapper; import io.fabric8.kubernetes.client.http.HttpClient; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; import io.fabric8.kubernetes.client.utils.HttpClientUtils; @@ -49,7 +48,7 @@ default void onClose(Executor executor) { private Class clazz; private ExecutorSupplier executorSupplier; private Consumer builderConsumer; - private KubernetesSerialization kubernetesSerialization = new KubernetesSerialization(new ObjectMapper()); + private KubernetesSerialization kubernetesSerialization = new KubernetesSerialization(); public KubernetesClientBuilder() { // basically the same logic as in KubernetesResourceUtil for finding list types diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java index 52d5755aad7..be8cd7a9d2f 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.jsontype.TypeIdResolver; import com.fasterxml.jackson.databind.jsontype.TypeResolverBuilder; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesResource; import io.fabric8.kubernetes.api.model.KubernetesResourceList; import io.fabric8.kubernetes.api.model.runtime.RawExtension; @@ -58,23 +59,35 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.ServiceLoader; public class KubernetesSerialization { private final ObjectMapper mapper; private final UnmatchedFieldTypeModule unmatchedFieldTypeModule = new UnmatchedFieldTypeModule(); private KubernetesDeserializer kubernetesDeserializer; + private final boolean searchClassloaders; + /** + * Creates a new instance with a fresh ObjectMapper + */ public KubernetesSerialization() { - this(new ObjectMapper()); + this(new ObjectMapper(), true); } - public KubernetesSerialization(ObjectMapper mapper) { + /** + * Creates a new instance with the given ObjectMapper, which will be configured for use for + * kubernetes resource serialization / deserialization. + * + * @param searchClassloaders if {@link KubernetesResource} should be automatically discovered via {@link ServiceLoader} + */ + public KubernetesSerialization(ObjectMapper mapper, boolean searchClassloaders) { this.mapper = mapper; + this.searchClassloaders = searchClassloaders; configureMapper(mapper); } - private void configureMapper(ObjectMapper mapper) { + protected void configureMapper(ObjectMapper mapper) { mapper.registerModules(new JavaTimeModule(), unmatchedFieldTypeModule); mapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE); HandlerInstantiator instanciator = mapper.getDeserializationConfig().getHandlerInstantiator(); @@ -131,8 +144,7 @@ public TypeIdResolver typeIdResolverInstance(MapperConfig config, Annotated a private synchronized KubernetesDeserializer getKubernetesDeserializer() { // created lazily to avoid holding all model classes in memory by default if (this.kubernetesDeserializer == null) { - // TODO: expose control over KubernetesDeserializer class registration - this.kubernetesDeserializer = new KubernetesDeserializer(true); + this.kubernetesDeserializer = new KubernetesDeserializer(searchClassloaders); } return kubernetesDeserializer; } @@ -244,17 +256,6 @@ public T unmarshal(InputStream is, TypeReference type) { } } - private boolean isLikelyYaml(BufferedInputStream bis) throws IOException { - bis.mark(-1); - int intch; - do { - intch = bis.read(); - } while (intch > -1 && Character.isWhitespace(intch)); - bis.reset(); - - return intch != '{' && intch != '['; - } - /** * If multiple docs exist, only non-null resources will be kept. Results spanning multiple docs * will be returned as a List of KubernetesResource @@ -368,7 +369,7 @@ public Type constructParametricType(Class parameterizedClass, Class parame return mapper.getTypeFactory().constructParametricType(parameterizedClass, parameterType); } - public Class getRegisteredKind(String apiVersion, String kind) { + public Class getRegisteredKubernetesResource(String apiVersion, String kind) { return this.getKubernetesDeserializer().getRegisteredKind(apiVersion, kind); } @@ -381,16 +382,16 @@ ObjectMapper getMapper() { } /** - * Registers a Custom Resource Definition Kind + * Registers a new resource, which can then be recognized for deserialization */ - public void registerCustomKind(String kind, Class clazz) { - registerCustomKind(null, kind, clazz); + public void registerKubernetesResource(Class clazz) { + registerKubernetesResource(null, HasMetadata.getKind(clazz), clazz); } /** - * Registers a Custom Resource Definition Kind + * Registers a new resource, which can then be recognized for deserialization */ - public void registerCustomKind(String apiVersion, String kind, Class clazz) { + public void registerKubernetesResource(String apiVersion, String kind, Class clazz) { getKubernetesDeserializer().registerCustomKind(apiVersion, kind, clazz); } @@ -399,7 +400,7 @@ public String convertToJson(String input) { mapper.readTree(input); return input; // valid json } catch (JsonProcessingException e) { - return Serialization.asJson(Serialization.unmarshal(input, JsonNode.class)); + return asJson(unmarshal(input, JsonNode.class)); } } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java index a94df210975..1324c8b84a3 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/OpenIDConnectionUtils.java @@ -15,7 +15,6 @@ */ package io.fabric8.kubernetes.client.utils; -import com.fasterxml.jackson.core.JsonProcessingException; import io.fabric8.kubernetes.api.model.AuthInfo; import io.fabric8.kubernetes.api.model.NamedAuthInfo; import io.fabric8.kubernetes.api.model.NamedContext; @@ -333,12 +332,12 @@ public static boolean idTokenExpired(Config config) { String[] jwtParts = accessToken.split(JWT_PARTS_DELIMITER_REGEX); String jwtPayload = jwtParts[1]; String jwtPayloadDecoded = new String(Base64.getDecoder().decode(jwtPayload)); - Map jwtPayloadMap = Serialization.jsonMapper().readValue(jwtPayloadDecoded, Map.class); + Map jwtPayloadMap = Serialization.unmarshal(jwtPayloadDecoded, Map.class); int expiryTimestampInSeconds = (Integer) jwtPayloadMap.get(JWT_TOKEN_EXPIRY_TIMESTAMP_KEY); return Instant.ofEpochSecond(expiryTimestampInSeconds) .minusSeconds(TOKEN_EXPIRY_DELTA) .isBefore(Instant.now()); - } catch (JsonProcessingException e) { + } catch (Exception e) { return true; } } diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/Serialization.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/Serialization.java index 50e676692d2..479a8ddfc1d 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/Serialization.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/Serialization.java @@ -204,7 +204,10 @@ public static T unmarshal(InputStream is, final Class type) { * @param type The {@link TypeReference}. * @param Template argument denoting type * @return returns de-serialized object + * + * @deprecated use {@link KubernetesSerialization#unmarshal(InputStream, TypeReference)} */ + @Deprecated public static T unmarshal(InputStream is, TypeReference type) { return kubernetesSerialization.unmarshal(is, type); } diff --git a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/informers/cache/ReducedStateItemStoreTest.java b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/informers/cache/ReducedStateItemStoreTest.java index a51062399db..7438de08919 100644 --- a/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/informers/cache/ReducedStateItemStoreTest.java +++ b/kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/informers/cache/ReducedStateItemStoreTest.java @@ -15,7 +15,6 @@ */ package io.fabric8.kubernetes.client.informers.cache; -import com.fasterxml.jackson.databind.ObjectMapper; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.client.utils.KubernetesSerialization; @@ -30,7 +29,7 @@ class ReducedStateItemStoreTest { @Test void testStoreRestore() { ReducedStateItemStore store = new ReducedStateItemStore<>(ReducedStateItemStore.UID_KEY_STATE, - Pod.class, new KubernetesSerialization(new ObjectMapper()), "metadata.labels", "foo.bar"); + Pod.class, new KubernetesSerialization(), "metadata.labels", "foo.bar"); Pod pod = new PodBuilder().withNewSpec().endSpec().withNewMetadata().withUid("x").withName("y").addToLabels("one", "1") .addToLabels("two", "2").withResourceVersion("2").endMetadata().withNewStatus().endStatus().build(); diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/Handlers.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/Handlers.java index 8baeaebb47a..7ae449fddd5 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/Handlers.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/impl/Handlers.java @@ -84,7 +84,7 @@ public ResourceDefinitionContext getResourceDefinitionContext(String apiVersion, Client client) { // check if it's built-in Class clazz = client.adapt(BaseClient.class).getKubernetesSerialization() - .getRegisteredKind(apiVersion, kind); + .getRegisteredKubernetesResource(apiVersion, kind); ResourceDefinitionContext rdc = null; if (clazz != null) { diff --git a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceCrudTest.java b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceCrudTest.java index 5fbe9d6904f..87c395bf24e 100644 --- a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceCrudTest.java +++ b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceCrudTest.java @@ -22,7 +22,6 @@ import io.fabric8.kubernetes.client.dsl.MixedOperation; import io.fabric8.kubernetes.client.dsl.NonNamespaceOperation; import io.fabric8.kubernetes.client.dsl.Resource; -import io.fabric8.kubernetes.client.impl.BaseClient; import io.fabric8.kubernetes.client.mock.crd.CronTab; import io.fabric8.kubernetes.client.mock.crd.CronTabSpec; import io.fabric8.kubernetes.client.mock.crd.CronTabStatus; @@ -48,7 +47,7 @@ class CustomResourceCrudTest { @BeforeEach void setUp() { - client.adapt(BaseClient.class).getKubernetesSerialization().registerCustomKind("stable.example.com/v1", "CronTab", + client.getKubernetesSerialization().registerKubernetesResource("stable.example.com/v1", "CronTab", CronTab.class); cronTabCrd = client .apiextensions() diff --git a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceCrudWithCRDContextTest.java b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceCrudWithCRDContextTest.java index cf03b8af945..4cbb5faf4df 100644 --- a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceCrudWithCRDContextTest.java +++ b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceCrudWithCRDContextTest.java @@ -38,7 +38,7 @@ class CustomResourceCrudWithCRDContextTest { @Test void testCreateAndGet() { // Given - client.adapt(BaseClient.class).getKubernetesSerialization().registerCustomKind("demo.fabric8.io/v1alpha1", + client.getKubernetesSerialization().registerKubernetesResource("demo.fabric8.io/v1alpha1", "EntandoBundleRelease", EntandoBundleRelease.class); MixedOperation> ebrClient = client .resources(EntandoBundleRelease.class, EntandoBundleReleaseList.class); @@ -55,7 +55,7 @@ void testCreateAndGet() { @Test void testCreateAndGetWithInferredContext() { // Given - client.adapt(BaseClient.class).getKubernetesSerialization().registerCustomKind("demo.fabric8.io/v1alpha1", + client.adapt(BaseClient.class).getKubernetesSerialization().registerKubernetesResource("demo.fabric8.io/v1alpha1", "EntandoBundleRelease", EntandoBundleRelease.class); MixedOperation, Resource> ebrClient = client .resources(EntandoBundleRelease.class); diff --git a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/DefaultSharedIndexInformerTest.java b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/DefaultSharedIndexInformerTest.java index 2ad9367592b..e6825c25603 100644 --- a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/DefaultSharedIndexInformerTest.java +++ b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/DefaultSharedIndexInformerTest.java @@ -42,7 +42,6 @@ import io.fabric8.kubernetes.client.WatcherException; import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext; -import io.fabric8.kubernetes.client.impl.BaseClient; import io.fabric8.kubernetes.client.informers.ResourceEventHandler; import io.fabric8.kubernetes.client.informers.SharedIndexInformer; import io.fabric8.kubernetes.client.informers.SharedInformerFactory; @@ -1166,7 +1165,7 @@ void testGenericKubernetesResourceSharedIndexInformerWithAdditionalDeserializers r -> new WatchEvent(getAnimal("red-panda", "Carnivora", r), "ADDED"), null, null); // When - client.adapt(BaseClient.class).getKubernetesSerialization().registerCustomKind("jungle.example.com/v1", "Animal", + client.getKubernetesSerialization().registerKubernetesResource("jungle.example.com/v1", "Animal", CronTab.class); SharedIndexInformer animalSharedIndexInformer = client .genericKubernetesResources(animalContext)