Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump kubernetes-client-bom from 6.6.2 to 6.7.1 #33767

Merged
merged 3 commits into from
Jun 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/src/main/asciidoc/kubernetes-client.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ In dev mode and when running tests, xref:kubernetes-dev-services.adoc[Dev Servic

Quarkus provides multiple integration points for influencing the Kubernetes Client provided as a CDI bean.

==== Kubernetes Client Config customization

The first integration point is the use of the `io.quarkus.kubernetes.client.KubernetesConfigCustomizer` interface. When such a bean exists,
it allows for arbitrary customizations of the `io.fabric8.kubernetes.client.Config` created by Quarkus (which takes into account the `quarkus.kubernetes-client.*` properties).

Expand All @@ -82,6 +84,47 @@ public class KubernetesClientProducer {
}
----

==== Kubernetes Client ObjectMapper customization

The Fabric8 Kubernetes Client uses its own `ObjectMapper` instance for serialization and deserialization of Kubernetes resources.
This mapper is provided to the client through a `KubernetesSerialization` instance that's injected into
the `KubernetesClient` bean.

If for some reason you must customize the default `ObjectMapper` bean provided by this extension and used by the Kubernetes Client, you can do so by declaring a bean that implements the `KubernetesClientObjectMapperCustomizer` interface.

The following code snippet contains an example of a `KubernetesClientObjectMapperCustomizer` to set the `ObjectMapper` locale:

[source,java]
----
@Singleton
public static class Customizer implements KubernetesClientObjectMapperCustomizer {
@Override
public void customize(ObjectMapper objectMapper) {
objectMapper.setLocale(Locale.ROOT);
}
}
----

Furthermore, if you must replace the default `ObjectMapper` bean used by the Kubernetes Client that the extension creates automatically, you can do so by declaring a bean of type `@KubernetesClientObjectMapper`.
The following code snippet shows how you can declare this bean:

[source,java]
----
@Singleton
public class KubernetesObjectMapperProducer {
@KubernetesClientObjectMapper
@Singleton
@Produces
public ObjectMapper kubernetesClientObjectMapper() {
return new ObjectMapper();
}
}
----


WARNING: The static `io.fabric8.kubernetes.client.utils.Serialization` utils class is deprecated and should not be used.
Access to `Serialization.jsonMapper()` should be replaced by the usage of @KubernetesClientObjectMapperCustomizer` declared beans.

== Testing

To make testing against a mock Kubernetes API extremely simple, Quarkus provides the `WithKubernetesTestServer` annotation which automatically launches
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@
import io.quarkus.deployment.util.JandexUtil;
import io.quarkus.jackson.deployment.IgnoreJsonDeserializeClassBuildItem;
import io.quarkus.kubernetes.client.runtime.KubernetesClientBuildConfig;
import io.quarkus.kubernetes.client.runtime.KubernetesClientObjectMapperProducer;
import io.quarkus.kubernetes.client.runtime.KubernetesClientProducer;
import io.quarkus.kubernetes.client.runtime.KubernetesConfigProducer;
import io.quarkus.kubernetes.client.runtime.KubernetesSerializationProducer;
import io.quarkus.kubernetes.client.spi.KubernetesClientCapabilityBuildItem;
import io.quarkus.maven.dependency.ArtifactKey;

Expand All @@ -77,6 +79,9 @@ public class KubernetesClientProcessor {
@BuildStep
public void registerBeanProducers(BuildProducer<AdditionalBeanBuildItem> additionalBeanBuildItemBuildItem,
Capabilities capabilities) {
additionalBeanBuildItemBuildItem
.produce(AdditionalBeanBuildItem.unremovableOf(KubernetesClientObjectMapperProducer.class));
additionalBeanBuildItemBuildItem.produce(AdditionalBeanBuildItem.unremovableOf(KubernetesSerializationProducer.class));
// wire up the Config bean support
additionalBeanBuildItemBuildItem.produce(AdditionalBeanBuildItem.unremovableOf(KubernetesConfigProducer.class));
// do not register our client producer if the openshift client is present, because it provides it too
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ServiceProviderBuildItem> serviceProviderProducer,
BuildProducer<KubernetesResourcesBuildItem> kubernetesResourcesBuildItemBuildProducer) {
serviceProviderProducer.produce(ServiceProviderBuildItem.allProvidersFromClassPath(KubernetesResource.class.getName()));
final Set<String> 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<AdditionalBeanBuildItem> 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();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.quarkus.kubernetes.client.deployment;

import static org.junit.jupiter.api.Assertions.assertEquals;

import jakarta.inject.Inject;
import jakarta.inject.Singleton;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.quarkus.kubernetes.client.KubernetesClientObjectMapper;
import io.quarkus.kubernetes.client.KubernetesClientObjectMapperCustomizer;
import io.quarkus.test.QuarkusUnitTest;

public class KubernetesClientObjectMapperCDITest {

@Inject
KubernetesClient kubernetesClient;

@Inject
@KubernetesClientObjectMapper
ObjectMapper objectMapper;

@Test
public void kubernetesClientObjectMapperCustomizer() throws JsonProcessingException {
final var result = objectMapper.readValue("{\"quarkusName\":\"the-name\"}", ObjectMeta.class);
assertEquals("the-name", result.getName());
}

@Test
public void kubernetesClientUsesCustomizedObjectMapper() {
final var result = kubernetesClient.getKubernetesSerialization()
.unmarshal("{\"quarkusName\":\"the-name\"}", ObjectMeta.class);
assertEquals("the-name", result.getName());
}

@RegisterExtension
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(KubernetesClientCDITest.Customizer.class))
.overrideConfigKey("quarkus.kubernetes-client.devservices.enabled", "false");

@Singleton
public static class Customizer implements KubernetesClientObjectMapperCustomizer {
@Override
public void customize(ObjectMapper objectMapper) {
objectMapper.addMixIn(ObjectMeta.class, ObjectMetaMixin.class);
}

private static final class ObjectMetaMixin {
@JsonProperty("quarkusName")
String name;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public class KubernetesClientUtils {

private static final String PREFIX = "quarkus.kubernetes-client.";

private KubernetesClientUtils() {
}

public static Config createConfig(KubernetesClientBuildConfig buildConfig, TlsConfig tlsConfig) {
Config base = Config.autoConfigure(null);
boolean trustAll = buildConfig.trustCerts.isPresent() ? buildConfig.trustCerts.get() : tlsConfig.trustAll;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Class<? extends KubernetesResource>[]> initKubernetesResources(String[] resourceClassNames) {
final Set<Class<?>> 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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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;

/**
* {@link Qualifier} to inject the Fabric8 Kubernetes Client specific {@link com.fasterxml.jackson.databind.ObjectMapper}.
* <p>
* Allows users to modify the behavior of the mapper for very specific use cases (such as adding Kotlin-specific modules).
* Otherwise, it's not recommended to modify the mapper since it might break the Kubernetes Client.
*/
@Qualifier
@Retention(RUNTIME)
@Target({ METHOD, FIELD, PARAMETER, TYPE })
public @interface KubernetesClientObjectMapper {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.kubernetes.client;

import java.util.List;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import io.quarkus.kubernetes.client.runtime.KubernetesClientObjectMapperProducer;
import io.quarkus.kubernetes.client.runtime.KubernetesClientProducer;
import io.quarkus.kubernetes.client.runtime.KubernetesSerializationProducer;

/**
* Allow the provision of beans that customize the default {@link ObjectMapper} used by the KubernetesClient to
* perform kubernetes-specific serialization and deserialization operations.
* <p>
* The resulting {@link ObjectMapper} is used to produce the default {@link KubernetesSerialization} bean, which is in turn
* used to produce the default {@link io.fabric8.kubernetes.client.KubernetesClient} bean.
* <p>
* The following code snippet shows how to provide a KubernetesClientObjectMapperCustomizer:
*
* <pre>{@code
*
* &#64;Singleton
* public static class Customizer implements KubernetesClientObjectMapperCustomizer {
* @Override
* public void customize(ObjectMapper objectMapper) {
* objectMapper.setLocale(Locale.ROOT);
* }
* }
*
* }</pre>
*
* @see KubernetesClientObjectMapperProducer#kubernetesClientObjectMapper(List)
* @see KubernetesSerializationProducer#kubernetesSerialization(ObjectMapper, Class[])
* @see KubernetesClientProducer#kubernetesClient(KubernetesSerialization, Config)
*/
public interface KubernetesClientObjectMapperCustomizer {
void customize(ObjectMapper objectMapper);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import io.quarkus.kubernetes.client.runtime.KubernetesClientBuildConfig;
import io.quarkus.kubernetes.client.runtime.KubernetesClientProducer;
import io.quarkus.kubernetes.client.runtime.KubernetesConfigProducer;
Expand All @@ -16,8 +17,8 @@
* The {@link Config} is in turn used to produce the default {@link KubernetesClient}
* <p>
*
* @see KubernetesConfigProducer#config(KubernetesClientBuildConfig, TlsConfig, List) }
* @see KubernetesClientProducer#kubernetesClient(Config) }
* @see KubernetesConfigProducer#config(KubernetesClientBuildConfig, TlsConfig, List)
* @see KubernetesClientProducer#kubernetesClient(KubernetesSerialization, Config)
*/
public interface KubernetesConfigCustomizer {

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.quarkus.kubernetes.client.runtime;

import java.util.List;

import jakarta.annotation.Priority;
import jakarta.enterprise.inject.Produces;
import jakarta.inject.Singleton;

import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.arc.All;
import io.quarkus.arc.DefaultBean;
import io.quarkus.kubernetes.client.KubernetesClientObjectMapper;
import io.quarkus.kubernetes.client.KubernetesClientObjectMapperCustomizer;

@Singleton
public class KubernetesClientObjectMapperProducer {

@KubernetesClientObjectMapper
@DefaultBean
@Priority(Integer.MIN_VALUE)
@Singleton
@Produces
public ObjectMapper kubernetesClientObjectMapper(@All List<KubernetesClientObjectMapperCustomizer> customizers) {
final var result = new ObjectMapper();
for (var customizer : customizers) {
customizer.customize(result);
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
import io.quarkus.arc.DefaultBean;

@Singleton
Expand All @@ -17,8 +18,9 @@ public class KubernetesClientProducer {
@DefaultBean
@Singleton
@Produces
public KubernetesClient kubernetesClient(Config config) {
client = new KubernetesClientBuilder().withConfig(config).build();
public KubernetesClient kubernetesClient(KubernetesSerialization kubernetesSerialization, Config config) {
client = new KubernetesClientBuilder()
.withKubernetesSerialization(kubernetesSerialization).withConfig(config).build();
return client;
}

Expand Down
Loading