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
The following commit(s) were added to refs/heads/main by this push: new 063fa5b85f Add camel-kubernetes deployment, secret, customresource tests 063fa5b85f is described below commit 063fa5b85f8e43dcc13877f969d8ab52f0967e76 Author: Andrej Vaňo <av...@redhat.com> AuthorDate: Fri Feb 14 16:10:57 2025 +0100 Add camel-kubernetes deployment, secret, customresource tests * test(camel-kubernetes): Add deployment tests * test(camel-kubernetes): Add secret tests * test(camel-kubernetes): Add custom resource tests --- .../kubernetes/it/KubernetesCRResource.java | 154 ++++++++++++++++ .../it/KubernetesDeploymentResource.java | 167 +++++++++++++++++ .../kubernetes/it/KubernetesSecretResource.java | 153 ++++++++++++++++ .../kubernetes/it/KubernetesCustomResourceIT.java | 24 +++ .../it/KubernetesCustomResourceTest.java | 198 ++++++++++++++++++++ .../kubernetes/it/KubernetesDeploymentIT.java | 24 +++ .../kubernetes/it/KubernetesDeploymentTest.java | 201 +++++++++++++++++++++ .../kubernetes/it/KubernetesSecretIT.java | 24 +++ .../kubernetes/it/KubernetesSecretTest.java | 144 +++++++++++++++ 9 files changed, 1089 insertions(+) diff --git a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCRResource.java b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCRResource.java new file mode 100644 index 0000000000..a3084fbe17 --- /dev/null +++ b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCRResource.java @@ -0,0 +1,154 @@ +/* + * 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.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.ConfigMap; +import io.fabric8.kubernetes.api.model.GenericKubernetesResource; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.kubernetes.KubernetesConstants; +import org.apache.camel.component.kubernetes.KubernetesOperations; +import org.apache.camel.util.json.JsonObject; + +@Path("/kubernetes/customresource") +@ApplicationScoped +public class KubernetesCRResource { + public static final String CRD_NAME = "testcr"; + public static final String CRD_GROUP = "test.com"; + public static final String CRD_VERSION = "v1"; + public static final String CRD_PLURAL = "testcrs"; + public static final String CRD_SCOPE = "Namespaced"; + + @Inject + ProducerTemplate producerTemplate; + + private final Map<String, String> common = Map.of( + "componentName", "kubernetes-custom-resources", + KubernetesConstants.KUBERNETES_CRD_NAME, CRD_NAME, + KubernetesConstants.KUBERNETES_CRD_PLURAL, CRD_PLURAL, + KubernetesConstants.KUBERNETES_CRD_GROUP, CRD_GROUP, + KubernetesConstants.KUBERNETES_CRD_VERSION, CRD_VERSION, + KubernetesConstants.KUBERNETES_CRD_SCOPE, CRD_SCOPE); + + @Path("/{namespace}/{name}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public JsonObject getCustomResource( + @PathParam("namespace") String namespace, + @PathParam("name") String name) { + + Map<String, Object> headers = new HashMap<>(common); + headers.putAll(Map.of( + KubernetesConstants.KUBERNETES_CRD_INSTANCE_NAME, name, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.GET_CUSTOMRESOURCE, + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace)); + + return producerTemplate.requestBodyAndHeaders("direct:start", null, headers, JsonObject.class); + } + + @Path("/{namespace}") + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public GenericKubernetesResource createCustomResource(@PathParam("namespace") String namespace, String instance) { + Map<String, Object> headers = new HashMap<>(common); + headers.putAll(Map.of( + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.CREATE_CUSTOMRESOURCE, + KubernetesConstants.KUBERNETES_CRD_INSTANCE, instance, + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace)); + return producerTemplate.requestBodyAndHeaders("direct:start", null, headers, GenericKubernetesResource.class); + } + + @Path("/{namespace}/{name}") + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response updateCustomResource( + @PathParam("namespace") String namespace, + @PathParam("name") String name, + String instance) { + + Map<String, Object> headers = new HashMap<>(common); + headers.putAll(Map.of( + KubernetesConstants.KUBERNETES_CRD_INSTANCE_NAME, name, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.UPDATE_CUSTOMRESOURCE, + KubernetesConstants.KUBERNETES_CRD_INSTANCE, instance, + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace)); + GenericKubernetesResource updated = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, + GenericKubernetesResource.class); + return Response.ok() + .entity(updated) + .build(); + } + + @Path("/{namespace}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response listCustomResources(@PathParam("namespace") String namespace) { + Map<String, Object> headers = new HashMap<>(common); + headers.putAll(Map.of( + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.LIST_CUSTOMRESOURCES, + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace)); + List<ConfigMap> list = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, List.class); + return Response.ok().entity(list).build(); + } + + @Path("/labels/{namespace}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response listCustomResourcesByLabels(@PathParam("namespace") String namespace, Map<String, String> labels) { + Map<String, Object> headers = new HashMap<>(common); + headers.putAll(Map.of( + KubernetesConstants.KUBERNETES_CRD_LABELS, labels, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.LIST_CUSTOMRESOURCES_BY_LABELS_OPERATION, + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace)); + List<GenericKubernetesResource> list = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, + List.class); + return Response.ok().entity(list).build(); + } + + @Path("/{namespace}/{name}") + @DELETE + public Response deleteCustomResource( + @PathParam("namespace") String namespace, + @PathParam("name") String name) { + Map<String, Object> headers = new HashMap<>(common); + headers.putAll(Map.of( + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.DELETE_CUSTOMRESOURCE, + KubernetesConstants.KUBERNETES_CRD_INSTANCE_NAME, name, + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace)); + producerTemplate.requestBodyAndHeaders("direct:start", null, headers); + return Response + .status(Response.Status.NO_CONTENT) + .build(); + } +} diff --git a/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentResource.java b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentResource.java new file mode 100644 index 0000000000..a596a34c0f --- /dev/null +++ b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentResource.java @@ -0,0 +1,167 @@ +/* + * 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.net.URI; +import java.util.List; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.kubernetes.KubernetesConstants; +import org.apache.camel.component.kubernetes.KubernetesOperations; + +@Path("/kubernetes/deployment") +@ApplicationScoped +public class KubernetesDeploymentResource { + + @Inject + ProducerTemplate producerTemplate; + + @Path("/{namespace}/{name}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Deployment getDeployment( + @PathParam("namespace") String namespace, + @PathParam("name") String name) { + + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-deployments", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_DEPLOYMENT_NAME, name, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.GET_DEPLOYMENT); + return producerTemplate.requestBodyAndHeaders("direct:start", null, headers, Deployment.class); + } + + @Path("/{namespace}") + @POST + @Produces(MediaType.APPLICATION_JSON) + public Response createDeployment( + @PathParam("namespace") String namespace, + Deployment deployment) throws Exception { + + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-deployments", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_DEPLOYMENT_NAME, deployment.getMetadata().getName(), + KubernetesConstants.KUBERNETES_DEPLOYMENT_SPEC, deployment.getSpec(), + KubernetesConstants.KUBERNETES_DEPLOYMENTS_LABELS, Map.of("app", deployment.getMetadata().getName()), + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.CREATE_DEPLOYMENT); + + Deployment createdDeployment = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, Deployment.class); + return Response + .created(new URI("https://camel.apache.org/")) + .entity(createdDeployment) + .build(); + } + + @Path("/{namespace}") + @PUT + @Produces(MediaType.APPLICATION_JSON) + public Response updateDeployment( + @PathParam("namespace") String namespace, + Deployment deployment) { + + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-deployments", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_DEPLOYMENT_NAME, deployment.getMetadata().getName(), + KubernetesConstants.KUBERNETES_DEPLOYMENT_SPEC, deployment.getSpec(), + KubernetesConstants.KUBERNETES_DEPLOYMENTS_LABELS, Map.of("app", deployment.getMetadata().getName()), + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.UPDATE_DEPLOYMENT); + + Deployment updatedDeployment = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, Deployment.class); + return Response.ok().entity(updatedDeployment).build(); + } + + @Path("/{namespace}/{name}") + @DELETE + public Response deleteDeployment( + @PathParam("namespace") String namespace, + @PathParam("name") String name) { + + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-deployments", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_DEPLOYMENT_NAME, name, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.DELETE_DEPLOYMENT); + producerTemplate.requestBodyAndHeaders("direct:start", null, headers); + return Response.status(Response.Status.NO_CONTENT).build(); + } + + @Path("/{namespace}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response listDeployments(@PathParam("namespace") String namespace) { + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-deployments", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.LIST_DEPLOYMENTS); + List<Deployment> list = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, List.class); + return Response.ok().entity(list).build(); + } + + @Path("/labels/{namespace}") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response listDeploymentsByLabels( + @PathParam("namespace") String namespace, + Map<String, String> labels) { + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-deployments", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_DEPLOYMENTS_LABELS, labels, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.LIST_DEPLOYMENTS_BY_LABELS_OPERATION); + List<Deployment> list = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, List.class); + return Response.ok().entity(list).build(); + } + + @Path("/{namespace}/{name}/{replicas}") + @POST + @Produces(MediaType.TEXT_PLAIN) + public Response scaleDeployment( + @PathParam("namespace") String namespace, + @PathParam("name") String name, + @PathParam("replicas") String replicas) throws Exception { + + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-deployments", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_DEPLOYMENT_NAME, name, + KubernetesConstants.KUBERNETES_DEPLOYMENT_REPLICAS, replicas, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.SCALE_DEPLOYMENT); + + Integer updatedReplicas = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, Integer.class); + return Response + .created(new URI("https://camel.apache.org/")) + .entity(updatedReplicas) + .build(); + } +} 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 new file mode 100644 index 0000000000..587ce30a51 --- /dev/null +++ b/integration-tests/kubernetes/src/main/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretResource.java @@ -0,0 +1,153 @@ +/* + * 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.net.URI; +import java.util.List; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.Secret; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.apache.camel.ProducerTemplate; +import org.apache.camel.component.kubernetes.KubernetesConstants; +import org.apache.camel.component.kubernetes.KubernetesOperations; + +@Path("/kubernetes/secret") +@ApplicationScoped +public class KubernetesSecretResource { + + @Inject + ProducerTemplate producerTemplate; + + @Path("/{namespace}/{name}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Secret getSecret( + @PathParam("namespace") String namespace, + @PathParam("name") String name) { + + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-secrets", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_SECRET_NAME, name, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.GET_SECRET_OPERATION); + return producerTemplate.requestBodyAndHeaders("direct:start", null, headers, Secret.class); + } + + @Path("/{namespace}") + @POST + @Produces(MediaType.APPLICATION_JSON) + public Response createSecret( + @PathParam("namespace") String namespace, + Secret secret) throws Exception { + + Map<String, String> labelsAndAnnotations = Map.of("app", secret.getMetadata().getName()); + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-secrets", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_SECRET_NAME, secret.getMetadata().getName(), + KubernetesConstants.KUBERNETES_SECRET, secret, + KubernetesConstants.KUBERNETES_SECRETS_LABELS, labelsAndAnnotations, + KubernetesConstants.KUBERNETES_SECRETS_ANNOTATIONS, labelsAndAnnotations, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.CREATE_SECRET_OPERATION); + + Secret createdSecret = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, Secret.class); + return Response + .created(new URI("https://camel.apache.org/")) + .entity(createdSecret) + .build(); + } + + @Path("/{namespace}") + @PUT + @Produces(MediaType.APPLICATION_JSON) + public Response updateSecret( + @PathParam("namespace") String namespace, + Secret secret) { + + Map<String, String> labelsAndAnnotations = Map.of("app", secret.getMetadata().getName()); + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-secrets", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_SECRET_NAME, secret.getMetadata().getName(), + KubernetesConstants.KUBERNETES_SECRET, secret, + KubernetesConstants.KUBERNETES_SECRETS_LABELS, labelsAndAnnotations, + KubernetesConstants.KUBERNETES_SECRETS_ANNOTATIONS, labelsAndAnnotations, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.UPDATE_SECRET_OPERATION); + + Secret updatedSecret = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, Secret.class); + return Response.ok() + .entity(updatedSecret) + .build(); + } + + @Path("/{namespace}/{name}") + @DELETE + public Response deleteSecret( + @PathParam("namespace") String namespace, + @PathParam("name") String name) { + + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-secrets", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_SECRET_NAME, name, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.DELETE_SECRET_OPERATION); + producerTemplate.requestBodyAndHeaders("direct:start", null, headers); + return Response + .status(Response.Status.NO_CONTENT) + .build(); + } + + @Path("/{namespace}") + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response listSecrets(@PathParam("namespace") String namespace) { + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-secrets", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.LIST_SECRETS); + List<Secret> list = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, List.class); + return Response.ok().entity(list).build(); + } + + @Path("/labels/{namespace}") + @GET + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response listSecretsByLabels( + @PathParam("namespace") String namespace, + Map<String, String> labels) { + Map<String, Object> headers = Map.of( + "componentName", "kubernetes-secrets", + KubernetesConstants.KUBERNETES_NAMESPACE_NAME, namespace, + KubernetesConstants.KUBERNETES_SECRETS_LABELS, labels, + KubernetesConstants.KUBERNETES_OPERATION, KubernetesOperations.LIST_SECRETS_BY_LABELS_OPERATION); + List<Secret> list = producerTemplate.requestBodyAndHeaders("direct:start", null, headers, List.class); + return Response.ok().entity(list).build(); + } +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCustomResourceIT.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCustomResourceIT.java new file mode 100644 index 0000000000..ca31440bfe --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCustomResourceIT.java @@ -0,0 +1,24 @@ +/* + * 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; + +@QuarkusIntegrationTest +class KubernetesCustomResourceIT extends KubernetesCustomResourceTest { + +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCustomResourceTest.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCustomResourceTest.java new file mode 100644 index 0000000000..3acec99ebe --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesCustomResourceTest.java @@ -0,0 +1,198 @@ +/* + * 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.HashMap; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.server.mock.KubernetesServer; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.kubernetes.client.KubernetesTestServer; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.is; + +@QuarkusTest +@QuarkusTestResource(CamelQuarkusKubernetesServerTestResource.class) +class KubernetesCustomResourceTest { + @KubernetesTestServer + private KubernetesServer mockServer; + + private final CustomResourceDefinition crd = new CustomResourceDefinitionBuilder() + .withApiVersion("apiextensions.k8s.io/v1") + .withKind("CustomResourceDefinition") + .withNewMetadata() + .withName(KubernetesCRResource.CRD_PLURAL + "." + KubernetesCRResource.CRD_GROUP) + .endMetadata() + .withNewSpec() + .withGroup(KubernetesCRResource.CRD_GROUP) + .withVersions(new CustomResourceDefinitionVersionBuilder() + .withName(KubernetesCRResource.CRD_VERSION) + .withServed(true) + .withStorage(true) + .withNewSchema() + .withNewOpenAPIV3Schema() + .withType("object") + .withProperties(Map.of("spec", new JSONSchemaPropsBuilder().withType("object") + .withProperties(Map.of("foo", new JSONSchemaPropsBuilder().withType("string").build())).build())) + .endOpenAPIV3Schema() + .endSchema() + .build()) + .withScope(KubernetesCRResource.CRD_SCOPE) + .withNewNames() + .withSingular(KubernetesCRResource.CRD_NAME) + .withPlural(KubernetesCRResource.CRD_PLURAL) + .withKind(KubernetesCRResource.CRD_NAME) + .endNames() + .endSpec() + .build(); + + @BeforeEach + public void loadCRD() { + if (mockServer == null) { + try (KubernetesClient client = new KubernetesClientBuilder().build()) { + client.apiextensions().v1().customResourceDefinitions().resource(crd).serverSideApply(); + } + } + } + + @AfterEach + public void removeCRD() { + if (mockServer == null) { + try (KubernetesClient client = new KubernetesClientBuilder().build()) { + client.apiextensions().v1().customResourceDefinitions().resource(crd).delete(); + + } + } + } + + @Test + void customResourceOperations() throws Exception { + + try (CamelKubernetesNamespace namespace = new CamelKubernetesNamespace()) { + namespace.awaitCreation(); + + ObjectMapper mapper = new ObjectMapper(); + String name = "camel-cr"; + Map<String, Object> instance = Map.of( + "apiVersion", KubernetesCRResource.CRD_GROUP + "/" + KubernetesCRResource.CRD_VERSION, + "kind", KubernetesCRResource.CRD_NAME, + "metadata", Map.of( + "name", name, + "labels", Map.of( + "app", name)), + "spec", new HashMap<>(Map.of( + "foo", "bar"))); + String json = mapper.writeValueAsString(instance); + // Create + RestAssured.given() + .contentType(ContentType.JSON) + .body(json) + .when() + .post("/kubernetes/customresource/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace.getNamespace()), + "spec.foo", is(((Map) instance.get("spec")).get("foo"))); + + // Read + RestAssured.given() + .when() + .get("/kubernetes/customresource/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(200) + .body("spec.foo", is(((Map) instance.get("spec")).get("foo"))); + + // Update + ((Map<String, Object>) instance.get("spec")).put("foo", "baz"); + json = mapper.writeValueAsString(instance); + RestAssured.given() + .contentType(ContentType.JSON) + .body(json) + .when() + .put("/kubernetes/customresource/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(200) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace.getNamespace()), + "spec.foo", is(((Map) instance.get("spec")).get("foo"))); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)) + .untilAsserted(() -> RestAssured.given() + .when() + .get("/kubernetes/customresource/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(200) + .body("spec.foo", is(((Map) instance.get("spec")).get("foo")))); + + // List + RestAssured.given() + .when() + .get("/kubernetes/customresource/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("[0].metadata.name", is(name), + "[0].metadata.namespace", is(namespace.getNamespace()), + "[0].spec.foo", is(((Map) instance.get("spec")).get("foo"))); + + // List by labels + RestAssured.given() + .contentType(ContentType.JSON) + .body(Map.of("app", name)) + .when() + .get("/kubernetes/customresource/labels/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("[0].metadata.name", is(name), + "[0].metadata.namespace", is(namespace.getNamespace()), + "[0].spec.foo", is(((Map) instance.get("spec")).get("foo"))); + + // Delete + RestAssured.given() + .when() + .delete("/kubernetes/customresource/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(204); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)).untilAsserted(() -> { + RestAssured.given() + .contentType(ContentType.JSON) + .body(Map.of("app", name)) + .when() + .get("/kubernetes/customresource/labels/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("size()", is(0)); + }); + } + } +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentIT.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentIT.java new file mode 100644 index 0000000000..71ebfbf846 --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentIT.java @@ -0,0 +1,24 @@ +/* + * 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; + +@QuarkusIntegrationTest +class KubernetesDeploymentIT extends KubernetesDeploymentTest { + +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentTest.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentTest.java new file mode 100644 index 0000000000..1ff3f9bfea --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesDeploymentTest.java @@ -0,0 +1,201 @@ +/* + * 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.List; +import java.util.Map; + +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; +import io.fabric8.kubernetes.api.model.apps.DeploymentListBuilder; +import io.fabric8.kubernetes.client.server.mock.KubernetesServer; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.kubernetes.client.KubernetesTestServer; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.is; + +@QuarkusTest +@QuarkusTestResource(CamelQuarkusKubernetesServerTestResource.class) +class KubernetesDeploymentTest { + + @KubernetesTestServer + private KubernetesServer mockServer; + + @Test + void deploymentOperations() throws Exception { + try (CamelKubernetesNamespace namespace = new CamelKubernetesNamespace()) { + namespace.awaitCreation(); + + String name = "camel-deployment"; + + Deployment deployment = new DeploymentBuilder() + .withNewMetadata() + .withName(name) + .endMetadata() + .withNewSpec() + .withReplicas(0) + .withNewSelector() + .addToMatchLabels("app", name) + .endSelector() + .withNewTemplate() + .withNewMetadata() + .addToLabels("app", name) + .endMetadata() + .withNewSpec() + .addNewContainer() + .withName(name) + .withImage("busybox:latest") + .withRestartPolicy("Never") + .endContainer() + .endSpec() + .endTemplate() + .endSpec() + .build(); + + // Create + RestAssured.given() + .contentType(ContentType.JSON) + .body(deployment) + .when() + .post("/kubernetes/deployment/" + namespace.getNamespace()) + .then() + .statusCode(201) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace.getNamespace())); + + // TODO: Remove the if block when 23b7f03878faf906e81932e2c92fd3dceef666a6 is present in camel + // https://github.com/apache/camel-quarkus/issues/7011 + if (false) { + // Read + Deployment currentDeployment = RestAssured.given() + .when() + .get("/kubernetes/deployment/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(200) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace.getNamespace())) + .extract() + .as(Deployment.class); + + // Update + int value = 120; + Deployment updatedDeployment = new DeploymentBuilder(currentDeployment) + .editSpec() + .withMinReadySeconds(value) + .endSpec() + .build(); + + RestAssured.given() + .contentType(ContentType.JSON) + .body(updatedDeployment) + .when() + .put("/kubernetes/deployment/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("spec.minReadySeconds", is(value)); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)).untilAsserted(() -> { + RestAssured.given() + .when() + .get("/kubernetes/deployment/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(200) + .body("spec.minReadySeconds", is(value)); + }); + + String listNamespace = mockServer == null ? namespace.getNamespace() : "test"; + if (mockServer != null) { + mockServer.expect() + .get() + .withPath("/apis/apps/v1/namespaces/" + listNamespace + "/deployments") + .andReturn(200, new DeploymentListBuilder().addAllToItems(List.of(updatedDeployment)).build()) + .once(); + + mockServer.expect() + .get() + .withPath("/apis/apps/v1/namespaces/" + listNamespace + "/deployments?labelSelector=app%3D" + name) + .andReturn(200, new DeploymentListBuilder().addAllToItems(List.of(updatedDeployment)).build()) + .once(); + } + + // List + RestAssured.given() + .when() + .get("/kubernetes/deployment/" + listNamespace) + .then() + .statusCode(200) + .body("[0].metadata.name", is(name), + "[0].metadata.namespace", is(namespace.getNamespace())); + + // List by labels + RestAssured.given() + .contentType(ContentType.JSON) + .body(Map.of("app", name)) + .when() + .get("/kubernetes/deployment/labels/" + listNamespace) + .then() + .statusCode(200) + .body("[0].metadata.name", is(name), + "[0].metadata.namespace", is(namespace.getNamespace())); + + // Scale + // Requires a real k8s server, as the scale operation returns deployment.getStatus().getReplicas() that is not available in the mock server + if (mockServer == null) { + int scaleReplicas = 1; + RestAssured.given() + .when() + .post("/kubernetes/deployment/" + namespace.getNamespace() + "/" + name + "/" + scaleReplicas) + .then() + .statusCode(201); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)).untilAsserted(() -> { + RestAssured.given() + .when() + .get("/kubernetes/deployment/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(200) + .body("spec.replicas", is(scaleReplicas)); + }); + } + } + + // Delete + RestAssured.given() + .when() + .delete("/kubernetes/deployment/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(204); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)).untilAsserted(() -> { + RestAssured.given() + .contentType(ContentType.JSON) + .body(Map.of("app", name)) + .when() + .get("/kubernetes/deployment/labels/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("size()", is(0)); + }); + } + } +} diff --git a/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretIT.java b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretIT.java new file mode 100644 index 0000000000..1e05c3c4ce --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretIT.java @@ -0,0 +1,24 @@ +/* + * 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; + +@QuarkusIntegrationTest +class KubernetesSecretIT extends KubernetesSecretTest { + +} 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 new file mode 100644 index 0000000000..ca83d83cd7 --- /dev/null +++ b/integration-tests/kubernetes/src/test/java/org/apache/camel/quarkus/component/kubernetes/it/KubernetesSecretTest.java @@ -0,0 +1,144 @@ +/* + * 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.fabric8.kubernetes.client.server.mock.KubernetesServer; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.junit.QuarkusTest; +import io.quarkus.test.kubernetes.client.KubernetesTestServer; +import io.restassured.RestAssured; +import io.restassured.http.ContentType; +import org.awaitility.Awaitility; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.is; + +@QuarkusTest +@QuarkusTestResource(CamelQuarkusKubernetesServerTestResource.class) +class KubernetesSecretTest { + + @KubernetesTestServer + private KubernetesServer mockServer; + + @Test + void secretOperations() throws Exception { + try (CamelKubernetesNamespace namespace = new CamelKubernetesNamespace()) { + namespace.awaitCreation(); + + String name = "camel-secret"; + // base64 of "secretValue" - the mockServer does not work process .withStringData into base64 + Map<String, String> data = Map.of("secretKey", "c2VjcmV0VmFsdWUK"); + + Secret secret = new SecretBuilder() + .withNewMetadata() + .withLabels(Map.of("app", name)) + .withName(name) + .endMetadata().withData(data) + .build(); + + // Create + RestAssured.given() + .contentType(ContentType.JSON) + .body(secret) + .when() + .post("/kubernetes/secret/" + namespace.getNamespace()) + .then() + .statusCode(201) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace.getNamespace()), + "metadata.annotations.app", is(name), + "data.secretKey", is(data.get("secretKey"))); + + // Read + RestAssured.given() + .when() + .get("/kubernetes/secret/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(200) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace.getNamespace()), + "data.secretKey", is(data.get("secretKey"))); + + // Update + Map<String, String> newData = Map.of("newSecretKey", "bmV3U2VjcmV0VmFsdWUK"); + secret.setData(newData); + RestAssured.given() + .contentType(ContentType.JSON) + .body(secret) + .when() + .put("/kubernetes/secret/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("metadata.name", is(name), + "data.newSecretKey", is(newData.get("newSecretKey"))); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)) + .untilAsserted(() -> RestAssured.given() + .when() + .get("/kubernetes/secret/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(200) + .body("metadata.name", is(name), + "metadata.namespace", is(namespace.getNamespace()), + "data.newSecretKey", is(newData.get("newSecretKey")))); + + // List + RestAssured.given() + .when() + .get("/kubernetes/secret/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("[0].metadata.name", is(name), + "[0].metadata.namespace", is(namespace.getNamespace()), + "[0].data.newSecretKey", is(newData.get("newSecretKey"))); + + // List by labels + RestAssured.given() + .contentType(ContentType.JSON) + .body(Map.of("app", name)) + .when() + .get("/kubernetes/secret/labels/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("[0].metadata.name", is(name), + "[0].metadata.namespace", is(namespace.getNamespace()), + "[0].data.newSecretKey", is(newData.get("newSecretKey"))); + + // Delete + RestAssured.given() + .when() + .delete("/kubernetes/secret/" + namespace.getNamespace() + "/" + name) + .then() + .statusCode(204); + + Awaitility.await().pollInterval(Duration.ofMillis(250)).atMost(Duration.ofMinutes(1)) + .untilAsserted(() -> RestAssured.given() + .contentType(ContentType.JSON) + .body(Map.of("app", name)) + .when() + .get("/kubernetes/secret/labels/" + namespace.getNamespace()) + .then() + .statusCode(200) + .body("size()", is(0))); + } + } +}