From 51a2d56075c916d4a23610683d393d71573b7411 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 1 Jun 2023 12:34:33 +0200 Subject: [PATCH 1/3] deps: Bump kubernetes-client-bom from 6.6.2 to 6.7.1 Signed-off-by: Marc Nuri --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e9821fc3fc95b..16b741b341380 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ 0.8.10 - 6.6.2 + 6.7.1 1.55.1 From 2f85dad0cf741423188e6a194f59b0d75c1f54c2 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Thu, 8 Jun 2023 06:02:40 +0200 Subject: [PATCH 2/3] feat: customizable KubernetesClient-specific ObjectMapper and KubernetesSerialization Signed-off-by: Marc Nuri --- docs/src/main/asciidoc/kubernetes-client.adoc | 43 +++++++++++++ .../deployment/KubernetesClientProcessor.java | 5 ++ .../KubernetesClientObjectMapperCDITest.java | 61 +++++++++++++++++++ .../client/runtime/KubernetesClientUtils.java | 3 + .../client/KubernetesClientObjectMapper.java | 24 ++++++++ ...ubernetesClientObjectMapperCustomizer.java | 40 ++++++++++++ .../client/KubernetesConfigCustomizer.java | 5 +- .../KubernetesClientObjectMapperProducer.java | 31 ++++++++++ .../runtime/KubernetesClientProducer.java | 6 +- .../KubernetesSerializationProducer.java | 23 +++++++ 10 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 extensions/kubernetes-client/deployment/src/test/java/io/quarkus/kubernetes/client/deployment/KubernetesClientObjectMapperCDITest.java create mode 100644 extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesClientObjectMapper.java create mode 100644 extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesClientObjectMapperCustomizer.java create mode 100644 extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientObjectMapperProducer.java create mode 100644 extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationProducer.java diff --git a/docs/src/main/asciidoc/kubernetes-client.adoc b/docs/src/main/asciidoc/kubernetes-client.adoc index 3e8074bc4d908..20e6f2df149b9 100644 --- a/docs/src/main/asciidoc/kubernetes-client.adoc +++ b/docs/src/main/asciidoc/kubernetes-client.adoc @@ -61,6 +61,8 @@ In dev mode and when running tests, xref:kubernetes-dev-services.adoc[Dev Servic Quarkus provides multiple integration points for influencing the Kubernetes Client provided as a CDI bean. +==== Kubernetes Client Config customization + The first integration point is the use of the `io.quarkus.kubernetes.client.KubernetesConfigCustomizer` interface. When such a bean exists, it allows for arbitrary customizations of the `io.fabric8.kubernetes.client.Config` created by Quarkus (which takes into account the `quarkus.kubernetes-client.*` properties). @@ -82,6 +84,47 @@ public class KubernetesClientProducer { } ---- +==== Kubernetes Client ObjectMapper customization + +The Fabric8 Kubernetes Client uses its own `ObjectMapper` instance for serialization and deserialization of Kubernetes resources. +This mapper is provided to the client through a `KubernetesSerialization` instance that's injected into +the `KubernetesClient` bean. + +If for some reason you must customize the default `ObjectMapper` bean provided by this extension and used by the Kubernetes Client, you can do so by declaring a bean that implements the `KubernetesClientObjectMapperCustomizer` interface. + +The following code snippet contains an example of a `KubernetesClientObjectMapperCustomizer` to set the `ObjectMapper` locale: + +[source,java] +---- +@Singleton +public static class Customizer implements KubernetesClientObjectMapperCustomizer { + @Override + public void customize(ObjectMapper objectMapper) { + objectMapper.setLocale(Locale.ROOT); + } +} +---- + +Furthermore, if you must replace the default `ObjectMapper` bean used by the Kubernetes Client that the extension creates automatically, you can do so by declaring a bean of type `@KubernetesClientObjectMapper`. +The following code snippet shows how you can declare this bean: + +[source,java] +---- +@Singleton +public class KubernetesObjectMapperProducer { + @KubernetesClientObjectMapper + @Singleton + @Produces + public ObjectMapper kubernetesClientObjectMapper() { + return new ObjectMapper(); + } +} +---- + + +WARNING: The static `io.fabric8.kubernetes.client.utils.Serialization` utils class is deprecated and should not be used. +Access to `Serialization.jsonMapper()` should be replaced by the usage of @KubernetesClientObjectMapperCustomizer` declared beans. + == Testing To make testing against a mock Kubernetes API extremely simple, Quarkus provides the `WithKubernetesTestServer` annotation which automatically launches diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java index b04612791c9d5..5174c40f1f8e5 100644 --- a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesClientProcessor.java @@ -52,8 +52,10 @@ import io.quarkus.deployment.util.JandexUtil; import io.quarkus.jackson.deployment.IgnoreJsonDeserializeClassBuildItem; import io.quarkus.kubernetes.client.runtime.KubernetesClientBuildConfig; +import io.quarkus.kubernetes.client.runtime.KubernetesClientObjectMapperProducer; import io.quarkus.kubernetes.client.runtime.KubernetesClientProducer; import io.quarkus.kubernetes.client.runtime.KubernetesConfigProducer; +import io.quarkus.kubernetes.client.runtime.KubernetesSerializationProducer; import io.quarkus.kubernetes.client.spi.KubernetesClientCapabilityBuildItem; import io.quarkus.maven.dependency.ArtifactKey; @@ -77,6 +79,9 @@ public class KubernetesClientProcessor { @BuildStep public void registerBeanProducers(BuildProducer additionalBeanBuildItemBuildItem, Capabilities capabilities) { + additionalBeanBuildItemBuildItem + .produce(AdditionalBeanBuildItem.unremovableOf(KubernetesClientObjectMapperProducer.class)); + additionalBeanBuildItemBuildItem.produce(AdditionalBeanBuildItem.unremovableOf(KubernetesSerializationProducer.class)); // wire up the Config bean support additionalBeanBuildItemBuildItem.produce(AdditionalBeanBuildItem.unremovableOf(KubernetesConfigProducer.class)); // do not register our client producer if the openshift client is present, because it provides it too diff --git a/extensions/kubernetes-client/deployment/src/test/java/io/quarkus/kubernetes/client/deployment/KubernetesClientObjectMapperCDITest.java b/extensions/kubernetes-client/deployment/src/test/java/io/quarkus/kubernetes/client/deployment/KubernetesClientObjectMapperCDITest.java new file mode 100644 index 0000000000000..256e75a5071db --- /dev/null +++ b/extensions/kubernetes-client/deployment/src/test/java/io/quarkus/kubernetes/client/deployment/KubernetesClientObjectMapperCDITest.java @@ -0,0 +1,61 @@ +package io.quarkus.kubernetes.client.deployment; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import jakarta.inject.Inject; +import jakarta.inject.Singleton; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.quarkus.kubernetes.client.KubernetesClientObjectMapper; +import io.quarkus.kubernetes.client.KubernetesClientObjectMapperCustomizer; +import io.quarkus.test.QuarkusUnitTest; + +public class KubernetesClientObjectMapperCDITest { + + @Inject + KubernetesClient kubernetesClient; + + @Inject + @KubernetesClientObjectMapper + ObjectMapper objectMapper; + + @Test + public void kubernetesClientObjectMapperCustomizer() throws JsonProcessingException { + final var result = objectMapper.readValue("{\"quarkusName\":\"the-name\"}", ObjectMeta.class); + assertEquals("the-name", result.getName()); + } + + @Test + public void kubernetesClientUsesCustomizedObjectMapper() { + final var result = kubernetesClient.getKubernetesSerialization() + .unmarshal("{\"quarkusName\":\"the-name\"}", ObjectMeta.class); + assertEquals("the-name", result.getName()); + } + + @RegisterExtension + static QuarkusUnitTest runner = new QuarkusUnitTest() + .withApplicationRoot((jar) -> jar + .addClasses(KubernetesClientCDITest.Customizer.class)) + .overrideConfigKey("quarkus.kubernetes-client.devservices.enabled", "false"); + + @Singleton + public static class Customizer implements KubernetesClientObjectMapperCustomizer { + @Override + public void customize(ObjectMapper objectMapper) { + objectMapper.addMixIn(ObjectMeta.class, ObjectMetaMixin.class); + } + + private static final class ObjectMetaMixin { + @JsonProperty("quarkusName") + String name; + } + } +} diff --git a/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientUtils.java b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientUtils.java index ac27ac253b6a2..e217e7a2675e7 100644 --- a/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientUtils.java +++ b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientUtils.java @@ -14,6 +14,9 @@ public class KubernetesClientUtils { private static final String PREFIX = "quarkus.kubernetes-client."; + private KubernetesClientUtils() { + } + public static Config createConfig(KubernetesClientBuildConfig buildConfig, TlsConfig tlsConfig) { Config base = Config.autoConfigure(null); boolean trustAll = buildConfig.trustCerts.isPresent() ? buildConfig.trustCerts.get() : tlsConfig.trustAll; diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesClientObjectMapper.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesClientObjectMapper.java new file mode 100644 index 0000000000000..87438c4638022 --- /dev/null +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesClientObjectMapper.java @@ -0,0 +1,24 @@ +package io.quarkus.kubernetes.client; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.inject.Qualifier; + +/** + * {@link Qualifier} to inject the Fabric8 Kubernetes Client specific {@link com.fasterxml.jackson.databind.ObjectMapper}. + *

+ * Allows users to modify the behavior of the mapper for very specific use cases (such as adding Kotlin-specific modules). + * Otherwise, it's not recommended to modify the mapper since it might break the Kubernetes Client. + */ +@Qualifier +@Retention(RUNTIME) +@Target({ METHOD, FIELD, PARAMETER, TYPE }) +public @interface KubernetesClientObjectMapper { +} diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesClientObjectMapperCustomizer.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesClientObjectMapperCustomizer.java new file mode 100644 index 0000000000000..fab8011c469a0 --- /dev/null +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesClientObjectMapperCustomizer.java @@ -0,0 +1,40 @@ +package io.quarkus.kubernetes.client; + +import java.util.List; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; +import io.quarkus.kubernetes.client.runtime.KubernetesClientObjectMapperProducer; +import io.quarkus.kubernetes.client.runtime.KubernetesClientProducer; +import io.quarkus.kubernetes.client.runtime.KubernetesSerializationProducer; + +/** + * Allow the provision of beans that customize the default {@link ObjectMapper} used by the KubernetesClient to + * perform kubernetes-specific serialization and deserialization operations. + *

+ * The resulting {@link ObjectMapper} is used to produce the default {@link KubernetesSerialization} bean, which is in turn + * used to produce the default {@link io.fabric8.kubernetes.client.KubernetesClient} bean. + *

+ * The following code snippet shows how to provide a KubernetesClientObjectMapperCustomizer: + * + *

{@code
+ *
+ * @Singleton
+ * public static class Customizer implements KubernetesClientObjectMapperCustomizer {
+ *     @Override
+ *     public void customize(ObjectMapper objectMapper) {
+ *         objectMapper.setLocale(Locale.ROOT);
+ *     }
+ * }
+ *
+ * }
+ * + * @see KubernetesClientObjectMapperProducer#kubernetesClientObjectMapper(List) + * @see KubernetesSerializationProducer#kubernetesSerialization(ObjectMapper, Class[]) + * @see KubernetesClientProducer#kubernetesClient(KubernetesSerialization, Config) + */ +public interface KubernetesClientObjectMapperCustomizer { + void customize(ObjectMapper objectMapper); +} diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesConfigCustomizer.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesConfigCustomizer.java index 1ff0a573bfe7c..075cd0e4497f2 100644 --- a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesConfigCustomizer.java +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesConfigCustomizer.java @@ -4,6 +4,7 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; import io.quarkus.kubernetes.client.runtime.KubernetesClientBuildConfig; import io.quarkus.kubernetes.client.runtime.KubernetesClientProducer; import io.quarkus.kubernetes.client.runtime.KubernetesConfigProducer; @@ -16,8 +17,8 @@ * The {@link Config} is in turn used to produce the default {@link KubernetesClient} *

* - * @see KubernetesConfigProducer#config(KubernetesClientBuildConfig, TlsConfig, List) } - * @see KubernetesClientProducer#kubernetesClient(Config) } + * @see KubernetesConfigProducer#config(KubernetesClientBuildConfig, TlsConfig, List) + * @see KubernetesClientProducer#kubernetesClient(KubernetesSerialization, Config) */ public interface KubernetesConfigCustomizer { diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientObjectMapperProducer.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientObjectMapperProducer.java new file mode 100644 index 0000000000000..0a7032c9b44c3 --- /dev/null +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientObjectMapperProducer.java @@ -0,0 +1,31 @@ +package io.quarkus.kubernetes.client.runtime; + +import java.util.List; + +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.arc.All; +import io.quarkus.arc.DefaultBean; +import io.quarkus.kubernetes.client.KubernetesClientObjectMapper; +import io.quarkus.kubernetes.client.KubernetesClientObjectMapperCustomizer; + +@Singleton +public class KubernetesClientObjectMapperProducer { + + @KubernetesClientObjectMapper + @DefaultBean + @Priority(Integer.MIN_VALUE) + @Singleton + @Produces + public ObjectMapper kubernetesClientObjectMapper(@All List customizers) { + final var result = new ObjectMapper(); + for (var customizer : customizers) { + customizer.customize(result); + } + return result; + } +} diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java index 1915254bb3ce1..63d4e4f77a1ee 100644 --- a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesClientProducer.java @@ -7,6 +7,7 @@ import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClient; import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; import io.quarkus.arc.DefaultBean; @Singleton @@ -17,8 +18,9 @@ public class KubernetesClientProducer { @DefaultBean @Singleton @Produces - public KubernetesClient kubernetesClient(Config config) { - client = new KubernetesClientBuilder().withConfig(config).build(); + public KubernetesClient kubernetesClient(KubernetesSerialization kubernetesSerialization, Config config) { + client = new KubernetesClientBuilder() + .withKubernetesSerialization(kubernetesSerialization).withConfig(config).build(); return client; } diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationProducer.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationProducer.java new file mode 100644 index 0000000000000..e1b7690c92c92 --- /dev/null +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationProducer.java @@ -0,0 +1,23 @@ +package io.quarkus.kubernetes.client.runtime; + +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Singleton; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; +import io.quarkus.arc.DefaultBean; +import io.quarkus.kubernetes.client.KubernetesClientObjectMapper; + +@Singleton +public class KubernetesSerializationProducer { + + @DefaultBean + @Singleton + @Produces + public KubernetesSerialization kubernetesSerialization(@KubernetesClientObjectMapper ObjectMapper objectMapper) { + final var kubernetesSerialization = new KubernetesSerialization(objectMapper, false); + KubernetesClientUtils.scanKubernetesResources().forEach(kubernetesSerialization::registerKubernetesResource); + return kubernetesSerialization; + } +} From fea1aeeaa69e67be4ee8e6b0cb0d9fd7048d1055 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Fri, 9 Jun 2023 15:29:22 +0200 Subject: [PATCH 3/3] feat: static KubernetesResource class registration in synthetic bean Signed-off-by: Marc Nuri --- .../KubernetesResourceBuildStep.java | 61 +++++++++++++++++++ .../KubernetesSerializationRecorder.java | 24 ++++++++ .../client/KubernetesResources.java | 18 ++++++ .../KubernetesSerializationProducer.java | 9 ++- .../spi/KubernetesResourcesBuildItem.java | 16 +++++ 5 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesResourceBuildStep.java create mode 100644 extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationRecorder.java create mode 100644 extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesResources.java create mode 100644 extensions/kubernetes-client/spi/src/main/java/io/quarkus/kubernetes/client/spi/KubernetesResourcesBuildItem.java diff --git a/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesResourceBuildStep.java b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesResourceBuildStep.java new file mode 100644 index 0000000000000..da13f1ea08b23 --- /dev/null +++ b/extensions/kubernetes-client/deployment/src/main/java/io/quarkus/kubernetes/client/deployment/KubernetesResourceBuildStep.java @@ -0,0 +1,61 @@ +package io.quarkus.kubernetes.client.deployment; + +import java.util.HashSet; +import java.util.ServiceLoader; +import java.util.Set; + +import jakarta.inject.Singleton; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; + +import io.fabric8.kubernetes.api.model.KubernetesResource; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; +import io.quarkus.kubernetes.client.KubernetesResources; +import io.quarkus.kubernetes.client.runtime.KubernetesSerializationRecorder; +import io.quarkus.kubernetes.client.spi.KubernetesResourcesBuildItem; + +public class KubernetesResourceBuildStep { + @BuildStep + void scanKubernetesResourceClasses( + BuildProducer serviceProviderProducer, + BuildProducer kubernetesResourcesBuildItemBuildProducer) { + serviceProviderProducer.produce(ServiceProviderBuildItem.allProvidersFromClassPath(KubernetesResource.class.getName())); + final Set resourceClasses = new HashSet<>(); + final var serviceLoader = ServiceLoader.load(KubernetesResource.class); + for (var kr : serviceLoader) { + final var className = kr.getClass().getName(); + // Filter build-time only available classes from those that are really available at runtime + // e.g. The Kubernetes extension provides KubernetesResource classes only for deployment purposes (not prod code) + if (QuarkusClassLoader.isClassPresentAtRuntime(className)) { + resourceClasses.add(className); + } + } + kubernetesResourcesBuildItemBuildProducer.produce( + new KubernetesResourcesBuildItem(resourceClasses.toArray(String[]::new))); + } + + @BuildStep + @Record(ExecutionTime.STATIC_INIT) + SyntheticBeanBuildItem kubernetesResourceClasses( + KubernetesSerializationRecorder recorder, + KubernetesResourcesBuildItem kubernetesResourcesBuildItem, + BuildProducer additionalBeans) { + additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(KubernetesResources.class)); + final var classArray = Type.create(DotName.createSimple(Class[].class.getName()), Type.Kind.ARRAY); + return SyntheticBeanBuildItem + .configure(Object.class).providerType(classArray).addType(classArray) + .scope(Singleton.class) + .qualifiers(AnnotationInstance.builder(KubernetesResources.class).build()) + .runtimeValue(recorder.initKubernetesResources(kubernetesResourcesBuildItem.getResourceClasses())) + .done(); + } +} diff --git a/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationRecorder.java b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationRecorder.java new file mode 100644 index 0000000000000..8b8a37b7c3330 --- /dev/null +++ b/extensions/kubernetes-client/runtime-internal/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationRecorder.java @@ -0,0 +1,24 @@ +package io.quarkus.kubernetes.client.runtime; + +import java.util.HashSet; +import java.util.Set; + +import io.fabric8.kubernetes.api.model.KubernetesResource; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class KubernetesSerializationRecorder { + + public RuntimeValue[]> initKubernetesResources(String[] resourceClassNames) { + final Set> resourceClasses = new HashSet<>(); + for (var resourceClassName : resourceClassNames) { + try { + resourceClasses.add( + Class.forName(resourceClassName, false, KubernetesSerializationRecorder.class.getClassLoader())); + } catch (ClassNotFoundException e) { + } + } + return new RuntimeValue<>(resourceClasses.toArray(Class[]::new)); + } +} diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesResources.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesResources.java new file mode 100644 index 0000000000000..0d7e8ca56c168 --- /dev/null +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/KubernetesResources.java @@ -0,0 +1,18 @@ +package io.quarkus.kubernetes.client; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import jakarta.inject.Qualifier; + +@Qualifier +@Retention(RUNTIME) +@Target({ METHOD, FIELD, PARAMETER, TYPE }) +public @interface KubernetesResources { +} diff --git a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationProducer.java b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationProducer.java index e1b7690c92c92..a822ab09da0a8 100644 --- a/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationProducer.java +++ b/extensions/kubernetes-client/runtime/src/main/java/io/quarkus/kubernetes/client/runtime/KubernetesSerializationProducer.java @@ -8,6 +8,7 @@ import io.fabric8.kubernetes.client.utils.KubernetesSerialization; import io.quarkus.arc.DefaultBean; import io.quarkus.kubernetes.client.KubernetesClientObjectMapper; +import io.quarkus.kubernetes.client.KubernetesResources; @Singleton public class KubernetesSerializationProducer { @@ -15,9 +16,13 @@ public class KubernetesSerializationProducer { @DefaultBean @Singleton @Produces - public KubernetesSerialization kubernetesSerialization(@KubernetesClientObjectMapper ObjectMapper objectMapper) { + public KubernetesSerialization kubernetesSerialization( + @KubernetesClientObjectMapper ObjectMapper objectMapper, + @KubernetesResources Class[] kubernetesResources) { final var kubernetesSerialization = new KubernetesSerialization(objectMapper, false); - KubernetesClientUtils.scanKubernetesResources().forEach(kubernetesSerialization::registerKubernetesResource); + for (var kubernetesResource : kubernetesResources) { + kubernetesSerialization.registerKubernetesResource(kubernetesResource); + } return kubernetesSerialization; } } diff --git a/extensions/kubernetes-client/spi/src/main/java/io/quarkus/kubernetes/client/spi/KubernetesResourcesBuildItem.java b/extensions/kubernetes-client/spi/src/main/java/io/quarkus/kubernetes/client/spi/KubernetesResourcesBuildItem.java new file mode 100644 index 0000000000000..3b9af8e88951b --- /dev/null +++ b/extensions/kubernetes-client/spi/src/main/java/io/quarkus/kubernetes/client/spi/KubernetesResourcesBuildItem.java @@ -0,0 +1,16 @@ +package io.quarkus.kubernetes.client.spi; + +import io.quarkus.builder.item.SimpleBuildItem; + +public final class KubernetesResourcesBuildItem extends SimpleBuildItem { + + private final String[] resourceClasses; + + public KubernetesResourcesBuildItem(String[] resourceClasses) { + this.resourceClasses = resourceClasses; + } + + public String[] getResourceClasses() { + return resourceClasses; + } +}