diff --git a/spring-cloud-kubernetes-fabric8-autoconfig/src/main/java/org/springframework/cloud/kubernetes/fabric8/Fabric8RuntimeHints.java b/spring-cloud-kubernetes-fabric8-autoconfig/src/main/java/org/springframework/cloud/kubernetes/fabric8/Fabric8RuntimeHints.java new file mode 100644 index 0000000000..8071f5b4d4 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-autoconfig/src/main/java/org/springframework/cloud/kubernetes/fabric8/Fabric8RuntimeHints.java @@ -0,0 +1,110 @@ +/* + * Copyright 2013-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapList; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretList; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.impl.KubernetesClientImpl; + +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.RuntimeHintsRegistrar; +import org.springframework.aot.hint.TypeReference; +import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; + +/** + * {@link RuntimeHintsRegistrar} for Spring Cloud Kubernetes Fabric8 native image support. + * + *

+ * Registers reflection hints required for GraalVM native image compilation when using the + * Fabric8 Kubernetes client with Spring Cloud Kubernetes. + * + * @author Abu Hena Mostafa Kamal + * @since 5.0.3 + */ +public class Fabric8RuntimeHints implements RuntimeHintsRegistrar { + + @Override + public void registerHints(RuntimeHints hints, ClassLoader classLoader) { + // ===== FABRIC8 CLIENT CLASSES ===== + // Fabric8 client impl - loaded reflectively via KubernetesClientBuilder + hints.reflection() + .registerType(TypeReference.of(KubernetesClientImpl.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS); + + // KubernetesClientBuilder - used in Fabric8AutoConfiguration + hints.reflection() + .registerType(TypeReference.of(KubernetesClientBuilder.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS); + + // Fabric8 Config - used in Fabric8AutoConfiguration + hints.reflection() + .registerType(TypeReference.of(Config.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.ACCESS_DECLARED_FIELDS); + + // ConfigBuilder - used in Fabric8AutoConfiguration.kubernetesClientConfig() + hints.reflection() + .registerType(TypeReference.of(ConfigBuilder.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.INVOKE_DECLARED_METHODS); + + // ===== KUBERNETES RESOURCES (CRITICAL FOR CONFIGMAP READING) ===== + // ConfigMap - the actual resource being read + hints.reflection() + .registerType(TypeReference.of(ConfigMap.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS); + + // ConfigMapList - for listing ConfigMaps + hints.reflection() + .registerType(TypeReference.of(ConfigMapList.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS); + + // ObjectMeta - metadata for all Kubernetes resources + hints.reflection() + .registerType(TypeReference.of(ObjectMeta.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS); + + // Secret - if using secrets + hints.reflection() + .registerType(TypeReference.of(Secret.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS); + + hints.reflection() + .registerType(TypeReference.of(SecretList.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, + MemberCategory.ACCESS_DECLARED_FIELDS, MemberCategory.INVOKE_DECLARED_METHODS); + + hints.reflection() + .registerType(TypeReference.of(ConfigMapConfigProperties.class), + MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + + hints.reflection() + .registerType(TypeReference.of(SecretsConfigProperties.class), MemberCategory.INVOKE_DECLARED_CONSTRUCTORS); + + // ===== RESOURCE HINTS ===== + // Kubernetes service account and Fabric8 service files + hints.resources().registerPattern("classpath*:META-INF/services/io.fabric8.kubernetes.client.*"); + + hints.resources().registerPattern("classpath*:META-INF/fabric8/*"); + } + +} diff --git a/spring-cloud-kubernetes-fabric8-autoconfig/src/main/resources/META-INF/spring/aot.factories b/spring-cloud-kubernetes-fabric8-autoconfig/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..3d348bed44 --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-autoconfig/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1,2 @@ +org.springframework.aot.hint.RuntimeHintsRegistrar=\ +org.springframework.cloud.kubernetes.fabric8.Fabric8RuntimeHints diff --git a/spring-cloud-kubernetes-fabric8-autoconfig/src/test/java/org/springframework/cloud/kubernetes/fabric8/Fabric8RuntimeHintsTests.java b/spring-cloud-kubernetes-fabric8-autoconfig/src/test/java/org/springframework/cloud/kubernetes/fabric8/Fabric8RuntimeHintsTests.java new file mode 100644 index 0000000000..5c0c55938d --- /dev/null +++ b/spring-cloud-kubernetes-fabric8-autoconfig/src/test/java/org/springframework/cloud/kubernetes/fabric8/Fabric8RuntimeHintsTests.java @@ -0,0 +1,88 @@ +/* + * Copyright 2013-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.cloud.kubernetes.fabric8; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.ConfigMapList; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretList; +import io.fabric8.kubernetes.client.Config; +import io.fabric8.kubernetes.client.ConfigBuilder; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.impl.KubernetesClientImpl; +import org.junit.jupiter.api.Test; + +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; +import org.springframework.cloud.kubernetes.commons.config.ConfigMapConfigProperties; +import org.springframework.cloud.kubernetes.commons.config.SecretsConfigProperties; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Fabric8RuntimeHints}. + * + * @author Abu Hena Mostafa Kamal + * @since 5.0.3 + */ +class Fabric8RuntimeHintsTests { + + private final Fabric8RuntimeHints hintsRegistrar = new Fabric8RuntimeHints(); + + @Test + void fabric8ClientClassesShouldBeRegistered() { + RuntimeHints hints = new RuntimeHints(); + hintsRegistrar.registerHints(hints, null); + + assertThat(RuntimeHintsPredicates.reflection().onType(KubernetesClientImpl.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(KubernetesClientBuilder.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(Config.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(ConfigBuilder.class)).accepts(hints); + } + + @Test + void kubernetesResourcesShouldBeRegistered() { + RuntimeHints hints = new RuntimeHints(); + hintsRegistrar.registerHints(hints, null); + + assertThat(RuntimeHintsPredicates.reflection().onType(ConfigMap.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(ConfigMapList.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(ObjectMeta.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(Secret.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(SecretList.class)).accepts(hints); + } + + @Test + void configPropertiesShouldBeRegistered() { + RuntimeHints hints = new RuntimeHints(); + hintsRegistrar.registerHints(hints, null); + + assertThat(RuntimeHintsPredicates.reflection().onType(ConfigMapConfigProperties.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(SecretsConfigProperties.class)).accepts(hints); + } + + @Test + void unrelatedClassesShouldNotBeRegistered() { + RuntimeHints hints = new RuntimeHints(); + hintsRegistrar.registerHints(hints, null); + + assertThat(RuntimeHintsPredicates.reflection().onType(String.class)).rejects(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(Integer.class)).rejects(hints); + } + +}