diff --git a/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncConversionController.java b/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncConversionController.java index 3d3e9922..a08ccd42 100644 --- a/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncConversionController.java +++ b/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncConversionController.java @@ -29,8 +29,6 @@ public void registerMapper(AsyncMapper mapper) { throw new IllegalStateException(MAPPER_ALREADY_REGISTERED_FOR_VERSION_MESSAGE + version); } mappers.put(version, mapper); - Utils.registerCustomKind( - Utils.getFirstTypeArgumentFromInterface(mapper.getClass(), AsyncMapper.class)); } @Override diff --git a/core/src/main/java/io/javaoperatorsdk/webhook/conversion/ConversionController.java b/core/src/main/java/io/javaoperatorsdk/webhook/conversion/ConversionController.java index 8ed9996a..122a672c 100644 --- a/core/src/main/java/io/javaoperatorsdk/webhook/conversion/ConversionController.java +++ b/core/src/main/java/io/javaoperatorsdk/webhook/conversion/ConversionController.java @@ -26,8 +26,6 @@ public void registerMapper(Mapper mapper) { throw new IllegalStateException(MAPPER_ALREADY_REGISTERED_FOR_VERSION_MESSAGE + version); } mappers.put(version, mapper); - Utils.registerCustomKind( - Utils.getFirstTypeArgumentFromInterface(mapper.getClass(), Mapper.class)); } @Override diff --git a/core/src/main/java/io/javaoperatorsdk/webhook/conversion/Utils.java b/core/src/main/java/io/javaoperatorsdk/webhook/conversion/Utils.java index a423ddea..aeb645c4 100644 --- a/core/src/main/java/io/javaoperatorsdk/webhook/conversion/Utils.java +++ b/core/src/main/java/io/javaoperatorsdk/webhook/conversion/Utils.java @@ -1,11 +1,5 @@ package io.javaoperatorsdk.webhook.conversion; -import java.lang.reflect.ParameterizedType; -import java.util.Arrays; - -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.internal.KubernetesDeserializer; - public class Utils { private Utils() {} @@ -19,24 +13,4 @@ public static String versionOfApiVersion(String apiVersion) { return apiVersion.substring(lastDelimiter + 1); } - public static void registerCustomKind(Class clazz) { - KubernetesDeserializer.registerCustomKind(HasMetadata.getApiVersion(clazz), - HasMetadata.getKind(clazz), clazz); - } - - @SuppressWarnings("unchecked") - public static Class getFirstTypeArgumentFromInterface(Class clazz, - Class expectedImplementedInterface) { - return (Class) Arrays.stream(clazz.getGenericInterfaces()) - .filter(type -> type.getTypeName().startsWith(expectedImplementedInterface.getName()) - && type instanceof ParameterizedType) - .map(ParameterizedType.class::cast) - .findFirst() - .map(t -> (Class) t.getActualTypeArguments()[0]) - .orElseThrow(() -> new RuntimeException( - "Couldn't retrieve generic parameter type from " + clazz.getSimpleName() - + " because it doesn't implement " - + expectedImplementedInterface.getSimpleName() - + " directly")); - } } diff --git a/core/src/test/java/io/javaoperatorsdk/webhook/conversion/UtilsTest.java b/core/src/test/java/io/javaoperatorsdk/webhook/conversion/UtilsTest.java index bed5264a..7997721f 100644 --- a/core/src/test/java/io/javaoperatorsdk/webhook/conversion/UtilsTest.java +++ b/core/src/test/java/io/javaoperatorsdk/webhook/conversion/UtilsTest.java @@ -2,13 +2,6 @@ import org.junit.jupiter.api.Test; -import io.javaoperatorsdk.webhook.conversion.crd.CustomResourceV1; -import io.javaoperatorsdk.webhook.conversion.crd.CustomResourceV2; -import io.javaoperatorsdk.webhook.conversion.mapper.AsyncV1Mapper; -import io.javaoperatorsdk.webhook.conversion.mapper.AsyncV2Mapper; -import io.javaoperatorsdk.webhook.conversion.mapper.CustomResourceV1Mapper; -import io.javaoperatorsdk.webhook.conversion.mapper.CustomResourceV2Mapper; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; @@ -19,17 +12,4 @@ void getsVersionFromApiVersion() { assertThat(Utils.versionOfApiVersion("apiextensions.k8s.io/v1")).isEqualTo("v1"); assertThat(Utils.versionOfApiVersion("extensions/v1beta1")).isEqualTo("v1beta1"); } - - @Test - void getMapperResourceType() { - assertThat(Utils.getFirstTypeArgumentFromInterface(CustomResourceV1Mapper.class, Mapper.class)) - .isEqualTo(CustomResourceV1.class); - assertThat(Utils.getFirstTypeArgumentFromInterface(CustomResourceV2Mapper.class, Mapper.class)) - .isEqualTo(CustomResourceV2.class); - assertThat(Utils.getFirstTypeArgumentFromInterface(AsyncV1Mapper.class, AsyncMapper.class)) - .isEqualTo(CustomResourceV1.class); - assertThat(Utils.getFirstTypeArgumentFromInterface(AsyncV2Mapper.class, AsyncMapper.class)) - .isEqualTo(CustomResourceV2.class); - } - } diff --git a/docs/README.md b/docs/README.md index b45950de..d9be7a04 100644 --- a/docs/README.md +++ b/docs/README.md @@ -33,12 +33,16 @@ The goal of the end-to-end tests is to test the framework in a production-like e executable documentation to guide developers how to deploy and configure the target service. The [end-to-end tests](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/sample/QuarkusWebhooksE2E.java) -are using the [same test cases](https://github.com/java-operator-sdk/admission-controller-framework/blob/de2b0da7f592aa166049ef7ad65bcebf8d45e358/samples/commons/src/test/java/io/javaoperatorsdk/webhook/sample/EndToEndTestBase.java) and are based on the samples (See Spring Boot +are using +the [same test cases](https://github.com/java-operator-sdk/admission-controller-framework/blob/de2b0da7f592aa166049ef7ad65bcebf8d45e358/samples/commons/src/test/java/io/javaoperatorsdk/webhook/sample/EndToEndTestBase.java) +and are based on the samples (See Spring Boot version [here](https://github.com/java-operator-sdk/admission-controller-framework/blob/e2637a90152bebfca2983ba17268c1f7ec7e9602/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/SpringBootWebhooksE2E.java)). To see how those tests are executed during a pull request check the [related GitHub Action](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/.github/workflows/pr.yml#L66-L66) -The samples are first built, then [deployed](https://github.com/java-operator-sdk/admission-controller-framework/blob/6959de06c0de1c8e04fc241ea5f4196219002e53/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/sample/QuarkusWebhooksE2E.java#L23-L30) to a local Kubernetes cluster (in our case minikube is used). +The samples are first built, +then [deployed](https://github.com/java-operator-sdk/admission-controller-framework/blob/6959de06c0de1c8e04fc241ea5f4196219002e53/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/sample/QuarkusWebhooksE2E.java#L23-L30) +to a local Kubernetes cluster (in our case minikube is used). For Quarkus most of the deployment artifacts is generated using extensions (works similarly for Spring Boot, using [dekorate](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/samples/spring-boot/pom.xml#L52-L63)): @@ -91,7 +95,8 @@ The conversion hook is configured within the `CustomResourceDefinition`, see related [Kubernetes docs](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#configure-customresourcedefinition-to-use-conversion-webhooks). Since this is [not yet supported](https://github.com/fabric8io/kubernetes-client/issues/4692) by the fabric8 client CRD generator, the hook definition is -[added before](https://github.com/java-operator-sdk/admission-controller-framework/blob/57a889ea1c0cb42b5a703a3cc8053f51c3982f74/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/Utils.java#L83-L110) CRD is applied. +[added before](https://github.com/java-operator-sdk/admission-controller-framework/blob/57a889ea1c0cb42b5a703a3cc8053f51c3982f74/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/Utils.java#L83-L110) +CRD is applied. Note that [cert manager](https://github.com/java-operator-sdk/admission-controller-framework/blob/e2637a90152bebfca2983ba17268c1f7ec7e9602/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/sample/QuarkusWebhooksE2E.java#L19-L23) @@ -135,7 +140,8 @@ All changes made to the resource are reflected in the response created by the ad respectively [AsyncConversionController](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncConversionController.java)) handles conversion between different versions of custom resources using [mappers](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/conversion/Mapper.java) -( respectively [async mappers](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncMapper.java)). +( +respectively [async mappers](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/core/src/main/java/io/javaoperatorsdk/webhook/conversion/AsyncMapper.java)). The mapper interface is simple: @@ -149,8 +155,26 @@ public interface Mapper { } ``` -It handles mapping to and from a Hub. Hub is an intermediate representation in a conversion. Thus, the conversion +It handles mapping to and from a Hub. Hub is an intermediate representation in a conversion. Thus, the conversion steps from v1 to v2 happen in the following way: v1 -> HUB -> v2. Using the provided v1 and v2 mappers implementations. -Having this approach is useful mainly in case there are more than two versions of resources on the cluster, so there is +Having this approach is useful mainly in case there are more than two versions of resources on the cluster, so there is no need for a mapper for every combination. See also related docs in [Kubebuilder](https://book.kubebuilder.io/multiversion-tutorial/conversion-concepts.html). + +## Using Custom Resources in the API + +In order to properly register your own custom types (custom resources) for deserialization it needs to be added to +`META-INF/services/io.fabric8.kubernetes.api.model.KubernetesResource` file. + +See in the [samples](https://github.com/java-operator-sdk/admission-controller-framework/blob/main/samples/commons/src/main/resources/META-INF/services/io.fabric8.kubernetes.api.model.KubernetesResource#L164-L164). + +Related release not in fabric8 client: +```text +Fix #4579: the implicit registration of resource and list types that happens when using the resource(class) methods +has been removed. This makes the behavior of the client more predictable as that was an undocumented side-effect. +If you expect to see instances of a custom type from an untyped api call - typically KubernetesClient.load, +KubernetesClient.resourceList, KubernetesClient.resource(InputStream|String), then you must either create a +META-INF/services/io.fabric8.kubernetes.api.model.KubernetesResource file (see above #3923), or make calls to +KubernetesDeserializer.registerCustomKind - however since KubernetesDeserializer is an internal class that mechanism +is not preferred. +``` \ No newline at end of file diff --git a/samples/commons/src/main/resources/META-INF/services/io.fabric8.kubernetes.api.model.KubernetesResource b/samples/commons/src/main/resources/META-INF/services/io.fabric8.kubernetes.api.model.KubernetesResource new file mode 100644 index 00000000..dc6c1a7b --- /dev/null +++ b/samples/commons/src/main/resources/META-INF/services/io.fabric8.kubernetes.api.model.KubernetesResource @@ -0,0 +1,2 @@ +io.javaoperatorsdk.webhook.sample.commons.customresource.MultiVersionCustomResource +io.javaoperatorsdk.webhook.sample.commons.customresource.MultiVersionCustomResourceV2 \ No newline at end of file diff --git a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/sample/conversion/CustomResourceDeserializationCustomizer.java b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/sample/conversion/CustomResourceDeserializationCustomizer.java deleted file mode 100644 index 602e58f2..00000000 --- a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/sample/conversion/CustomResourceDeserializationCustomizer.java +++ /dev/null @@ -1,23 +0,0 @@ -package io.javaoperatorsdk.webhook.sample.conversion; - -import javax.inject.Singleton; - -import io.javaoperatorsdk.webhook.conversion.Utils; -import io.javaoperatorsdk.webhook.sample.commons.customresource.MultiVersionCustomResource; -import io.javaoperatorsdk.webhook.sample.commons.customresource.MultiVersionCustomResourceV2; -import io.quarkus.jackson.ObjectMapperCustomizer; - -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * For quarkus for now the custom kinds needs to be registered explicitly - */ -@Singleton -public class CustomResourceDeserializationCustomizer implements ObjectMapperCustomizer { - - @Override - public void customize(ObjectMapper objectMapper) { - Utils.registerCustomKind(MultiVersionCustomResource.class); - Utils.registerCustomKind(MultiVersionCustomResourceV2.class); - } -}