diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 8ceee82e5a..9588665131 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -29,7 +29,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v3 - + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 17.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 43b79823ac..04cfd86995 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -43,16 +43,23 @@ jobs: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ matrix.java }}-${{ hashFiles('pom.xml', '**/pom.xml') }} - name: Build with Maven + shell: bash run: | - mvn clean test -q -B --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn + if [ $(grep -E '^(8|11)\.' <<< '${{ matrix.java }}') ]; then + # some module doesn't compile on java platform lower than 17, need to skip them by specifying a profile + MODS_OVERRIDES='-pl !spring-aot' + fi + mvn -q -B --define=org.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn $MODS_OVERRIDES clean test build-graalvm: runs-on: ubuntu-latest name: GraalVM Maven Test steps: - uses: actions/checkout@v3 - - uses: DeLaGuardo/setup-graalvm@48f2bf339ab7d35e31029b1822a213681fdfc42e + - uses: graalvm/setup-graalvm@v1 with: - graalvm-version: '19.3.0.java8' + version: '22.3.0' + java-version: '17' + components: 'native-image' - name: Build with Maven run: mvn -q test -B -Dorg.slf4j.simpleLogger.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=warn e2e: @@ -62,6 +69,11 @@ jobs: - uses: actions/checkout@v3 - name: Create k8s Kind Cluster uses: helm/kind-action@v1.4.0 + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: 17.0.x - name: Run E2E with Maven run: | mvn clean install \ @@ -81,7 +93,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: 11.0.x + java-version: 17.0.x - name: Cache local Maven repository uses: actions/cache@v3 with: diff --git a/pom.xml b/pom.xml index c63fc964c2..cc27c642ae 100644 --- a/pom.xml +++ b/pom.xml @@ -15,6 +15,7 @@ extended fluent spring + spring-aot e2e examples client-java-contrib/cert-manager @@ -63,6 +64,7 @@ 2.7.5 5.3.23 0.15.0 + 0.10.2 true diff --git a/spring-aot/pom.xml b/spring-aot/pom.xml new file mode 100644 index 0000000000..adbd697fee --- /dev/null +++ b/spring-aot/pom.xml @@ -0,0 +1,87 @@ + + + 4.0.0 + io.kubernetes + client-java-spring-aot-integration + bundle + client-java-spring-aot-integration + https://github.com/kubernetes-client/java + + client-java-parent + io.kubernetes + 16.0.0-SNAPSHOT + ../pom.xml + + + 3.0.0 + + + + org.reflections + reflections + ${reflections.version} + + + io.kubernetes + client-java-spring-integration + ${project.version} + + + org.springframework.boot + spring-boot + + + junit + junit + test + + + org.springframework.boot + spring-boot-test + test + + + org.springframework + spring-test + test + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + jacoco-initialize + + prepare-agent + + + + jacoco-report + test + + report + + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.apache.maven.plugins + maven-compiler-plugin + + 17 + 17 + + + + + diff --git a/spring-aot/src/main/java/io/kubernetes/client/spring/aot/KubernetesBeanFactoryInitializationAotProcessor.java b/spring-aot/src/main/java/io/kubernetes/client/spring/aot/KubernetesBeanFactoryInitializationAotProcessor.java new file mode 100644 index 0000000000..e7cbcd64cb --- /dev/null +++ b/spring-aot/src/main/java/io/kubernetes/client/spring/aot/KubernetesBeanFactoryInitializationAotProcessor.java @@ -0,0 +1,105 @@ +/* +Copyright 2022 The Kubernetes 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 +http://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 io.kubernetes.client.spring.aot; + +import com.google.gson.annotations.JsonAdapter; +import io.kubernetes.client.extended.controller.Controller; +import io.swagger.annotations.ApiModel; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import org.jetbrains.annotations.NotNull; +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.TypeReference; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; +import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.autoconfigure.AutoConfigurationPackages; + +/** + * GraalVM native images need to know about when you might reflectively work with types at runtime. + * The Kubernetes Java client works with several types reflectively at runtime. This code uses the + * third-party Reflections library to reflect upon all the code in your Spring Boot application or + * in the official Kubernetes Java client that has {@link ApiModel}, {@link JsonAdapter}, and + * registers them. It also registers a few other specific handful of classes that should be + * accounted for, in specific cases. + * + * @author Josh Long + */ +@SuppressWarnings("unused") +public class KubernetesBeanFactoryInitializationAotProcessor + implements BeanFactoryInitializationAotProcessor { + + private static final Logger LOGGER = + LoggerFactory.getLogger(KubernetesBeanFactoryInitializationAotProcessor.class); + + private final MemberCategory[] allMemberCategories = MemberCategory.values(); + + @Override + public BeanFactoryInitializationAotContribution processAheadOfTime( + @NotNull ConfigurableListableBeanFactory beanFactory) { + return (generationContext, beanFactoryInitializationCode) -> { + RuntimeHints hints = generationContext.getRuntimeHints(); + String[] classNames = + new String[] { + "com.google.gson.JsonElement", // + "io.kubernetes.client.informer.cache.ProcessorListener", // + "io.kubernetes.client.extended.controller.Controller", // + "io.kubernetes.client.util.generic.GenericKubernetesApi$StatusPatch", // + "io.kubernetes.client.util.Watch$Response" // + }; + for (String className : classNames) { + LOGGER.info("registering " + className + " for reflection"); + hints.reflection().registerType(TypeReference.of(className), allMemberCategories); + } + registerForPackage("io.kubernetes", hints); + Collection packages = AutoConfigurationPackages.get(beanFactory); + for (String packageName : packages) { + registerForPackage(packageName, hints); + } + }; + } + + private void registerForPackage(String packageName, RuntimeHints hints) { + Reflections reflections = new Reflections(packageName); + Set> apiModels = reflections.getTypesAnnotatedWith(ApiModel.class); + Set> controllers = reflections.getSubTypesOf(Controller.class); + Set> jsonAdapters = findJsonAdapters(reflections); + Set> all = new HashSet<>(); + all.addAll(jsonAdapters); + all.addAll(controllers); + all.addAll(apiModels); + for (Class clazz : all) { + LOGGER.info("registering " + clazz.getName() + " for reflection"); + hints.reflection().registerType(clazz, allMemberCategories); + } + } + + private Set> findJsonAdapters(Reflections reflections) { + Class jsonAdapterClass = JsonAdapter.class; + Set> classes = new HashSet<>(); + for (Class clazz : reflections.getTypesAnnotatedWith(jsonAdapterClass)) { + JsonAdapter annotation = clazz.getAnnotation(jsonAdapterClass); + if (null != annotation) { + classes.add(annotation.value()); + } + classes.add(clazz); + } + return classes; + } +} diff --git a/spring-aot/src/main/resources/META-INF/spring/aot.factories b/spring-aot/src/main/resources/META-INF/spring/aot.factories new file mode 100644 index 0000000000..d8b3f2434e --- /dev/null +++ b/spring-aot/src/main/resources/META-INF/spring/aot.factories @@ -0,0 +1 @@ +org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor=io.kubernetes.client.spring.aot.KubernetesBeanFactoryInitializationAotProcessor \ No newline at end of file diff --git a/spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports new file mode 100644 index 0000000000..7e78a1ccfa --- /dev/null +++ b/spring/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports @@ -0,0 +1,3 @@ +io.kubernetes.client.spring.extended.controller.config.KubernetesInformerAutoConfiguration +io.kubernetes.client.spring.extended.controller.config.KubernetesReconcilerAutoConfiguration +io.kubernetes.client.spring.extended.manifests.config.KubernetesManifestsAutoConfiguration \ No newline at end of file