diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 3d8d487e0e628..dbebd9d7917e4 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -86,8 +86,8 @@ 3.12.0 1.15 1.5.1 - 5.6.7.Final - 1.12.8 + 5.6.8.Final + 1.12.9 1.1.4.Final 6.2.3.Final 6.1.3.Final @@ -153,7 +153,7 @@ 1.6.10 1.6.0 1.3.2 - 2.9.0 + 2.9.2 3.1.0 4.2.0 1.0.9 @@ -195,8 +195,8 @@ 2.2.1.Final 0.1.7.Final 0.8.7 - 1.16.3 - 3.2.12 + 1.17.1 + 3.2.13 2.2 2.6 diff --git a/devtools/cli/distribution/jreleaser.yml b/devtools/cli/distribution/jreleaser.yml index 31f15b55c7b60..40a4b01e8a033 100644 --- a/devtools/cli/distribution/jreleaser.yml +++ b/devtools/cli/distribution/jreleaser.yml @@ -41,7 +41,6 @@ distributions: executable: name: quarkus windowsExtension: bat - executableExtension: bat tags: - quarkus - cli diff --git a/devtools/cli/distribution/release-cli.sh b/devtools/cli/distribution/release-cli.sh index 80410588077f8..786b207f485b2 100755 --- a/devtools/cli/distribution/release-cli.sh +++ b/devtools/cli/distribution/release-cli.sh @@ -45,7 +45,7 @@ export JRELEASER_PROJECT_VERSION=${VERSION} export JRELEASER_BRANCH=${BRANCH} export JRELEASER_CHOCOLATEY_GITHUB_BRANCH=${BRANCH} -jbang org.jreleaser:jreleaser:1.0.0-M3 full-release \ +jbang org.jreleaser:jreleaser:1.0.0 full-release \ --git-root-search \ -od target diff --git a/docs/src/main/asciidoc/building-my-first-extension.adoc b/docs/src/main/asciidoc/building-my-first-extension.adoc index 761cbf2147bc9..7ae69185458bd 100644 --- a/docs/src/main/asciidoc/building-my-first-extension.adoc +++ b/docs/src/main/asciidoc/building-my-first-extension.adoc @@ -849,7 +849,7 @@ mvn io.quarkus.platform:quarkus-maven-plugin:{quarkus-version}:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=greeting-app \ -Dextensions="org.acme:greeting-extension:1.0.0-SNAPSHOT" \ - -DnoExamples + -DnoCode ---- `cd` into `greeting-app`. diff --git a/docs/src/main/asciidoc/kafka.adoc b/docs/src/main/asciidoc/kafka.adoc index d6137301e99a5..37b84824e3627 100644 --- a/docs/src/main/asciidoc/kafka.adoc +++ b/docs/src/main/asciidoc/kafka.adoc @@ -2401,6 +2401,103 @@ quarkus.log.category."org.apache.kafka.common.utils".level=INFO quarkus.log.category."org.apache.kafka.common.metrics".level=INFO ---- +== Connecting to Managed Kafka clusters + +This section explains how to connect to notorious Kafka Cloud Services. + +=== Azure Event Hub + +https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-for-kafka-ecosystem-overview[Azure Event Hub] provides an endpoint compatible with Apache Kafka. + +NOTE: Azure Event Hubs for Kafka is not available in the _basic_ tier. +You need at least the _standard_ tier to use Kafka. +See https://azure.microsoft.com/en-us/pricing/details/event-hubs/[Azure Event Hubs Pricing] to see the other options. + +To connect to Azure Event Hub, using the Kafka protocol with TLS, you need the following configuration: + +[source, properties] +---- +kafka.bootstrap.servers=my-event-hub.servicebus.windows.net:9093 # <1> +kafka.security.protocol=SASL_SSL +kafka.sasl.mechanism=PLAIN +kafka.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \ # <2> + username="$ConnectionString" \ # <3> + password=""; # <4> +---- +<1> The port is `9093`. +<2> You need to use the JAAS `PlainLoginModule`. +<3> The username is the `$ConnectionString` string. +<4> The Event Hub connection string given by Azure. + +Replace `` with the connection string for your Event Hubs namespace. +For instructions on getting the connection string, see https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-get-connection-string[Get an Event Hubs connection string]. +The result would be something like: + +[source, properties] +---- +kafka.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \ + username="$ConnectionString" \ + password="Endpoint=sb://my-event-hub.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=XXXXXXXXXXXXXXXX"; +---- + +This configuration can be global (as above), or set in the channel configuration: + +[source, properties] +---- +mp.messaging.incoming.$channel.bootstrap.servers=my-event-hub.servicebus.windows.net:9093 +mp.messaging.incoming.$channel.security.protocol=SASL_SSL +mp.messaging.incoming.$channel.sasl.mechanism=PLAIN +mp.messaging.incoming.$channel.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \ + username="$ConnectionString" \ + password="Endpoint=sb://my-event-hub.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=..."; +---- + +=== Red Hat OpenShift Streams for Apache Kafka + +https://cloud.redhat.com/[Red Hat OpenShift Streams for Apache Kafka] provides managed Kafka brokers. +First, follow the instructions from https://access.redhat.com/documentation/en-us/red_hat_openshift_streams_for_apache_kafka/1/guide/88e1487a-2a14-4b35-85b9-a7a2d67a37f3[Getting started with the `rhoas` CLI for Red Hat OpenShift Streams for Apache Kafka] to create your Kafka broker instance. +Make sure you copied the client id and client secret associated with the _ServiceAccount_ you created. + +Then, you can configure the Quarkus application to connect to the broker as follows: + +[source, properties] +---- +kafka.bootstrap.servers= # <1> +kafka.security.protocol=SASL_SSL +kafka.sasl.mechanism=PLAIN +kafka.sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required \ + username="${KAFKA_USERNAME}" \ # <2> + password="${KAFKA_PASSWORD}"; # <3> +---- +<1> The connection string, given on the admin console, such as `demo-c--bjsv-ldd-cvavkc-a.bf2.kafka.rhcloud.com:443` +<2> The kafka username (the client id from the service account) +<3> the kafka password (the client secret from the service account) + +NOTE: In general, these properties are prefixed using `%prod` to enable them only when running in production mode. + +IMPORTANT: As explained in https://access.redhat.com/documentation/en-us/red_hat_openshift_streams_for_apache_kafka/1/guide/88e1487a-2a14-4b35-85b9-a7a2d67a37f3[Getting started with the rhoas CLI for Red Hat OpenShift Streams for Apache Kafka], to use Red Hat OpenShift Streams for Apache Kafka, you must create the topic beforehand, create a _Service Account_, and provide permissions to read and write to your topic from that service account. +The authentication data (client id and secret) relates to the service account, which means you can implement fine-grain permissions and restrict access to the topic. + +When using Kubernetes, it is recommended to set the client id and secret in a Kubernetes secret: + +[source, yaml] +---- +apiVersion: v1 +kind: Secret +metadata: + name: kafka-credentials +stringData: + KAFKA_USERNAME: "..." + KAFKA_PASSWORD: "..." +---- + +To allow your Quarkus application to use that secret, add the following line to the `application.properties` file: + +[source, properties] +---- +%prod.quarkus.openshift.env.secrets=kafka-credentials +---- + == Going further This guide has shown how you can interact with Kafka using Quarkus. diff --git a/docs/src/main/asciidoc/rest-client.adoc b/docs/src/main/asciidoc/rest-client.adoc index 2a61527b55439..6be30f043a969 100644 --- a/docs/src/main/asciidoc/rest-client.adoc +++ b/docs/src/main/asciidoc/rest-client.adoc @@ -685,7 +685,7 @@ public class WireMockExtensions implements QuarkusTestResourceLifecycleManager { wireMockServer = new WireMockServer(); wireMockServer.start(); // <3> - stubFor(get(urlEqualTo("/extensions?id=io.quarkus:quarkus-rest-client")) // <4> + wireMockServer.stubFor(get(urlEqualTo("/extensions?id=io.quarkus:quarkus-rest-client")) // <4> .willReturn(aResponse() .withHeader("Content-Type", "application/json") .withBody( @@ -695,7 +695,7 @@ public class WireMockExtensions implements QuarkusTestResourceLifecycleManager { "}]" ))); - stubFor(get(urlMatching(".*")).atPriority(10).willReturn(aResponse().proxiedFrom("https://stage.code.quarkus.io/api"))); // <5> + wireMockServer.stubFor(get(urlMatching(".*")).atPriority(10).willReturn(aResponse().proxiedFrom("https://stage.code.quarkus.io/api"))); // <5> return Collections.singletonMap("quarkus.rest-client.\"org.acme.rest.client.ExtensionsService\".url", wireMockServer.baseUrl()); // <6> } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AzureDiskVolumeConverter.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AzureDiskVolumeConverter.java index a48ebb1ed2395..4a248b66d73bc 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AzureDiskVolumeConverter.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/AzureDiskVolumeConverter.java @@ -14,7 +14,7 @@ public static AzureDiskVolume convert(Map.Entry e private static AzureDiskVolumeBuilder convert(AzureDiskVolumeConfig c) { AzureDiskVolumeBuilder b = new AzureDiskVolumeBuilder(); - b.withNewDiskName(c.diskName); + b.withDiskName(c.diskName); b.withDiskURI(c.diskURI); b.withKind(c.kind.name()); b.withCachingMode(c.cachingMode.name()); diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java index 42c6b455214d6..57e9574d610a9 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KnativeProcessor.java @@ -24,11 +24,14 @@ import io.dekorate.knative.decorator.ApplyGlobalTargetUtilizationDecorator; import io.dekorate.knative.decorator.ApplyLocalContainerConcurrencyDecorator; import io.dekorate.knative.decorator.ApplyRevisionNameDecorator; +import io.dekorate.knative.decorator.ApplyServiceAccountToRevisionSpecDecorator; import io.dekorate.knative.decorator.ApplyTrafficDecorator; import io.dekorate.kubernetes.config.EnvBuilder; import io.dekorate.kubernetes.decorator.AddConfigMapDataDecorator; import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; +import io.dekorate.kubernetes.decorator.AddImagePullSecretToServiceAccountDecorator; import io.dekorate.kubernetes.decorator.AddLabelDecorator; +import io.dekorate.kubernetes.decorator.AddServiceAccountResourceDecorator; import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; import io.dekorate.project.Project; import io.quarkus.container.spi.BaseImageInfoBuildItem; @@ -136,17 +139,20 @@ public List createDecorators(ApplicationInfoBuildItem applic Optional readinessPath, List roles, List roleBindings, - Optional customProjectRoot) { + Optional customProjectRoot, + List targets) { List result = new ArrayList<>(); - String name = ResourceNameUtil.getResourceName(config, applicationInfo); + if (!targets.stream().filter(KubernetesDeploymentTargetBuildItem::isEnabled) + .anyMatch(t -> KNATIVE.equals(t.getName()))) { + return result; + } + String name = ResourceNameUtil.getResourceName(config, applicationInfo); Optional project = KubernetesCommonHelper.createProject(applicationInfo, customProjectRoot, outputTarget, packageConfig); - result.addAll(KubernetesCommonHelper.createDecorators(project, KNATIVE, name, config, - metricsConfiguration, annotations, - labels, command, - ports, livenessPath, readinessPath, roles, roleBindings)); + result.addAll(KubernetesCommonHelper.createDecorators(project, KNATIVE, name, config, metricsConfiguration, annotations, + labels, command, ports, livenessPath, readinessPath, roles, roleBindings)); image.ifPresent(i -> { result.add(new DecoratorBuildItem(KNATIVE, new ApplyContainerImageDecorator(name, i.getImage()))); @@ -294,6 +300,16 @@ public List createDecorators(ApplicationInfoBuildItem applic result.add(new DecoratorBuildItem(new ApplyServiceAccountNameToRevisionSpecDecorator())); } + //Handle Image Pull Secrets + config.getImagePullSecrets().ifPresent(imagePullSecrets -> { + String serviceAccountName = config.getServiceAccount().orElse(name); + result.add(new DecoratorBuildItem(KNATIVE, new AddServiceAccountResourceDecorator(name))); + result.add( + new DecoratorBuildItem(KNATIVE, new ApplyServiceAccountToRevisionSpecDecorator(name, serviceAccountName))); + result.add(new DecoratorBuildItem(KNATIVE, + new AddImagePullSecretToServiceAccountDecorator(serviceAccountName, imagePullSecrets))); + }); + return result; } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java index 20ca37f526a12..49e9304788a9d 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesCommonHelper.java @@ -183,15 +183,16 @@ public static List createDecorators(Optional projec //Handle RBAC if (!roleBindings.isEmpty()) { - result.add(new DecoratorBuildItem(new ApplyServiceAccountNameDecorator())); - result.add(new DecoratorBuildItem(new AddServiceAccountResourceDecorator())); - roles.forEach(r -> result.add(new DecoratorBuildItem(new AddRoleResourceDecorator(name, r)))); + result.add(new DecoratorBuildItem(target, new ApplyServiceAccountNameDecorator())); + result.add(new DecoratorBuildItem(target, new AddServiceAccountResourceDecorator())); + roles.forEach(r -> result.add(new DecoratorBuildItem(target, new AddRoleResourceDecorator(name, r)))); roleBindings.forEach(rb -> { - result.add(new DecoratorBuildItem(new AddRoleBindingResourceDecorator(rb.getName(), null, rb.getRole(), - rb.isClusterWide() ? AddRoleBindingResourceDecorator.RoleKind.ClusterRole - : AddRoleBindingResourceDecorator.RoleKind.Role))); + result.add(new DecoratorBuildItem(target, + new AddRoleBindingResourceDecorator(rb.getName(), null, rb.getRole(), + rb.isClusterWide() ? AddRoleBindingResourceDecorator.RoleKind.ClusterRole + : AddRoleBindingResourceDecorator.RoleKind.Role))); labels.forEach(l -> { - result.add(new DecoratorBuildItem( + result.add(new DecoratorBuildItem(target, new AddLabelDecorator(rb.getName(), l.getKey(), l.getValue(), "RoleBinding"))); }); }); diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeManifestGenerator.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeManifestGenerator.java index 04887c821e6e2..657c8d9aa2f7b 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeManifestGenerator.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/MinikubeManifestGenerator.java @@ -21,7 +21,7 @@ import io.dekorate.kubernetes.decorator.AddServiceResourceDecorator; import io.dekorate.kubernetes.decorator.ApplyHeadlessDecorator; import io.dekorate.kubernetes.decorator.ApplyImageDecorator; -import io.dekorate.kubernetes.decorator.ApplyReplicasDecorator; +import io.dekorate.kubernetes.decorator.ApplyReplicasToDeploymentDecorator; import io.dekorate.project.ApplyProjectInfo; import io.dekorate.project.Project; import io.dekorate.utils.Images; @@ -48,11 +48,8 @@ public class MinikubeManifestGenerator extends AbstractKubernetesManifestGenerat private static final String METADATA_NAMESPACE = "metadata.namespace"; private static final String MINIKUBE = "minikube"; - private final ConfigurationRegistry configurationRegistry; - public MinikubeManifestGenerator(ResourceRegistry resourceRegistry, ConfigurationRegistry configurationRegistry) { - super(resourceRegistry); - this.configurationRegistry = configurationRegistry; + super(resourceRegistry, configurationRegistry); } @Override @@ -86,7 +83,9 @@ public void generate(KubernetesConfig config) { } if (config.getReplicas() != 1) { - resourceRegistry.decorate(MINIKUBE, new ApplyReplicasDecorator(config.getName(), config.getReplicas())); + resourceRegistry.decorate(MINIKUBE, new ApplyReplicasToDeploymentDecorator(config.getName(), config.getReplicas())); + resourceRegistry.decorate(MINIKUBE, + new ApplyReplicasToStatefulSetDecorator(config.getName(), config.getReplicas())); } String image = Strings.isNotNullOrEmpty(imageConfig.getImage()) diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java index fce53fda3d200..389f4531ab484 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/OpenshiftProcessor.java @@ -25,7 +25,7 @@ import io.dekorate.kubernetes.decorator.ApplyImagePullPolicyDecorator; import io.dekorate.kubernetes.decorator.RemoveFromSelectorDecorator; import io.dekorate.kubernetes.decorator.RemoveLabelDecorator; -import io.dekorate.openshift.decorator.ApplyReplicasDecorator; +import io.dekorate.openshift.decorator.ApplyReplicasToDeploymentConfigDecorator; import io.dekorate.project.Project; import io.dekorate.s2i.config.S2iBuildConfig; import io.dekorate.s2i.config.S2iBuildConfigBuilder; @@ -175,9 +175,15 @@ public List createDecorators(ApplicationInfoBuildItem applic Optional readinessPath, List roles, List roleBindings, - Optional customProjectRoot) { + Optional customProjectRoot, + List targets) { List result = new ArrayList<>(); + if (!targets.stream().filter(KubernetesDeploymentTargetBuildItem::isEnabled) + .anyMatch(t -> OPENSHIFT.equals(t.getName()))) { + return result; + } + String name = ResourceNameUtil.getResourceName(config, applicationInfo); Optional project = KubernetesCommonHelper.createProject(applicationInfo, customProjectRoot, outputTarget, @@ -215,10 +221,11 @@ public List createDecorators(ApplicationInfoBuildItem applic if (config.getReplicas() != 1) { // This only affects DeploymentConfig - result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyReplicasDecorator(name, config.getReplicas()))); + result.add(new DecoratorBuildItem(OPENSHIFT, + new ApplyReplicasToDeploymentConfigDecorator(name, config.getReplicas()))); // This only affects Deployment result.add(new DecoratorBuildItem(OPENSHIFT, - new io.dekorate.kubernetes.decorator.ApplyReplicasDecorator(name, config.getReplicas()))); + new io.dekorate.kubernetes.decorator.ApplyReplicasToDeploymentDecorator(name, config.getReplicas()))); // This only affects StatefulSet result.add(new DecoratorBuildItem(OPENSHIFT, new ApplyReplicasToStatefulSetDecorator(name, config.getReplicas()))); } diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java index b409c1d991005..e0a8e31d9fce0 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/VanillaKubernetesProcessor.java @@ -20,7 +20,8 @@ import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; import io.dekorate.kubernetes.decorator.ApplicationContainerDecorator; import io.dekorate.kubernetes.decorator.ApplyImagePullPolicyDecorator; -import io.dekorate.kubernetes.decorator.ApplyReplicasDecorator; +import io.dekorate.kubernetes.decorator.ApplyReplicasToDeploymentDecorator; +import io.dekorate.kubernetes.decorator.ApplyReplicasToStatefulSetDecorator; import io.dekorate.kubernetes.decorator.RemoveFromMatchingLabelsDecorator; import io.dekorate.kubernetes.decorator.RemoveFromSelectorDecorator; import io.dekorate.kubernetes.decorator.RemoveLabelDecorator; @@ -120,15 +121,19 @@ public List createDecorators(ApplicationInfoBuildItem applic Optional image, Optional command, List ports, Optional livenessPath, Optional readinessPath, List roles, - List roleBindings, Optional customProjectRoot) { + List roleBindings, Optional customProjectRoot, + List targets) { final List result = new ArrayList<>(); + if (!targets.stream().filter(KubernetesDeploymentTargetBuildItem::isEnabled) + .anyMatch(t -> KUBERNETES.equals(t.getName()))) { + return result; + } final String name = ResourceNameUtil.getResourceName(config, applicationInfo); Optional project = KubernetesCommonHelper.createProject(applicationInfo, customProjectRoot, outputTarget, packageConfig); - result.addAll(KubernetesCommonHelper.createDecorators(project, KUBERNETES, name, config, - metricsConfiguration, + result.addAll(KubernetesCommonHelper.createDecorators(project, KUBERNETES, name, config, metricsConfiguration, annotations, labels, command, ports, livenessPath, readinessPath, roles, roleBindings)); if (config.deploymentKind == KubernetesConfig.DeploymentResourceKind.StatefulSet) { @@ -138,7 +143,7 @@ public List createDecorators(ApplicationInfoBuildItem applic if (config.getReplicas() != 1) { // This only affects Deployment - result.add(new DecoratorBuildItem(KUBERNETES, new ApplyReplicasDecorator(name, config.getReplicas()))); + result.add(new DecoratorBuildItem(KUBERNETES, new ApplyReplicasToDeploymentDecorator(name, config.getReplicas()))); // This only affects StatefulSet result.add(new DecoratorBuildItem(KUBERNETES, new ApplyReplicasToStatefulSetDecorator(name, config.getReplicas()))); } diff --git a/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/PreMatchingHeadersFilterTest.java b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/PreMatchingHeadersFilterTest.java new file mode 100644 index 0000000000000..25f203132763f --- /dev/null +++ b/extensions/resteasy-reactive/quarkus-resteasy-reactive/deployment/src/test/java/io/quarkus/resteasy/reactive/server/test/customproviders/PreMatchingHeadersFilterTest.java @@ -0,0 +1,66 @@ +package io.quarkus.resteasy.reactive.server.test.customproviders; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.equalTo; + +import java.util.List; +import java.util.function.Supplier; + +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; + +import org.jboss.resteasy.reactive.server.ServerRequestFilter; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class PreMatchingHeadersFilterTest { + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest() + .setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(Resource.class); + } + }); + + @Test + public void testJsonHeaderAdded() { + given() + .body("{\"foo\": \"bar\"}") + .when() + .post("/test") + .then() + .statusCode(200) + .body("foo", equalTo("bar")); + } + + @Path("test") + public static class Resource { + + @POST + @Consumes("application/json") + @Produces("application/json") + public String post(String json) { + return json; + } + } + + public static class Filters { + @ServerRequestFilter(preMatching = true) + public void preMatchingFilter(ContainerRequestContext requestContext) { + // without this, RR would respond with HTTP 415 + requestContext.getHeaders().put(HttpHeaders.CONTENT_TYPE, List.of(MediaType.APPLICATION_JSON)); + } + } +} diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ArrayPairsQueryParamTest.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ArrayPairsQueryParamTest.java new file mode 100644 index 0000000000000..19c285979b981 --- /dev/null +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/test/java/io/quarkus/rest/client/reactive/ArrayPairsQueryParamTest.java @@ -0,0 +1,95 @@ +package io.quarkus.rest.client.reactive; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; + +import org.eclipse.microprofile.rest.client.RestClientBuilder; +import org.eclipse.microprofile.rest.client.ext.QueryParamStyle; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpServer; + +public class ArrayPairsQueryParamTest { + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest(); + + @Inject + Vertx vertx; + + Client client; + HttpServer server; + + @BeforeEach + void setUp() throws ExecutionException, InterruptedException { + setupServer(); + + client = createClient(); + } + + @AfterEach + void shutDown() throws IOException, ExecutionException, InterruptedException, TimeoutException { + server.close().toCompletionStage().toCompletableFuture().get(5, TimeUnit.SECONDS); + + ((Closeable) client).close(); + } + + @Test + void shouldPassMultiParamWithBrackets() { + String response = client.getWithParams(List.of("one", "two")); + + assertThat(response).contains("my-param[]=one").contains("my-param[]=two"); + } + + @Test + void shouldPassSingleParamWithoutBrackets() { + String response = client.getWithParams(List.of("one")); + + assertThat(response).isEqualTo("my-param=one"); + } + + @Path("/") + public interface Client { + + @GET + String getWithParams(@QueryParam("my-param") List paramValues); + } + + private void setupServer() throws InterruptedException, ExecutionException { + CompletableFuture startResult = new CompletableFuture<>(); + vertx.createHttpServer().requestHandler(request -> request.response().setStatusCode(200).end(request.query())) + .listen(8082) + .onComplete(server -> { + if (server.failed()) { + startResult.completeExceptionally(server.cause()); + } else { + startResult.complete(server.result()); + } + }); + server = startResult.get(); + } + + private Client createClient() { + return RestClientBuilder.newBuilder() + .queryParamStyle(QueryParamStyle.ARRAY_PAIRS) + .baseUri(URI.create("http://localhost:8082")) + .build(Client.class); + } +} diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java index 596da0894df60..52af24d337a7d 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/vertx/http/deployment/devmode/console/DevConsoleProcessor.java @@ -100,6 +100,7 @@ import io.quarkus.vertx.http.runtime.devmode.RuntimeDevConsoleRoute; import io.quarkus.vertx.http.runtime.logstream.LogStreamRecorder; import io.quarkus.vertx.http.runtime.logstream.WebSocketLogHandler; +import io.smallrye.common.vertx.VertxContext; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.VertxOptions; @@ -107,6 +108,7 @@ import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerRequest; import io.vertx.core.http.impl.Http1xServerConnection; +import io.vertx.core.impl.ContextInternal; import io.vertx.core.impl.EventLoopContext; import io.vertx.core.impl.VertxBuilder; import io.vertx.core.impl.VertxInternal; @@ -175,27 +177,15 @@ public void initChannel(VirtualChannel ch) throws Exception { // Vert.x 4 Migration: Verify this behavior EventLoopContext context = vertx.createEventLoopContext(); - // ContextInternal context = (ContextInternal) vertx - // .createEventLoopContext(null, null, new JsonObject(), - // Thread.currentThread().getContextClassLoader()); VertxHandler handler = VertxHandler.create(chctx -> { Http1xServerConnection connection = new Http1xServerConnection( - () -> context, + () -> (ContextInternal) VertxContext.getOrCreateDuplicatedContext(context), null, new HttpServerOptions(), chctx, context, "localhost", null); - - // Http1xServerConnection conn = new Http1xServerConnection( - // context.owner(), - // null, - // new HttpServerOptions(), - // chctx, - // context, - // "localhost", - // null); connection.handler(new Handler() { @Override public void handle(HttpServerRequest event) { @@ -269,6 +259,7 @@ public void handle(RoutingContext event) { }; router = Router.router(devConsoleVertx); router.errorHandler(500, errorHandler); + router.route() .order(Integer.MIN_VALUE) .handler(new FlashScopeHandler()); @@ -508,22 +499,30 @@ public static void openBrowser(HttpRootPathBuildItem rp, NonApplicationRootPathB Runtime rt = Runtime.getRuntime(); OS os = OS.determineOS(); + String[] command = null; try { switch (os) { case MAC: - rt.exec(new String[] { "open", url }); + command = new String[] { "open", url }; break; case LINUX: - rt.exec(new String[] { "xdg-open", url }); + command = new String[] { "xdg-open", url }; break; case WINDOWS: - rt.exec(new String[] { "rundll32", "url.dll,FileProtocolHandler", url }); + command = new String[] { "rundll32", "url.dll,FileProtocolHandler", url }; break; case OTHER: log.error("Cannot launch browser on this operating system"); } + if (command != null) { + rt.exec(command); + } } catch (Exception e) { - log.error("Failed to launch browser", e); + log.debug("Failed to launch browser", e); + if (command != null) { + log.warn("Unable to open browser using command: '" + String.join(" ", command) + "'. Failure is: '" + + e.getMessage() + "'"); + } } } diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java index cfb398cc681d5..fca78e6a0837c 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java @@ -891,6 +891,8 @@ private static HttpServerOptions createDomainSocketOptions(HttpConfiguration htt HttpServerOptions options = new HttpServerOptions(); applyCommonOptions(options, httpConfiguration, websocketSubProtocols); + // Override the host (0.0.0.0 by default) with the configured domain socket. + options.setHost(httpConfiguration.domainSocket); return options; } @@ -901,14 +903,6 @@ private static void setIdleTimeout(HttpConfiguration httpConfiguration, HttpServ options.setIdleTimeoutUnit(TimeUnit.MILLISECONDS); } - public void warnIfPortChanged(HttpConfiguration config, int port) { - if (config.port != port) { - LOGGER.errorf( - "quarkus.http.port was specified at build time as %s however run time value is %s, Kubernetes metadata will be incorrect.", - port, config.port); - } - } - public void addRoute(RuntimeValue router, Function route, Handler handler, HandlerType blocking) { diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleFilter.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleFilter.java index 4a870d1383fc1..a565c2d1f7d7c 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleFilter.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/devmode/DevConsoleFilter.java @@ -34,6 +34,7 @@ public void handle(RoutingContext event) { for (Map.Entry entry : event.request().headers()) { headers.put(entry.getKey(), event.request().headers().getAll(entry.getKey())); } + event.request().resume(); if (event.getBody() != null) { DevConsoleRequest request = new DevConsoleRequest(event.request().method().name(), event.request().uri(), headers, event.getBody().getBytes()); diff --git a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/UriBuilderImpl.java b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/UriBuilderImpl.java index d9d20a87e55ae..dff2c2d4eedb5 100644 --- a/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/UriBuilderImpl.java +++ b/independent-projects/resteasy-reactive/common/runtime/src/main/java/org/jboss/resteasy/reactive/common/jaxrs/UriBuilderImpl.java @@ -900,7 +900,8 @@ public UriBuilder clientQueryParam(String name, Object... values) throws Illegal break; case ARRAY_PAIRS: prefix = "&"; - sb.append(Encode.encodeQueryParamAsIs(name)).append("[]=") + String queryParamConnector = arrayPairsConnector(values); + sb.append(Encode.encodeQueryParamAsIs(name)).append(queryParamConnector) .append(Encode.encodeQueryParamAsIs(value.toString())); break; } @@ -943,7 +944,9 @@ public UriBuilder queryParam(String name, Object... values) throws IllegalArgume break; case ARRAY_PAIRS: prefix = "&"; - sb.append(Encode.encodeQueryParam(name)).append("[]=").append(Encode.encodeQueryParam(value.toString())); + String queryParamConnector = arrayPairsConnector(values); + sb.append(Encode.encodeQueryParam(name)).append(queryParamConnector) + .append(Encode.encodeQueryParam(value.toString())); break; } } @@ -952,6 +955,10 @@ public UriBuilder queryParam(String name, Object... values) throws IllegalArgume return this; } + private String arrayPairsConnector(Object[] values) { + return values.length == 1 ? "=" : "[]="; + } + public UriBuilder replaceQueryParam(String name, Object... values) throws IllegalArgumentException { if (name == null) throw new IllegalArgumentException("Name parameter is null"); diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java index 05296f9e0cbbc..e64039a8659e9 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/ClassRoutingHandler.java @@ -109,7 +109,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti // according to the spec we need to return HTTP 415 when content-type header doesn't match what is specified in @Consumes if (!target.value.getConsumes().isEmpty()) { - String contentType = serverRequest.getRequestHeader(HttpHeaders.CONTENT_TYPE); + String contentType = (String) requestContext.getHeader(HttpHeaders.CONTENT_TYPE, true); if (contentType != null) { try { if (MediaTypeHelper.getFirstMatch( @@ -126,7 +126,7 @@ public void handle(ResteasyReactiveRequestContext requestContext) throws Excepti if (target.value.getProduces() != null) { // there could potentially be multiple Accept headers and we need to response with 406 // if none match the method's @Produces - List accepts = serverRequest.getAllRequestHeaders(HttpHeaders.ACCEPT); + List accepts = (List) requestContext.getHeader(HttpHeaders.ACCEPT, false); if (!accepts.isEmpty()) { boolean hasAtLeastOneMatch = false; for (int i = 0; i < accepts.size(); i++) { diff --git a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FixedProducesHandler.java b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FixedProducesHandler.java index 0626066d3c823..503ecb988190b 100644 --- a/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FixedProducesHandler.java +++ b/independent-projects/resteasy-reactive/server/runtime/src/main/java/org/jboss/resteasy/reactive/server/handlers/FixedProducesHandler.java @@ -32,7 +32,7 @@ public FixedProducesHandler(MediaType mediaType, EntityWriter writer) { @Override public void handle(ResteasyReactiveRequestContext requestContext) throws Exception { - List acceptValues = requestContext.serverRequest().getAllRequestHeaders(HttpHeaders.ACCEPT); + List acceptValues = (List) requestContext.getHeader(HttpHeaders.ACCEPT, false); if (acceptValues.isEmpty()) { requestContext.setResponseContentType(mediaType); requestContext.setEntityWriter(writer); diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/gradle-kotlin-dsl/kotlin/build.tpl.qute.gradle.kts b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/gradle-kotlin-dsl/kotlin/build.tpl.qute.gradle.kts index a90e0705a1086..044ac95a86bd6 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/gradle-kotlin-dsl/kotlin/build.tpl.qute.gradle.kts +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/gradle-kotlin-dsl/kotlin/build.tpl.qute.gradle.kts @@ -15,10 +15,6 @@ allOpen { } tasks.withType { - {#if java.version == "11"} - kotlinOptions.jvmTarget = JavaVersion.VERSION_11.toString() - {#else} - kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() - {/if} + kotlinOptions.jvmTarget = JavaVersion.VERSION_{java.version}.toString() kotlinOptions.javaParameters = true } diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/gradle/kotlin/build.tpl.qute.gradle b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/gradle/kotlin/build.tpl.qute.gradle index 2a7e4b6591428..6c51b5039f488 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/gradle/kotlin/build.tpl.qute.gradle +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/gradle/kotlin/build.tpl.qute.gradle @@ -15,10 +15,10 @@ allOpen { } compileKotlin { - kotlinOptions.jvmTarget = JavaVersion.VERSION_11 + kotlinOptions.jvmTarget = JavaVersion.VERSION_{java.version} kotlinOptions.javaParameters = true } compileTestKotlin { - kotlinOptions.jvmTarget = JavaVersion.VERSION_11 + kotlinOptions.jvmTarget = JavaVersion.VERSION_{java.version} } diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/kotlin/pom.tpl.qute.xml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/kotlin/pom.tpl.qute.xml index e4b6c73d62bc7..303e11409fcc7 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/kotlin/pom.tpl.qute.xml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/buildtool/maven/kotlin/pom.tpl.qute.xml @@ -40,7 +40,7 @@ true - 11 + {java.version} all-open diff --git a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/spring-web-codestart/codestart.yml b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/spring-web-codestart/codestart.yml index 199ac0b1952e6..33e2454f8eab0 100644 --- a/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/spring-web-codestart/codestart.yml +++ b/independent-projects/tools/base-codestarts/src/main/resources/codestarts/quarkus/extension-codestarts/spring-web-codestart/codestart.yml @@ -16,6 +16,6 @@ language: package-name: org.acme dependencies: - io.quarkus:quarkus-spring-web - - io.quarkus:quarkus-resteasy-jackson + - io.quarkus:quarkus-resteasy-reactive-jackson test-dependencies: - io.rest-assured:rest-assured diff --git a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/CreateProjectHelper.java b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/CreateProjectHelper.java index a8371290c7534..8fd67e16cc071 100644 --- a/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/CreateProjectHelper.java +++ b/independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/codegen/CreateProjectHelper.java @@ -25,6 +25,7 @@ public class CreateProjectHelper { // ordering is important here, so let's keep them ordered public static final SortedSet JAVA_VERSIONS_LTS = new TreeSet<>(List.of(11, 17)); private static final int DEFAULT_JAVA_VERSION = 11; + private static final int MAX_LTS_SUPPORTED_BY_KOTLIN = 17; public static final String DETECT_JAVA_RUNTIME_VERSION = "<>"; private static final Pattern JAVA_VERSION_PATTERN = Pattern.compile("(\\d+)(?:\\..*)?"); @@ -98,7 +99,14 @@ public static void setJavaVersion(Map values, String javaTarget) javaFeatureVersionTarget = Runtime.version().feature(); } - values.put(ProjectGenerator.JAVA_TARGET, String.valueOf(determineBestJavaLtsVersion(javaFeatureVersionTarget))); + int bestJavaLtsVersion = determineBestJavaLtsVersion(javaFeatureVersionTarget); + + if (SourceType.KOTLIN.equals(values.get(ProjectGenerator.SOURCE_TYPE)) + && bestJavaLtsVersion > MAX_LTS_SUPPORTED_BY_KOTLIN) { + bestJavaLtsVersion = MAX_LTS_SUPPORTED_BY_KOTLIN; + } + + values.put(ProjectGenerator.JAVA_TARGET, String.valueOf(bestJavaLtsVersion)); } public static int determineBestJavaLtsVersion() { diff --git a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/codegen/CreateProjectHelperTest.java b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/codegen/CreateProjectHelperTest.java index 8e0dc592e7c65..9cacd513b3e15 100644 --- a/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/codegen/CreateProjectHelperTest.java +++ b/independent-projects/tools/devtools-common/src/test/java/io/quarkus/devtools/project/codegen/CreateProjectHelperTest.java @@ -62,4 +62,13 @@ public void testDetermineBestLtsVersion() { assertEquals(17, CreateProjectHelper.determineBestJavaLtsVersion(17)); assertEquals(17, CreateProjectHelper.determineBestJavaLtsVersion(18)); } + + @Test + public void givenKotlinProjectWithVersion18ShouldReturn17() { + Map values = new HashMap<>(); + values.put(ProjectGenerator.SOURCE_TYPE, SourceType.KOTLIN); + + CreateProjectHelper.setJavaVersion(values, "18"); + assertEquals("17", values.get("java_target")); + } } diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeContainerImageTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeContainerImageTest.java index 159e2bf9e4f80..83df7a354eadd 100644 --- a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeContainerImageTest.java +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeContainerImageTest.java @@ -31,6 +31,7 @@ public class KnativeContainerImageTest { @Test public void assertGeneratedResources() throws IOException { Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertThat(kubernetesDir) .isDirectoryContaining(p -> p.getFileName().endsWith("knative.json")) .isDirectoryContaining(p -> p.getFileName().endsWith("knative.yml")); diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeWithImagePullSecretTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeWithImagePullSecretTest.java new file mode 100644 index 0000000000000..4836b59f0cf1c --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KnativeWithImagePullSecretTest.java @@ -0,0 +1,73 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.knative.serving.v1.Service; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.ServiceAccount; +import io.quarkus.bootstrap.model.AppArtifact; +import io.quarkus.builder.Version; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class KnativeWithImagePullSecretTest { + + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class).addClasses(GreetingResource.class)) + .setApplicationName("knative-with-image-pull-secret") + .setApplicationVersion("0.1-SNAPSHOT") + .withConfigurationResource("knative-with-image-pull-secret.properties") + .setLogFileName("k8s.log") + .setForcedDependencies( + Collections.singletonList(new AppArtifact("io.quarkus", "quarkus-kubernetes", Version.getVersion()))); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + final Path kubernetesDir = prodModeTestResults.getBuildDir().resolve("kubernetes"); + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("knative.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("knative.yml")); + + List kubernetesList = DeserializationUtil + .deserializeAsList(kubernetesDir.resolve("knative.yml")); + + assertThat(kubernetesList).filteredOn(i -> "Service".equals(i.getKind())).singleElement().satisfies(i -> { + assertThat(i).isInstanceOfSatisfying(Service.class, s -> { + assertThat(s.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo("knative-with-image-pull-secret"); + }); + + assertThat(s.getSpec()).satisfies(serviceSpec -> { + assertThat(serviceSpec.getTemplate()).satisfies(t -> { + assertThat(t.getSpec()).satisfies(r -> { + assertThat(r.getServiceAccountName()).isEqualTo("knative-with-image-pull-secret"); + }); + }); + }); + }); + }); + + assertThat(kubernetesList).filteredOn(i -> "ServiceAccount".equals(i.getKind())).singleElement().satisfies(i -> { + assertThat(i).isInstanceOfSatisfying(ServiceAccount.class, s -> { + assertTrue(s.getImagePullSecrets().stream().anyMatch(r -> r.getName().equals("my-secret"))); + }); + }); + } + +} diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/knative-with-image-pull-secret.properties b/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/knative-with-image-pull-secret.properties new file mode 100644 index 0000000000000..12167dafaf894 --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/resources/knative-with-image-pull-secret.properties @@ -0,0 +1,2 @@ +quarkus.kubernetes.deployment-target=knative +quarkus.knative.image-pull-secrets=my-secret \ No newline at end of file diff --git a/integration-tests/locales/src/test/java/io/quarkus/locales/it/LocalesIT.java b/integration-tests/locales/src/test/java/io/quarkus/locales/it/LocalesIT.java index 69e015e51da41..90228c7ac399c 100644 --- a/integration-tests/locales/src/test/java/io/quarkus/locales/it/LocalesIT.java +++ b/integration-tests/locales/src/test/java/io/quarkus/locales/it/LocalesIT.java @@ -11,6 +11,15 @@ import io.quarkus.test.junit.NativeImageTest; import io.restassured.RestAssured; +/** + * For the Native test cases to function, the operating system has to have locales + * support installed. A barebone system with only C.UTF-8 default locale available + * won't be able to pass the tests. + * + * For example, this package satisfies the dependency on a RHEL 9 type of OS: + * glibc-all-langpacks + * + */ @NativeImageTest public class LocalesIT { diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestNestedWithTestProfileTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestNestedWithTestProfileTestCase.java new file mode 100644 index 0000000000000..ea34887a78915 --- /dev/null +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/QuarkusTestNestedWithTestProfileTestCase.java @@ -0,0 +1,83 @@ +package io.quarkus.it.main; + +import static org.hamcrest.Matchers.is; + +import java.util.Collections; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; + +@QuarkusTest +@Tag("nested") +@TestProfile(QuarkusTestNestedWithTestProfileTestCase.OuterProfile.class) +public class QuarkusTestNestedWithTestProfileTestCase { + + private static final int TEST_PORT_FROM_PROFILE = 7777; + + @Nested + class NestedCase { + + @Test + void testProfileFromNested() { + Assertions.assertEquals(TEST_PORT_FROM_PROFILE, RestAssured.port); + RestAssured.when() + .get("/greeting/Stu") + .then() + .statusCode(200) + .body(is("OuterProfile Stu")); + } + } + + @Nested + @TestProfile(QuarkusTestNestedWithTestProfileTestCase.ModernEnglishProfile.class) + class ModernEnglishCase { + + @Test + void testProfileFromNested() { + RestAssured.when() + .get("/greeting/Stu") + .then() + .statusCode(200) + .body(is("Hey Stu")); + } + } + + public static class OuterProfile implements QuarkusTestProfile { + + @Override + public Map getConfigOverrides() { + return Collections.singletonMap("quarkus.http.test-port", "" + TEST_PORT_FROM_PROFILE); + } + + @Override + public String[] commandLineParameters() { + return new String[] { "OuterProfile" }; + } + + @Override + public boolean runMainMethod() { + return true; + } + } + + public static class ModernEnglishProfile implements QuarkusTestProfile { + + @Override + public String[] commandLineParameters() { + return new String[] { "Hey" }; + } + + @Override + public boolean runMainMethod() { + return true; + } + } +} diff --git a/integration-tests/main/src/test/java/io/quarkus/it/main/RestClientTestCase.java b/integration-tests/main/src/test/java/io/quarkus/it/main/RestClientTestCase.java index 268573e938fab..d5f41f180751b 100644 --- a/integration-tests/main/src/test/java/io/quarkus/it/main/RestClientTestCase.java +++ b/integration-tests/main/src/test/java/io/quarkus/it/main/RestClientTestCase.java @@ -47,6 +47,14 @@ public void testMicroprofileClientCDIIntegration() { .body(is("TEST")); } + /** + * This test in Native won't work on a barebone system, + * just with C.UTF-8 default fallback locale. + * + * For example, this package satisfies the dependency on a RHEL 9 type of OS: + * glibc-all-langpacks + * + */ @DisabledOnOs(OS.WINDOWS) @Test public void testEmojis() { diff --git a/integration-tests/reactive-messaging-rabbitmq-dyn/src/test/java/io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java b/integration-tests/reactive-messaging-rabbitmq-dyn/src/test/java/io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java index dc3746a31717a..1f810f34b1701 100644 --- a/integration-tests/reactive-messaging-rabbitmq-dyn/src/test/java/io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java +++ b/integration-tests/reactive-messaging-rabbitmq-dyn/src/test/java/io/quarkus/it/rabbitmq/RabbitMQConnectorDynCredsTest.java @@ -7,11 +7,11 @@ import java.util.List; import java.util.Map; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import org.testcontainers.containers.Network; import org.testcontainers.containers.RabbitMQContainer; -import org.testcontainers.shaded.org.apache.commons.lang.RandomStringUtils; import org.testcontainers.utility.DockerImageName; import io.quarkus.it.rabbitmq.RabbitMQConnectorDynCredsTest.VaultResource; diff --git a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java index 970242d209c98..3ec62a44e2ea8 100644 --- a/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java +++ b/test-framework/junit5/src/main/java/io/quarkus/test/junit/AbstractJvmQuarkusTestExtension.java @@ -184,12 +184,17 @@ protected PrepareResult createAugmentor(ExtensionContext context, Class getQuarkusTestProfile(ExtensionContext extensionContext) { - TestProfile annotation = extensionContext.getRequiredTestClass().getAnnotation(TestProfile.class); - Class selectedProfile = null; - if (annotation != null) { - selectedProfile = annotation.value(); + Class testClass = extensionContext.getRequiredTestClass(); + while (testClass != null) { + TestProfile annotation = testClass.getAnnotation(TestProfile.class); + if (annotation != null) { + return annotation.value(); + } + + testClass = testClass.getEnclosingClass(); } - return selectedProfile; + + return null; } protected static boolean hasPerTestResources(ExtensionContext extensionContext) {