From 62c62c5151d069ed22faa5f6e21b316f27d38f1b Mon Sep 17 00:00:00 2001 From: csviri Date: Wed, 29 Mar 2023 14:38:42 +0200 Subject: [PATCH 1/4] feat: handling custom resources with SPI from fabric8 client --- .../conversion/AsyncConversionController.java | 2 -- .../conversion/ConversionController.java | 2 -- .../webhook/conversion/Utils.java | 26 ------------------- .../webhook/conversion/UtilsTest.java | 20 -------------- ...c8.kubernetes.api.model.KubernetesResource | 2 ++ ...stomResourceDeserializationCustomizer.java | 23 ---------------- 6 files changed, 2 insertions(+), 73 deletions(-) create mode 100644 samples/commons/src/main/resources/META-INF/services/io.fabric8.kubernetes.api.model.KubernetesResource delete mode 100644 samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/sample/conversion/CustomResourceDeserializationCustomizer.java 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/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); - } -} From eeee40d4e296f431fa97cfff0663a9be56b622cf Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 30 Mar 2023 08:36:02 +0200 Subject: [PATCH 2/4] docs --- docs/README.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/docs/README.md b/docs/README.md index b45950de..42f5ca3e 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,15 @@ 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). \ No newline at end of file From 2699d39350bc4978fb539f31b358da990f8ca8d4 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 30 Mar 2023 08:38:43 +0200 Subject: [PATCH 3/4] docs --- docs/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 42f5ca3e..9d32c207 100644 --- a/docs/README.md +++ b/docs/README.md @@ -166,4 +166,9 @@ no need for a mapper for every combination. See also related docs in 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). \ No newline at end of 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 From 3964bfbf25da0f93c5dda7bd54085c553cb0dc79 Mon Sep 17 00:00:00 2001 From: csviri Date: Thu, 30 Mar 2023 08:39:05 +0200 Subject: [PATCH 4/4] docs --- docs/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 9d32c207..d9be7a04 100644 --- a/docs/README.md +++ b/docs/README.md @@ -170,5 +170,11 @@ See in the [samples](https://github.com/java-operator-sdk/admission-controller-f 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. +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