diff --git a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc index 7cf863c7b8449..54b1535575b8a 100644 --- a/docs/src/main/asciidoc/deploying-to-kubernetes.adoc +++ b/docs/src/main/asciidoc/deploying-to-kubernetes.adoc @@ -1359,6 +1359,7 @@ To enable Service Binding for supported extensions, add the `quarkus-kubernetes- * `quarkus-reactive-mysql-client` * `quarkus-reactive-oracle-client` * `quarkus-reactive-pg-client` +* `quarkus-infinispan-client` ==== diff --git a/extensions/infinispan-client/deployment/pom.xml b/extensions/infinispan-client/deployment/pom.xml index a294e6da65c47..fba0493740e4b 100644 --- a/extensions/infinispan-client/deployment/pom.xml +++ b/extensions/infinispan-client/deployment/pom.xml @@ -56,6 +56,11 @@ quarkus-smallrye-health-spi + + io.quarkus + quarkus-kubernetes-service-binding-spi + + org.infinispan infinispan-server-testdriver-core diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java index e296c85041099..c7303ec6ace33 100644 --- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java +++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java @@ -39,6 +39,8 @@ import io.quarkus.arc.deployment.BeanContainerListenerBuildItem; import io.quarkus.arc.deployment.UnremovableBeanBuildItem; import io.quarkus.deployment.ApplicationArchive; +import io.quarkus.deployment.Capabilities; +import io.quarkus.deployment.Capability; import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; @@ -54,10 +56,12 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageConfigBuildItem; import io.quarkus.deployment.builditem.nativeimage.NativeImageSecurityProviderBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; import io.quarkus.deployment.pkg.steps.NativeOrNativeSourcesBuild; import io.quarkus.infinispan.client.runtime.InfinispanClientBuildTimeConfig; import io.quarkus.infinispan.client.runtime.InfinispanClientProducer; import io.quarkus.infinispan.client.runtime.InfinispanRecorder; +import io.quarkus.infinispan.client.runtime.InfinispanServiceBindingConverter; import io.quarkus.infinispan.client.runtime.cache.CacheInvalidateAllInterceptor; import io.quarkus.infinispan.client.runtime.cache.CacheInvalidateInterceptor; import io.quarkus.infinispan.client.runtime.cache.CacheResultInterceptor; @@ -68,6 +72,7 @@ class InfinispanClientProcessor { private static final Log log = LogFactory.getLog(InfinispanClientProcessor.class); + private static final String SERVICE_BINDING_INTERFACE_NAME = "io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter"; private static final String META_INF = "META-INF"; private static final String HOTROD_CLIENT_PROPERTIES = "hotrod-client.properties"; private static final String PROTO_EXTENSION = ".proto"; @@ -260,4 +265,13 @@ HealthBuildItem addHealthCheck(InfinispanClientBuildTimeConfig buildTimeConfig) buildTimeConfig.healthEnabled); } + @BuildStep + void registerServiceBinding(Capabilities capabilities, BuildProducer buildProducer) { + if (capabilities.isPresent(Capability.KUBERNETES_SERVICE_BINDING)) { + buildProducer.produce( + new ServiceProviderBuildItem(SERVICE_BINDING_INTERFACE_NAME, + InfinispanServiceBindingConverter.class.getName())); + } + } + } diff --git a/extensions/infinispan-client/runtime/pom.xml b/extensions/infinispan-client/runtime/pom.xml index f1d52a34bc7a2..d938542910d32 100644 --- a/extensions/infinispan-client/runtime/pom.xml +++ b/extensions/infinispan-client/runtime/pom.xml @@ -132,6 +132,21 @@ quarkus-smallrye-health true + + io.quarkus + quarkus-kubernetes-service-binding + true + + + io.quarkus + quarkus-junit5-internal + test + + + org.assertj + assertj-core + test + diff --git a/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanServiceBindingConverter.java b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanServiceBindingConverter.java new file mode 100644 index 0000000000000..c5f1d213be727 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/java/io/quarkus/infinispan/client/runtime/InfinispanServiceBindingConverter.java @@ -0,0 +1,112 @@ +package io.quarkus.infinispan.client.runtime; + +import static java.lang.String.format; +import static org.apache.sshd.common.util.GenericUtils.isBlank; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.jboss.logging.Logger; + +import io.quarkus.kubernetes.service.binding.runtime.ServiceBinding; +import io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConfigSource; +import io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter; + +/** + * ServiceBindingConverter for Infinispan to support SBO (Service Binding Operator) in Quarkus. + * + *
+ *
+ * Following individual properties are supported to make the connection: + * + * if uri is provided, all the other properties are ignored + * if uri is not provided, hosts is mandatory and all other properties are optional + * + *
+ *
+ * The Quarkus properties set by this class are: + * + */ +public class InfinispanServiceBindingConverter implements ServiceBindingConverter { + + private static final Logger LOGGER = Logger.getLogger(InfinispanServiceBindingConverter.class); + private static final String BINDING_TYPE = "infinispan"; + public static final String BINDING_CONFIG_SOURCE_NAME = BINDING_TYPE + "-k8s-service-binding-source"; + + // Connection properties + public static final String INFINISPAN_URI = "uri"; + public static final String INFINISPAN_HOSTS = "hosts"; + public static final String INFINISPAN_USE_AUTH = "useauth"; + public static final String INFINISPAN_USERNAME = "username"; + public static final String INFINISPAN_PASSWORD = "password"; + + // Infinispan Quarkus properties + public static final String INFINISPAN_CLIENT_URI = "quarkus.infinispan-client.uri"; + public static final String INFINISPAN_CLIENT_HOSTS = "quarkus.infinispan-client.hosts"; + public static final String INFINISPAN_CLIENT_USE_AUTH = "quarkus.infinispan-client.use-auth"; + public static final String INFINISPAN_CLIENT_AUTH_USERNAME = "quarkus.infinispan-client.username"; + public static final String INFINISPAN_CLIENT_AUTH_PASSWORD = "quarkus.infinispan-client.password"; + + @Override + public Optional convert(List serviceBindings) { + Optional matchingByType = ServiceBinding.singleMatchingByType(BINDING_TYPE, serviceBindings); + if (matchingByType.isEmpty()) { + return Optional.empty(); + } + + ServiceBinding binding = matchingByType.get(); + Map properties = new HashMap<>(); + + setUri(binding, properties); + setConnection(binding, properties); + setUseAuth(binding, properties); + setUsername(binding, properties); + setPassword(binding, properties); + + return Optional.of(new ServiceBindingConfigSource(BINDING_CONFIG_SOURCE_NAME, properties)); + } + + private void setUri(ServiceBinding binding, Map properties) { + properties.put(INFINISPAN_CLIENT_URI, getInfinispanProperty(binding, INFINISPAN_URI)); + } + + private void setConnection(ServiceBinding binding, Map properties) { + properties.put(INFINISPAN_CLIENT_HOSTS, getInfinispanProperty(binding, INFINISPAN_HOSTS)); + } + + private void setUseAuth(ServiceBinding binding, Map properties) { + properties.put(INFINISPAN_CLIENT_USE_AUTH, getInfinispanProperty(binding, INFINISPAN_USE_AUTH)); + } + + private void setUsername(ServiceBinding binding, Map properties) { + properties.put(INFINISPAN_CLIENT_AUTH_USERNAME, getInfinispanProperty(binding, INFINISPAN_USERNAME)); + } + + private void setPassword(ServiceBinding binding, Map properties) { + properties.put(INFINISPAN_CLIENT_AUTH_PASSWORD, getInfinispanProperty(binding, INFINISPAN_PASSWORD)); + } + + private String getInfinispanProperty(ServiceBinding binding, String infinispanPropertyKey) { + String infinispanPropertyValue = binding.getProperties().get(infinispanPropertyKey); + if (isBlank(infinispanPropertyValue)) { + LOGGER.debug(format("Property '%s' not found", infinispanPropertyKey)); + } + + return infinispanPropertyValue; + } + +} diff --git a/extensions/infinispan-client/runtime/src/main/resources/META-INF/services/io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter b/extensions/infinispan-client/runtime/src/main/resources/META-INF/services/io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter new file mode 100644 index 0000000000000..1eb49bc4aca88 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/main/resources/META-INF/services/io.quarkus.kubernetes.service.binding.runtime.ServiceBindingConverter @@ -0,0 +1 @@ +io.quarkus.infinispan.client.runtime.InfinispanServiceBindingConverter diff --git a/extensions/infinispan-client/runtime/src/test/java/io/quarkus/infinispan/client/runtime/InfinispanServiceBindingConverterTest.java b/extensions/infinispan-client/runtime/src/test/java/io/quarkus/infinispan/client/runtime/InfinispanServiceBindingConverterTest.java new file mode 100644 index 0000000000000..f97dc27e22f7b --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/java/io/quarkus/infinispan/client/runtime/InfinispanServiceBindingConverterTest.java @@ -0,0 +1,53 @@ +package io.quarkus.infinispan.client.runtime; + +import static io.quarkus.infinispan.client.runtime.InfinispanServiceBindingConverter.INFINISPAN_HOSTS; +import static io.quarkus.infinispan.client.runtime.InfinispanServiceBindingConverter.INFINISPAN_PASSWORD; +import static io.quarkus.infinispan.client.runtime.InfinispanServiceBindingConverter.INFINISPAN_URI; +import static io.quarkus.infinispan.client.runtime.InfinispanServiceBindingConverter.INFINISPAN_USERNAME; +import static io.quarkus.infinispan.client.runtime.InfinispanServiceBindingConverter.INFINISPAN_USE_AUTH; +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.jupiter.api.Test; + +import io.quarkus.kubernetes.service.binding.runtime.ServiceBinding; + +public class InfinispanServiceBindingConverterTest { + private final static Path rootPath = Paths.get("src/test/resources/service-binding"); + private final static String BINDING_DIRECTORY_ALL_PROPS = "all-props"; + private final static String BINDING_DIRECTORY_NO_PROPS = "no-props"; + private static final String EXPECTED_USERNAME = "someadmin"; + private static final String EXPECTED_PASSWORD = "infiniforever"; + private static final String EXPECTED_HOSTS = "infinispan.forever.com:11222"; + private static final String EXPECTED_URI = "hotrod://admin:password@infinispan.forever.com:11222"; + + @Test + public void testBindingWithAllProperties() { + String bindingDirectory = BINDING_DIRECTORY_ALL_PROPS; + ServiceBinding serviceBinding = new ServiceBinding(rootPath.resolve(bindingDirectory)); + assertThat(serviceBinding.getName()).isEqualTo(bindingDirectory); + assertThat(serviceBinding.getType()).isEqualTo("infinispan"); + assertThat(serviceBinding.getProvider()).isEqualTo("coco"); + assertThat(serviceBinding.getProperties().get(INFINISPAN_USE_AUTH)).isEqualTo("true"); + assertThat(serviceBinding.getProperties().get(INFINISPAN_USERNAME)).isEqualTo(EXPECTED_USERNAME); + assertThat(serviceBinding.getProperties().get(INFINISPAN_PASSWORD)).isEqualTo(EXPECTED_PASSWORD); + assertThat(serviceBinding.getProperties().get(INFINISPAN_HOSTS)).isEqualTo(EXPECTED_HOSTS); + assertThat(serviceBinding.getProperties().get(INFINISPAN_URI)).isEqualTo(EXPECTED_URI); + } + + @Test + public void testBindingWithMissingProperties() { + String bindingDirectory = BINDING_DIRECTORY_NO_PROPS; + ServiceBinding serviceBinding = new ServiceBinding(rootPath.resolve(bindingDirectory)); + assertThat(serviceBinding.getName()).isEqualTo(bindingDirectory); + assertThat(serviceBinding.getType()).isEqualTo("infinispan"); + assertThat(serviceBinding.getProvider()).isNull(); + assertThat(serviceBinding.getProperties().containsKey(INFINISPAN_USE_AUTH)).isFalse(); + assertThat(serviceBinding.getProperties().containsKey(INFINISPAN_USERNAME)).isFalse(); + assertThat(serviceBinding.getProperties().containsKey(INFINISPAN_PASSWORD)).isFalse(); + assertThat(serviceBinding.getProperties().containsKey(INFINISPAN_HOSTS)).isFalse(); + assertThat(serviceBinding.getProperties().containsKey(INFINISPAN_URI)).isFalse(); + } +} diff --git a/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/hosts b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/hosts new file mode 100644 index 0000000000000..5c311ffe8b92f --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/hosts @@ -0,0 +1 @@ +infinispan.forever.com:11222 \ No newline at end of file diff --git a/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/password b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/password new file mode 100644 index 0000000000000..8f2c4674476ac --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/password @@ -0,0 +1 @@ +infiniforever \ No newline at end of file diff --git a/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/provider b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/provider new file mode 100644 index 0000000000000..164b286104ce6 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/provider @@ -0,0 +1 @@ +coco \ No newline at end of file diff --git a/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/type b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/type new file mode 100644 index 0000000000000..45f7580c77695 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/type @@ -0,0 +1 @@ +infinispan \ No newline at end of file diff --git a/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/uri b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/uri new file mode 100644 index 0000000000000..a0734c96ece0c --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/uri @@ -0,0 +1 @@ +hotrod://admin:password@infinispan.forever.com:11222 \ No newline at end of file diff --git a/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/useauth b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/useauth new file mode 100644 index 0000000000000..f32a5804e292d --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/useauth @@ -0,0 +1 @@ +true \ No newline at end of file diff --git a/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/username b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/username new file mode 100644 index 0000000000000..5eabebd8bc0fc --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/resources/service-binding/all-props/username @@ -0,0 +1 @@ +someadmin \ No newline at end of file diff --git a/extensions/infinispan-client/runtime/src/test/resources/service-binding/no-props/type b/extensions/infinispan-client/runtime/src/test/resources/service-binding/no-props/type new file mode 100644 index 0000000000000..45f7580c77695 --- /dev/null +++ b/extensions/infinispan-client/runtime/src/test/resources/service-binding/no-props/type @@ -0,0 +1 @@ +infinispan \ No newline at end of file