This is an automated email from the ASF dual-hosted git repository. jamesnetherton pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-quarkus.git
commit 9922cef851b8979cd11601942c62b1c35c1caaac Author: James Netherton <[email protected]> AuthorDate: Mon Feb 24 11:10:47 2025 +0000 Add tests for Kubernetes property resolution and context reloading --- .../kubernetes/deployment/KubernetesProcessor.java | 15 +++ integration-tests/kubernetes/README.adoc | 8 ++ .../kubernetes/it/KubernetesConfigMapResource.java | 23 ++++- .../kubernetes/it/KubernetesResource.java | 32 ++++++ .../component/kubernetes/it/KubernetesRoutes.java | 25 +++++ .../kubernetes/it/KubernetesSecretResource.java | 18 ++++ .../src/main/resources/application.properties | 18 ++++ .../it/KubernetesConfigMapContextReloadIT.java | 25 +++++ .../it/KubernetesConfigMapContextReloadTest.java | 102 ++++++++++++++++++ .../kubernetes/it/KubernetesConfigMapTest.java | 41 ++++++++ .../component/kubernetes/it/KubernetesJobTest.java | 38 +++---- .../it/KubernetesSecretContextReloadIT.java | 25 +++++ .../it/KubernetesSecretContextReloadTest.java | 115 +++++++++++++++++++++ .../kubernetes/it/KubernetesSecretTest.java | 53 +++++++++- 14 files changed, 517 insertions(+), 21 deletions(-) diff --git a/extensions/kubernetes/deployment/src/main/java/org/apache/camel/quarkus/component/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/deployment/src/main/java/org/apache/camel/quarkus/component/kubernetes/deployment/KubernetesProcessor.java index a122e0987f..3280f2f596 100644 --- a/extensions/kubernetes/deployment/src/main/java/org/apache/camel/quarkus/component/kubernetes/deployment/KubernetesProcessor.java +++ b/extensions/kubernetes/deployment/src/main/java/org/apache/camel/quarkus/component/kubernetes/deployment/KubernetesProcessor.java @@ -18,12 +18,16 @@ package org.apache.camel.quarkus.component.kubernetes.deployment; import io.fabric8.kubernetes.client.KubernetesClient; import io.quarkus.arc.deployment.BeanContainerBuildItem; +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.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import org.apache.camel.quarkus.component.kubernetes.CamelKubernetesRecorder; import org.apache.camel.quarkus.core.deployment.spi.CamelRuntimeBeanBuildItem; +import org.apache.camel.vault.KubernetesConfigMapVaultConfiguration; +import org.apache.camel.vault.KubernetesVaultConfiguration; class KubernetesProcessor { private static final String FEATURE = "camel-kubernetes"; @@ -43,4 +47,15 @@ class KubernetesProcessor { KubernetesClient.class.getName(), recorder.getKubernetesClient(beanContainer.getValue())); } + + /** + * TODO: Remove this https://github.com/apache/camel-quarkus/issues/7045 + */ + @BuildStep + void registerForReflection(BuildProducer<ReflectiveClassBuildItem> reflectiveClass) { + reflectiveClass.produce(ReflectiveClassBuildItem + .builder(KubernetesVaultConfiguration.class, KubernetesConfigMapVaultConfiguration.class) + .methods() + .build()); + } } diff --git a/integration-tests/kubernetes/README.adoc b/integration-tests/kubernetes/README.adoc index 3799e9017f..95dc8b9fe8 100644 --- a/integration-tests/kubernetes/README.adoc +++ b/integration-tests/kubernetes/README.adoc @@ -19,3 +19,11 @@ Then provide the URL of your cluster. export QUARKUS_KUBERNETES_CLIENT_API_SERVER_URL=https://127.0.0.1:50239 ---- +It's also good practice to set the default namespace when working with a real cluster. + +[source,shell] +---- +export QUARKUS_KUBERNETES_CLIENT_NAMESPACE=my-namespace +---- + +Note that if you don't provide a default namespace then `default` is assumed to be the name of the default namespace. diff --git a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapResource.java b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapResource.java index d93027bbc1..3697f12b90 100644 --- a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapResource.java +++ b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapResource.java @@ -20,6 +20,7 @@ import java.net.URI; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import io.fabric8.kubernetes.api.model.ConfigMap; import jakarta.enterprise.context.ApplicationScoped; @@ -42,6 +43,7 @@ import org.apache.camel.component.kubernetes.KubernetesOperations; @Path("/kubernetes/configmap") @ApplicationScoped public class KubernetesConfigMapResource { + static final AtomicBoolean CONTEXT_RELOADED = new AtomicBoolean(false); @Inject ProducerTemplate producerTemplate; @@ -135,7 +137,10 @@ public class KubernetesConfigMapResource { headers.put(KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace); headers.put(KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.LIST_CONFIGMAPS); List<ConfigMap> configMapList = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, List.class); - return Response.ok().entity(configMapList).build(); + return Response.ok() + .entity(configMapList.stream() + .filter(configMap -> !configMap.getMetadata().getName().equals("kube-root-ca.crt"))) + .build(); } @Path("/labels/{namespace}") @@ -161,4 +166,20 @@ public class KubernetesConfigMapResource { ConfigMap configMap = consumerTemplate.receiveBody("seda:configMapEvents", 10000, ConfigMap.class); return Response.ok().entity(configMap).build(); } + + @Path("/property/resolve") + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response resolvePropertyFromConfigMap() { + String result = producerTemplate.requestBody("direct:configMapProperty", null, String.class); + return Response.ok().entity(result).build(); + } + + @Path("/context/reload/state") + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response contextReloadState() { + String result = CONTEXT_RELOADED.get() ? "reloaded" : "not-reloaded"; + return Response.ok().entity(result).build(); + } } diff --git a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesResource.java b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesResource.java index ec167718fc..ba46d2c177 100644 --- a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesResource.java +++ b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesResource.java @@ -18,19 +18,43 @@ package org.apache.camel.quarkus.component.kubernetes.it; import java.util.concurrent.atomic.AtomicReference; +import io.fabric8.kubernetes.client.KubernetesClient; +import jakarta.enterprise.event.Observes; import jakarta.inject.Inject; +import jakarta.ws.rs.GET; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.apache.camel.CamelContext; +import org.apache.camel.component.kubernetes.config_maps.vault.ConfigmapsReloadTriggerTask; import org.apache.camel.component.kubernetes.customresources.KubernetesCustomResourcesConsumer; +import org.apache.camel.component.kubernetes.secrets.vault.SecretsReloadTriggerTask; +import org.apache.camel.impl.event.CamelContextReloadedEvent; +import org.apache.camel.util.ObjectHelper; @Path("/kubernetes") public class KubernetesResource { @Inject CamelContext context; + @Inject + KubernetesClient client; + + void onReload(@Observes CamelContextReloadedEvent event) { + String eventSource = event.getAction().getClass().getName(); + if (eventSource.startsWith(ConfigmapsReloadTriggerTask.class.getName())) { + KubernetesConfigMapResource.CONTEXT_RELOADED.set(true); + } + + if (eventSource.startsWith(SecretsReloadTriggerTask.class.getName())) { + KubernetesSecretResource.CONTEXT_RELOADED.set(true); + } + } + @Path("/route/{routeId}/start") @POST public void startRoute( @@ -57,4 +81,12 @@ public class KubernetesResource { reference.set(null); context.getRouteController().stopRoute(routeId); } + + @Path("/default/namespace") + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response getDefaultNamespace() { + String namespace = ObjectHelper.isEmpty(client.getNamespace()) ? "default" : client.getNamespace(); + return Response.ok().entity(namespace).build(); + } } diff --git a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesRoutes.java b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesRoutes.java index 9c98bc0f76..426abd39e6 100644 --- a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesRoutes.java +++ b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesRoutes.java @@ -20,7 +20,10 @@ import java.util.concurrent.atomic.AtomicReference; import io.quarkus.runtime.annotations.RegisterForReflection; import org.apache.camel.BindToRegistry; +import org.apache.camel.Exchange; +import org.apache.camel.Processor; import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.spi.PropertiesComponent; @RegisterForReflection(targets = AtomicReference.class) public class KubernetesRoutes extends RouteBuilder { @@ -35,6 +38,28 @@ public class KubernetesRoutes extends RouteBuilder { from("direct:startNoAutoWired") .toD("kubernetes-pods-no-autowire:${header.masterUrl}"); + from("direct:configMapProperty") + .process(new Processor() { + @Override + public void process(Exchange exchange) { + PropertiesComponent component = exchange.getContext().getPropertiesComponent(); + component.resolveProperty("configmap:greeting-config/greeting").ifPresent(value -> { + exchange.getMessage().setBody("Hello " + value); + }); + } + }); + + from("direct:secretProperty") + .process(new Processor() { + @Override + public void process(Exchange exchange) { + PropertiesComponent component = exchange.getContext().getPropertiesComponent(); + component.resolveProperty("secret:secret-config/greeting").ifPresent(value -> { + exchange.getMessage().setBody("Hello " + value); + }); + } + }); + from("kubernetes-config-maps:local?resourceName=camel-configmap-watched") .id("configmap-listener") .autoStartup(false) diff --git a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretResource.java b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretResource.java index 587ce30a51..df088cc5cd 100644 --- a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretResource.java +++ b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretResource.java @@ -19,6 +19,7 @@ package org.apache.camel.quarkus.component.kubernetes.it; import java.net.URI; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import io.fabric8.kubernetes.api.model.Secret; import jakarta.enterprise.context.ApplicationScoped; @@ -40,6 +41,7 @@ import org.apache.camel.component.kubernetes.KubernetesOperations; @Path("/kubernetes/secret") @ApplicationScoped public class KubernetesSecretResource { + static final AtomicBoolean CONTEXT_RELOADED = new AtomicBoolean(false); @Inject ProducerTemplate producerTemplate; @@ -150,4 +152,20 @@ public class KubernetesSecretResource { List<Secret> list = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, List.class); return Response.ok().entity(list).build(); } + + @Path("/property/resolve") + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response resolvePropertyFromSecret() { + String result = producerTemplate.requestBody("direct:secretProperty", null, String.class); + return Response.ok().entity(result).build(); + } + + @Path("/context/reload/state") + @GET + @Produces(MediaType.TEXT_PLAIN) + public Response contextReloadState() { + String result = CONTEXT_RELOADED.get() ? "reloaded" : "not-reloaded"; + return Response.ok().entity(result).build(); + } } diff --git a/integration-tests/kubernetes/src/main/resources/application.properties b/integration-tests/kubernetes/src/main/resources/application.properties new file mode 100644 index 0000000000..0ae1d8a084 --- /dev/null +++ b/integration-tests/kubernetes/src/main/resources/application.properties @@ -0,0 +1,18 @@ +## --------------------------------------------------------------------------- +## Licensed to the Apache Software Foundation (ASF) under one or more +## contributor license agreements. See the NOTICE file distributed with +## this work for additional information regarding copyright ownership. +## The ASF licenses this file to You under the Apache License, Version 2.0 +## (the "License"); you may not use this file except in compliance with +## the License. You may obtain a copy of the License at +## +## http://www.apache.org/licenses/LICENSE-2.0 +## +## Unless required by applicable law or agreed to in writing, software +## distributed under the License is distributed on an "AS IS" BASIS, +## WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +## See the License for the specific language governing permissions and +## limitations under the License. +## --------------------------------------------------------------------------- + +camel.main.context-reload-enabled = true diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapContextReloadIT.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapContextReloadIT.java new file mode 100644 index 0000000000..696275675d --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapContextReloadIT.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.quarkus.component.kubernetes.it; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Disabled; + +@Disabled("https://github.com/apache/camel-quarkus/issues/7042") +@QuarkusTest +class KubernetesConfigMapContextReloadIT extends KubernetesConfigMapContextReloadTest { +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapContextReloadTest.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapContextReloadTest.java new file mode 100644 index 0000000000..2843c105a4 --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapContextReloadTest.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.quarkus.component.kubernetes.it; + +import java.time.Duration; +import java.util.Map; + +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.awaitility.Awaitility; +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.is; + +@Disabled("https://github.com/apache/camel-quarkus/issues/7042") +@TestProfile(KubernetesConfigMapContextReloadTest.KubernetesConfigMapContextReloadTestProfile.class) +@QuarkusTest +class KubernetesConfigMapContextReloadTest { + @Test + void configMapTriggersCamelContextReload() throws Exception { + Map<String, String> data = Map.of("foo", "bar"); + + String name = ConfigProvider.getConfig().getValue("camel.vault.kubernetescm.configmaps", String.class); + String namespace = RestAssured.get("/kubernetes/default/namespace") + .then() + .statusCode(200) + .extract() + .body() + .asString(); + + try { + // Create + RestAssured.given() + .contentType(ContentType.JSON) + .body(data) + .when() + .post("/kubernetes/configmap/" + namespace + "/" + name) + .then() + .statusCode(201) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace), + "data.foo", is("bar")); + + // Need to delay before updating the secret so that the reload trigger task can capture the event + Thread.sleep(500); + + // Update to trigger context reload + Map<String, String> updatedData = Map.of("bin", "baz"); + RestAssured.given() + .contentType(ContentType.JSON) + .body(updatedData) + .when() + .put("/kubernetes/configmap/" + namespace + "/" + name) + .then() + .statusCode(200) + .body("metadata.name", is(name), + "data.bin", is("baz")); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)).untilAsserted(() -> { + RestAssured.get("/kubernetes/configmap/context/reload/state") + .then() + .statusCode(200) + .body(is("reloaded")); + }); + } finally { + // Clean up + RestAssured.given() + .when() + .delete("/kubernetes/configmap/" + namespace + "/" + name) + .then() + .statusCode(204); + } + } + + public static final class KubernetesConfigMapContextReloadTestProfile implements QuarkusTestProfile { + @Override + public Map<String, String> getConfigOverrides() { + return Map.of( + "camel.vault.kubernetescm.refreshEnabled", "true", + "camel.vault.kubernetescm.configmaps", "configmap-reload-config"); + } + } +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapTest.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapTest.java index 5d2b67d544..d7a257e2d3 100644 --- a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapTest.java +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesConfigMapTest.java @@ -28,10 +28,13 @@ import io.quarkus.test.junit.QuarkusTest; import io.quarkus.test.kubernetes.client.KubernetesTestServer; import io.restassured.RestAssured; import io.restassured.http.ContentType; +import org.apache.camel.quarkus.test.EnabledIf; +import org.apache.camel.quarkus.test.mock.backend.MockBackendEnabled; import org.awaitility.Awaitility; import org.junit.jupiter.api.Test; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.oneOf; @QuarkusTest @QuarkusTestResource(CamelQuarkusKubernetesServerTestResource.class) @@ -200,4 +203,42 @@ class KubernetesConfigMapTest { .statusCode(204); } } + + @Test + void configMapPropertyResolution() throws Exception { + Map<String, String> data = Map.of("greeting", "Camel Quarkus"); + + String namespace = RestAssured.get("/kubernetes/default/namespace") + .then() + .statusCode(200) + .extract() + .body() + .asString(); + + try { + // Create + RestAssured.given() + .contentType(ContentType.JSON) + .body(data) + .when() + .post("/kubernetes/configmap/" + namespace + "/greeting-config") + .then() + .statusCode(201) + .body("metadata.name", is("greeting-config"), + "data.greeting", is(data.get("greeting"))); + + // Resolve property from ConfigMap + RestAssured.get("/kubernetes/configmap/property/resolve") + .then() + .statusCode(200) + .body(is("Hello Camel Quarkus")); + } finally { + // Clean up + RestAssured.given() + .when() + .delete("/kubernetes/configmap/" + namespace + "/greeting-config") + .then() + .statusCode(oneOf(404, 204)); + } + } } diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesJobTest.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesJobTest.java index cdab0d5c83..5f07b60615 100644 --- a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesJobTest.java +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesJobTest.java @@ -128,27 +128,27 @@ class KubernetesJobTest { .withPath("/apis/batch/v1/namespaces/" + listNamespace + "/jobs?labelSelector=app%3Dcamel-job") .andReturn(200, new JobListBuilder().addAllToItems(List.of(updatedJob)).build()) .once(); - } - // List - RestAssured.given() - .when() - .get("/kubernetes/job/" + listNamespace) - .then() - .statusCode(200) - .body("[0].metadata.name", is("camel-job"), - "[0].metadata.namespace", is(namespace.getNamespace())); + // List + RestAssured.given() + .when() + .get("/kubernetes/job/" + listNamespace) + .then() + .statusCode(200) + .body("[0].metadata.name", is("camel-job"), + "[0].metadata.namespace", is(namespace.getNamespace())); - // List by labels - RestAssured.given() - .contentType(ContentType.JSON) - .body(Map.of("app", "camel-job")) - .when() - .get("/kubernetes/job/labels/" + listNamespace) - .then() - .statusCode(200) - .body("[0].metadata.name", is("camel-job"), - "[0].metadata.namespace", is(namespace.getNamespace())); + // List by labels + RestAssured.given() + .contentType(ContentType.JSON) + .body(Map.of("app", "camel-job")) + .when() + .get("/kubernetes/job/labels/" + listNamespace) + .then() + .statusCode(200) + .body("[0].metadata.name", is("camel-job"), + "[0].metadata.namespace", is(namespace.getNamespace())); + } // Delete RestAssured.given() diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretContextReloadIT.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretContextReloadIT.java new file mode 100644 index 0000000000..4bea227c04 --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretContextReloadIT.java @@ -0,0 +1,25 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.quarkus.component.kubernetes.it; + +import io.quarkus.test.junit.QuarkusIntegrationTest; +import org.junit.jupiter.api.Disabled; + +@Disabled("https://github.com/apache/camel-quarkus/issues/7042") +@QuarkusIntegrationTest +class KubernetesSecretContextReloadIT extends KubernetesSecretContextReloadTest { +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretContextReloadTest.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretContextReloadTest.java new file mode 100644 index 0000000000..38c92983e9 --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretContextReloadTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.quarkus.component.kubernetes.it; + +import java.time.Duration; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.Secret; +import io.fabric8.kubernetes.api.model.SecretBuilder; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.junit.QuarkusTestProfile; +import io.quarkus.test.junit.TestProfile; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.awaitility.Awaitility; +import org.eclipse.microprofile.config.ConfigProvider; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.is; + +@Disabled("https://github.com/apache/camel-quarkus/issues/7042") +@TestProfile(KubernetesSecretContextReloadTest.KubernetesSecretContextReloadTestProfile.class) +@QuarkusTest +class KubernetesSecretContextReloadTest { + @Test + void secretTriggersCamelContextReload() throws Exception { + Map<String, String> data = Map.of("project-name", "Camel"); + + String name = ConfigProvider.getConfig().getValue("camel.vault.kubernetes.secrets", String.class); + Secret secret = new SecretBuilder() + .withNewMetadata() + .withLabels(Map.of("app", name)) + .withName(name) + .endMetadata() + .withData(data) + .build(); + + String namespace = RestAssured.get("/kubernetes/default/namespace") + .then() + .statusCode(200) + .extract() + .body() + .asString(); + + try { + // Create + RestAssured.given() + .contentType(ContentType.JSON) + .body(secret) + .when() + .post("/kubernetes/secret/" + namespace) + .then() + .statusCode(201) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace), + "metadata.annotations.app", is(name), + "data.project-name", is(data.get("project-name"))); + + // Need to delay before updating the secret so that the reload trigger task can capture the event + Thread.sleep(500); + + // Update to trigger context reload + Map<String, String> newData = Map.of("project-name", "Apache Camel Quarkus"); + secret.setData(newData); + RestAssured.given() + .contentType(ContentType.JSON) + .body(secret) + .when() + .put("/kubernetes/secret/" + namespace) + .then() + .statusCode(200) + .body("metadata.name", is(name), + "data.project-name", is(newData.get("project-name"))); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)).untilAsserted(() -> { + RestAssured.get("/kubernetes/secret/context/reload/state") + .then() + .statusCode(200) + .body(is("reloaded")); + }); + + } finally { + // Clean up + RestAssured.given() + .when() + .delete("/kubernetes/secret/" + namespace + "/" + name) + .then() + .statusCode(204); + } + } + + public static final class KubernetesSecretContextReloadTestProfile implements QuarkusTestProfile { + @Override + public Map<String, String> getConfigOverrides() { + return Map.of( + "camel.vault.kubernetes.refreshEnabled", "true", + "camel.vault.kubernetes.secrets", "secret-reload-config"); + } + } +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretTest.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretTest.java index ca83d83cd7..f06072066f 100644 --- a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretTest.java +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretTest.java @@ -52,7 +52,8 @@ class KubernetesSecretTest { .withNewMetadata() .withLabels(Map.of("app", name)) .withName(name) - .endMetadata().withData(data) + .endMetadata() + .withData(data) .build(); // Create @@ -141,4 +142,54 @@ class KubernetesSecretTest { .body("size()", is(0))); } } + + @Test + void secretPropertyResolution() throws Exception { + // base64 encoded string for: Camel Quarkus + Map<String, String> data = Map.of("greeting", "Q2FtZWwgUXVhcmt1cw=="); + + String name = "secret-config"; + Secret secret = new SecretBuilder() + .withNewMetadata() + .withLabels(Map.of("app", name)) + .withName(name) + .endMetadata() + .withData(data) + .build(); + + String namespace = RestAssured.get("/kubernetes/default/namespace") + .then() + .statusCode(200) + .extract() + .body() + .asString(); + + try { + // Create + RestAssured.given() + .contentType(ContentType.JSON) + .body(secret) + .when() + .post("/kubernetes/secret/" + namespace) + .then() + .statusCode(201) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace), + "metadata.annotations.app", is(name), + "data.greeting", is(data.get("greeting"))); + + // Resolve property from Secret + RestAssured.get("/kubernetes/secret/property/resolve") + .then() + .statusCode(200) + .body(is("Hello Camel Quarkus")); + } finally { + // Clean up + RestAssured.given() + .when() + .delete("/kubernetes/secret/" + namespace + "/" + name) + .then() + .statusCode(204); + } + } }
