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; + } +}