From 340fded5e6948b62849f764f33366dbccb5e91f3 Mon Sep 17 00:00:00 2001 From: Christoph Deppisch Date: Wed, 9 Oct 2024 15:40:12 +0200 Subject: [PATCH] chore: Add Testcontainers connector module - Provide test actions to interact with Testcontainers - Add AWS2 LocalStack container support - Add PostgreSQL container support - Add XML DSL support - Add YAML DSL support --- catalog/citrus-bom/pom.xml | 10 + .../src/test/resources/citrus-context.xml | 3 +- .../kubernetes/KubernetesActor.java | 2 + .../kubernetes/KubernetesSupport.java | 8 + .../kubernetes/KubernetesVariableNames.java | 1 + .../actions/ClusterConnectAction.java | 121 ---- .../actions/KubernetesConnectAction.java | 82 +++ .../functions/ServiceClusterIpFunction.java | 5 + connectors/citrus-testcontainers/pom.xml | 102 +++ .../TestContainersSettings.java | 121 ++++ .../testcontainers/TestcontainersActor.java | 87 +++ .../testcontainers/TestcontainersHelper.java | 28 + .../actions/AbstractTestcontainersAction.java | 59 ++ .../actions/StartTestcontainersAction.java | 224 +++++++ .../actions/StopTestcontainersAction.java | 69 ++ .../actions/TestcontainersAction.java | 27 + .../actions/TestcontainersActionBuilder.java | 203 ++++++ .../aws2/LocalStackContainer.java | 215 +++++++ .../aws2/LocalStackSettings.java | 170 +++++ .../aws2/StartLocalStackAction.java | 116 ++++ .../testcontainers/kafka/KafkaSettings.java | 142 ++++ .../kafka/StartKafkaAction.java | 91 +++ .../mongodb/MongoDBSettings.java | 129 ++++ .../mongodb/StartMongoDBAction.java | 94 +++ .../postgresql/PostgreSQLSettings.java | 167 +++++ .../postgresql/StartPostgreSQLAction.java | 192 ++++++ .../redpanda/RedpandaSettings.java | 142 ++++ .../redpanda/StartRedpandaAction.java | 101 +++ .../testcontainers/xml/ObjectFactory.java | 52 ++ .../testcontainers/xml/Start.java | 606 ++++++++++++++++++ .../testcontainers/xml/Stop.java | 58 ++ .../testcontainers/xml/Testcontainers.java | 94 +++ .../testcontainers/xml/package-info.java | 18 + .../testcontainers/yaml/Start.java | 465 ++++++++++++++ .../testcontainers/yaml/Stop.java | 54 ++ .../testcontainers/yaml/Testcontainers.java | 86 +++ .../src/main/resources/META-INF/LICENSE.txt | 201 ++++++ .../src/main/resources/META-INF/NOTICE.txt | 32 + .../citrus/xml/builder/testcontainers | 2 + .../citrus/yaml/builder/testcontainers | 1 + .../integration/AbstractTestcontainersIT.java | 59 ++ .../StartGenericTestcontainersIT.java | 89 +++ .../StopGenericTestcontainersIT.java | 46 ++ .../aws2/StartLocalStackContainerIT.java | 62 ++ .../kafka/StartKafkaContainerIT.java | 60 ++ .../mongodb/StartMongoDBContainerIT.java | 60 ++ .../StartPostgreSQLContainerIT.java | 61 ++ .../redpanda/StartRedpandaContainerIT.java | 60 ++ .../xml/AbstractXmlActionTest.java | 89 +++ .../integration/xml/StartContainerTest.java | 75 +++ .../integration/xml/StartKafkaTest.java | 61 ++ .../integration/xml/StartLocalStackTest.java | 61 ++ .../integration/xml/StartMongoDBTest.java | 61 ++ .../integration/xml/StartPostgreSQLTest.java | 61 ++ .../integration/xml/StartRedpandaTest.java | 61 ++ .../integration/xml/StopContainerTest.java | 53 ++ .../integration/xml/TestcontainersTest.java | 31 + .../yaml/AbstractYamlActionTest.java | 102 +++ .../integration/yaml/StartContainerTest.java | 75 +++ .../integration/yaml/StartKafkaTest.java | 61 ++ .../integration/yaml/StartLocalStackTest.java | 61 ++ .../integration/yaml/StartMongoDBTest.java | 61 ++ .../integration/yaml/StartPostgreSQLTest.java | 61 ++ .../integration/yaml/StartRedpandaTest.java | 61 ++ .../integration/yaml/StopContainerTest.java | 53 ++ .../integration/yaml/TestcontainersTest.java | 31 + .../context/citrus-unit-context.xml | 7 + .../xml/start-container-test.xml | 28 + .../testcontainers/xml/start-kafka-test.xml | 35 + .../xml/start-localstack-test.xml | 28 + .../testcontainers/xml/start-mongodb-test.xml | 35 + .../xml/start-postgresql-test.xml | 30 + .../xml/start-redpanda-test.xml | 35 + .../xml/stop-container-test.xml | 26 + .../yaml/start-container-test.yaml | 11 + .../testcontainers/yaml/start-kafka-test.yaml | 14 + .../yaml/start-localstack-test.yaml | 10 + .../yaml/start-mongodb-test.yaml | 14 + .../yaml/start-postgresql-test.yaml | 11 + .../yaml/start-redpanda-test.yaml | 14 + .../yaml/stop-container-test.yaml | 8 + connectors/pom.xml | 1 + .../KafkaEndpointIT_selectiveMessage.xml | 10 +- .../KafkaEndpointIT_singleMessage.xml | 10 +- pom.xml | 60 +- .../citrus-testcase-4.4.0-SNAPSHOT.xsd | 265 ++++++++ .../schema/xml/testcase/citrus-testcase.xsd | 265 ++++++++ src/main/assembly/dist-antlibs.xml | 2 + src/main/assembly/dist-release.xml | 16 + src/main/assembly/dist-sources.xml | 14 + 90 files changed, 6806 insertions(+), 139 deletions(-) delete mode 100644 connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/actions/ClusterConnectAction.java create mode 100644 connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/actions/KubernetesConnectAction.java create mode 100644 connectors/citrus-testcontainers/pom.xml create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestContainersSettings.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestcontainersActor.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestcontainersHelper.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/AbstractTestcontainersAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StartTestcontainersAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StopTestcontainersAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersActionBuilder.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/LocalStackContainer.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/LocalStackSettings.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/StartLocalStackAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/kafka/KafkaSettings.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/kafka/StartKafkaAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/mongodb/MongoDBSettings.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/mongodb/StartMongoDBAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/postgresql/PostgreSQLSettings.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/postgresql/StartPostgreSQLAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/redpanda/RedpandaSettings.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/redpanda/StartRedpandaAction.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/ObjectFactory.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Start.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Stop.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Testcontainers.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/package-info.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/yaml/Start.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/yaml/Stop.java create mode 100644 connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/yaml/Testcontainers.java create mode 100644 connectors/citrus-testcontainers/src/main/resources/META-INF/LICENSE.txt create mode 100644 connectors/citrus-testcontainers/src/main/resources/META-INF/NOTICE.txt create mode 100644 connectors/citrus-testcontainers/src/main/resources/META-INF/citrus/xml/builder/testcontainers create mode 100644 connectors/citrus-testcontainers/src/main/resources/META-INF/citrus/yaml/builder/testcontainers create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/AbstractTestcontainersIT.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/StartGenericTestcontainersIT.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/StopGenericTestcontainersIT.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/aws2/StartLocalStackContainerIT.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/kafka/StartKafkaContainerIT.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/mongodb/StartMongoDBContainerIT.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/postgresql/StartPostgreSQLContainerIT.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/redpanda/StartRedpandaContainerIT.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/AbstractXmlActionTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/StartContainerTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/StartKafkaTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/StartLocalStackTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/StartMongoDBTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/StartPostgreSQLTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/StartRedpandaTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/StopContainerTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/xml/TestcontainersTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/AbstractYamlActionTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/StartContainerTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/StartKafkaTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/StartLocalStackTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/StartMongoDBTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/StartPostgreSQLTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/StartRedpandaTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/StopContainerTest.java create mode 100644 connectors/citrus-testcontainers/src/test/java/org/citrusframework/testcontainers/integration/yaml/TestcontainersTest.java create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/context/citrus-unit-context.xml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/xml/start-container-test.xml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/xml/start-kafka-test.xml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/xml/start-localstack-test.xml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/xml/start-mongodb-test.xml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/xml/start-postgresql-test.xml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/xml/start-redpanda-test.xml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/xml/stop-container-test.xml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/yaml/start-container-test.yaml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/yaml/start-kafka-test.yaml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/yaml/start-localstack-test.yaml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/yaml/start-mongodb-test.yaml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/yaml/start-postgresql-test.yaml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/yaml/start-redpanda-test.yaml create mode 100644 connectors/citrus-testcontainers/src/test/resources/org/citrusframework/testcontainers/yaml/stop-container-test.yaml diff --git a/catalog/citrus-bom/pom.xml b/catalog/citrus-bom/pom.xml index 0e0aa4a2f4..d706b76052 100644 --- a/catalog/citrus-bom/pom.xml +++ b/catalog/citrus-bom/pom.xml @@ -342,6 +342,16 @@ citrus-kubernetes 4.4.0-SNAPSHOT + + org.citrusframework + citrus-knative + 4.4.0-SNAPSHOT + + + org.citrusframework + citrus-testcontainers + 4.4.0-SNAPSHOT + org.citrusframework citrus-sql diff --git a/connectors/citrus-knative/src/test/resources/citrus-context.xml b/connectors/citrus-knative/src/test/resources/citrus-context.xml index 39d8914a89..68a5ffa7b1 100644 --- a/connectors/citrus-knative/src/test/resources/citrus-context.xml +++ b/connectors/citrus-knative/src/test/resources/citrus-context.xml @@ -6,8 +6,7 @@ xmlns:citrus-k8s="http://www.citrusframework.org/schema/kubernetes/config" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd - http://www.citrusframework.org/schema/config http://www.citrusframework.org/schema/config/citrus-config.xsd - http://www.citrusframework.org/schema/kubernetes/config http://www.citrusframework.org/schema/kubernetes/config/citrus-kubernetes-config.xsd"> + http://www.citrusframework.org/schema/config http://www.citrusframework.org/schema/config/citrus-config.xsd"> diff --git a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesActor.java b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesActor.java index f885769ed8..2081142387 100644 --- a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesActor.java +++ b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesActor.java @@ -66,6 +66,8 @@ public boolean isDisabled() { logger.warn("Skipping Kubernetes action as no proper Kubernetes environment is available on host system!", e); connected = new AtomicBoolean(false); } + } else { + return false; } } diff --git a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesSupport.java b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesSupport.java index 5c5f7fcf08..b8b61fd67c 100644 --- a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesSupport.java +++ b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesSupport.java @@ -119,6 +119,14 @@ public static String getNamespace(TestContext context) { return KubernetesSettings.getNamespace(); } + public static boolean isConnected(TestContext context) { + if (context.getVariables().containsKey(KubernetesVariableNames.CONNECTED.value())) { + return context.getVariable(KubernetesVariableNames.CONNECTED.value(), Boolean.class); + } + + return false; + } + public static Yaml yaml() { Representer representer = new Representer(new DumperOptions()) { @Override diff --git a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesVariableNames.java b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesVariableNames.java index 4c5eb82935..a243deba52 100644 --- a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesVariableNames.java +++ b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/KubernetesVariableNames.java @@ -18,6 +18,7 @@ public enum KubernetesVariableNames { + CONNECTED("KUBERNETES_CONNECTED"), NAMESPACE("KUBERNETES_NAMESPACE"), SERVICE_CLUSTER_IP("KUBERNETES_SERVICE_CLUSTER_IP"); diff --git a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/actions/ClusterConnectAction.java b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/actions/ClusterConnectAction.java deleted file mode 100644 index 53856be404..0000000000 --- a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/actions/ClusterConnectAction.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright the original author or authors. - * - * Licensed 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.citrusframework.kubernetes.actions; - -import java.util.HashMap; -import java.util.Map; - -import io.fabric8.kubernetes.api.model.ContainerBuilder; -import io.fabric8.kubernetes.api.model.apps.Deployment; -import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder; -import io.fabric8.kubernetes.client.dsl.Updatable; -import org.citrusframework.CitrusSettings; -import org.citrusframework.context.TestContext; -import org.citrusframework.kubernetes.KubernetesSettings; -import org.citrusframework.kubernetes.KubernetesSupport; - -/** - * Connects to a Kubernetes cluster. - * The action starts an agent Pod that is responsible for connecting to services on the cluster. - */ -public class ClusterConnectAction extends AbstractKubernetesAction implements KubernetesAction { - - private final String containerImage; - private final Map annotations; - private final Map labels; - - public ClusterConnectAction(Builder builder) { - super("kubernetes-connect", builder); - - this.containerImage = builder.containerImage; - this.labels = builder.labels; - this.annotations = builder.annotations; - } - - @Override - public void doExecute(TestContext context) { - Map resolvedAnnotations = context.resolveDynamicValuesInMap(annotations); - Map resolvedLabels = context.resolveDynamicValuesInMap(labels); - - if (!labels.containsKey(KubernetesSettings.getTestIdLabel())) { - labels.put(KubernetesSettings.getTestIdLabel(), context.getVariable(CitrusSettings.TEST_NAME_VARIABLE)); - } - - String testName = KubernetesSupport.sanitize("citrus-test-" + context.getVariable(CitrusSettings.TEST_NAME_VARIABLE)); - Deployment deployment = new DeploymentBuilder() - .withNewMetadata() - .withName(testName) - .addToAnnotations(resolvedAnnotations) - .addToLabels(resolvedLabels) - .endMetadata() - .withNewSpec() - .withNewTemplate() - .withNewSpec() - .addToContainers(new ContainerBuilder() - .withImage(containerImage) - .build()) - .endSpec() - .endTemplate() - .endSpec() - .build(); - - getKubernetesClient().apps().deployments() - .inNamespace(namespace(context)) - .resource(deployment) - .createOr(Updatable::update); - } - - /** - * Action builder. - */ - public static class Builder extends AbstractKubernetesAction.Builder { - - private String containerImage; - private final Map annotations = new HashMap<>(); - private final Map labels = new HashMap<>(); - - public Builder image(String containerImage) { - this.containerImage = containerImage; - return this; - } - - public Builder annotations(Mapannotations) { - this.annotations.putAll(annotations); - return this; - } - - public Builder annotation(String annotation, String value) { - this.annotations.put(annotation, value); - return this; - } - - public Builder labels(Maplabels) { - this.labels.putAll(labels); - return this; - } - - public Builder label(String label, String value) { - this.labels.put(label, value); - return this; - } - - @Override - public ClusterConnectAction doBuild() { - return new ClusterConnectAction(this); - } - } -} diff --git a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/actions/KubernetesConnectAction.java b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/actions/KubernetesConnectAction.java new file mode 100644 index 0000000000..6fd376fe4e --- /dev/null +++ b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/actions/KubernetesConnectAction.java @@ -0,0 +1,82 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.kubernetes.actions; + +import java.util.HashMap; +import java.util.Map; + +import org.citrusframework.context.TestContext; +import org.citrusframework.kubernetes.KubernetesVariableNames; + +/** + * Connects to a Kubernetes cluster. + */ +public class KubernetesConnectAction extends AbstractKubernetesAction implements KubernetesAction { + + public KubernetesConnectAction(Builder builder) { + super("kubernetes-connect", builder); + } + + @Override + public void doExecute(TestContext context) { + if (isDisabled(context)) { + return; + } + + context.setVariable(KubernetesVariableNames.CONNECTED.value(), true); + } + + /** + * Action builder. + */ + public static class Builder extends AbstractKubernetesAction.Builder { + + private String containerImage; + private final Map annotations = new HashMap<>(); + private final Map labels = new HashMap<>(); + + public Builder image(String containerImage) { + this.containerImage = containerImage; + return this; + } + + public Builder annotations(Mapannotations) { + this.annotations.putAll(annotations); + return this; + } + + public Builder annotation(String annotation, String value) { + this.annotations.put(annotation, value); + return this; + } + + public Builder labels(Maplabels) { + this.labels.putAll(labels); + return this; + } + + public Builder label(String label, String value) { + this.labels.put(label, value); + return this; + } + + @Override + public KubernetesConnectAction doBuild() { + return new KubernetesConnectAction(this); + } + } +} diff --git a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/functions/ServiceClusterIpFunction.java b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/functions/ServiceClusterIpFunction.java index ed5b7b78d4..bff5112e70 100644 --- a/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/functions/ServiceClusterIpFunction.java +++ b/connectors/citrus-kubernetes/src/main/java/org/citrusframework/kubernetes/functions/ServiceClusterIpFunction.java @@ -24,12 +24,17 @@ import org.citrusframework.exceptions.CitrusRuntimeException; import org.citrusframework.exceptions.InvalidFunctionUsageException; import org.citrusframework.functions.Function; +import org.citrusframework.kubernetes.KubernetesSettings; import org.citrusframework.kubernetes.KubernetesSupport; public class ServiceClusterIpFunction implements Function { @Override public String execute(List parameterList, TestContext context) { + if (KubernetesSettings.isLocal()) { + return "127.0.0.1"; + } + if (parameterList.isEmpty()) { throw new InvalidFunctionUsageException("Function parameters must not be empty - please provide a proper service name"); } diff --git a/connectors/citrus-testcontainers/pom.xml b/connectors/citrus-testcontainers/pom.xml new file mode 100644 index 0000000000..6c822784b7 --- /dev/null +++ b/connectors/citrus-testcontainers/pom.xml @@ -0,0 +1,102 @@ + + 4.0.0 + + + org.citrusframework + citrus-connectors + 4.4.0-SNAPSHOT + ../pom.xml + + + citrus-testcontainers + Citrus :: Connectors :: Testcontainers + + + + org.citrusframework + citrus-base + ${project.version} + + + org.citrusframework + citrus-kubernetes + ${project.version} + + + + org.testcontainers + testcontainers + + + com.github.docker-java + docker-java-transport-okhttp + + + + org.testcontainers + mongodb + + + org.testcontainers + postgresql + + + org.testcontainers + redpanda + + + org.testcontainers + kafka + + + + org.apache.commons + commons-dbcp2 + + + + + software.amazon.awssdk + auth + + + + + org.citrusframework + citrus-test-support + ${project.version} + test + + + org.citrusframework + citrus-testng + ${project.version} + test + + + org.citrusframework + citrus-spring + ${project.version} + test + + + org.citrusframework + citrus-xml + ${project.version} + test + + + org.citrusframework + citrus-yaml + ${project.version} + test + + + org.postgresql + postgresql + test + + + diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestContainersSettings.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestContainersSettings.java new file mode 100644 index 0000000000..ed14beee34 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestContainersSettings.java @@ -0,0 +1,121 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers; + +import java.util.Optional; + +public final class TestContainersSettings { + + public static final String TESTCONTAINERS_VARIABLE_PREFIX = "CITRUS_TESTCONTAINERS_"; + + public static final String TESTCONTAINERS_PROPERTY_PREFIX = "citrus.testcontainers."; + public static final String TESTCONTAINERS_ENV_PREFIX = "CITRUS_TESTCONTAINERS_"; + + private static final String ENABLED_PROPERTY = TESTCONTAINERS_PROPERTY_PREFIX + "enabled"; + private static final String ENABLED_ENV = TESTCONTAINERS_ENV_PREFIX + "ENABLED"; + private static final String ENABLED_DEFAULT = "true"; + + private static final String AUTO_REMOVE_RESOURCES_PROPERTY = TESTCONTAINERS_PROPERTY_PREFIX + "auto.remove.resources"; + private static final String AUTO_REMOVE_RESOURCES_ENV = TESTCONTAINERS_ENV_PREFIX + "AUTO_REMOVE_RESOURCES"; + private static final String AUTO_REMOVE_RESOURCES_DEFAULT = "false"; + + private static final String KUBEDOCK_ENABLED_PROPERTY = TESTCONTAINERS_PROPERTY_PREFIX + "kubedock.enabled"; + private static final String KUBEDOCK_ENABLED_ENV = TESTCONTAINERS_ENV_PREFIX + "KUBEDOCK_ENABLED"; + private static final String KUBEDOCK_ENABLED_DEFAULT = "false"; + + private static final String TEST_ID_PROPERTY = "citrus.test.id"; + private static final String TEST_ID_ENV = "CITRUS_TEST_ID"; + private static final String TEST_ID_DEFAULT = "citrus-test"; + + private static final String TEST_NAME_PROPERTY = "citrus.test.name"; + private static final String TEST_NAME_ENV = "CITRUS_TEST_NAME"; + private static final String TEST_NAME_DEFAULT = "citrus"; + + private static final String STARTUP_TIMEOUT_PROPERTY = TESTCONTAINERS_PROPERTY_PREFIX + "startup.timeout"; + private static final String STARTUP_TIMEOUT_ENV = TESTCONTAINERS_ENV_PREFIX + "STARTUP_TIMEOUT"; + private static final String STARTUP_TIMEOUT_DEFAULT = "180"; + + private static final String CONNECT_TIMEOUT_PROPERTY = TESTCONTAINERS_PROPERTY_PREFIX + "connect.timeout"; + private static final String CONNECT_TIMEOUT_ENV = TESTCONTAINERS_ENV_PREFIX + "CONNECT_TIMEOUT"; + private static final String CONNECT_TIMEOUT_DEFAULT = "5000"; + + private TestContainersSettings() { + // prevent instantiation of utility class + } + + /** + * Kubernetes may be disabled by default. + * @return + */ + public static boolean isEnabled() { + return Boolean.parseBoolean(System.getProperty(ENABLED_PROPERTY, + System.getenv(ENABLED_ENV) != null ? System.getenv(ENABLED_ENV) : ENABLED_DEFAULT)); + } + + /** + * When set to true Kubernetes resources (e.g. services) created during the test are + * automatically removed after the test. + * @return + */ + public static boolean isAutoRemoveResources() { + return Boolean.parseBoolean(System.getProperty(AUTO_REMOVE_RESOURCES_PROPERTY, + System.getenv(AUTO_REMOVE_RESOURCES_ENV) != null ? System.getenv(AUTO_REMOVE_RESOURCES_ENV) : AUTO_REMOVE_RESOURCES_DEFAULT)); + } + + /** + * True when using KubeDock services. + * @return + */ + public static boolean isKubedockEnabled() { + return Boolean.parseBoolean(System.getProperty(KUBEDOCK_ENABLED_PROPERTY, + Optional.ofNullable(System.getenv(KUBEDOCK_ENABLED_ENV)).orElse(KUBEDOCK_ENABLED_DEFAULT))); + } + + /** + * Current test id that is also set as label on the Pod running the test. + * @return + */ + public static String getTestId() { + return System.getProperty(TEST_ID_PROPERTY, Optional.ofNullable(System.getenv(TEST_ID_ENV)).orElse(TEST_ID_DEFAULT)); + } + + /** + * Current test id that is also set as label on the Pod running the test. + * @return + */ + public static String getTestName() { + return System.getProperty(TEST_NAME_PROPERTY, Optional.ofNullable(System.getenv(TEST_NAME_ENV)).orElse(TEST_NAME_DEFAULT)); + } + + /** + * Time in seconds to wait for the container to startup and accept connections. + * @return + */ + public static int getStartupTimeout() { + return Integer.parseInt(System.getProperty(STARTUP_TIMEOUT_PROPERTY, + System.getenv(STARTUP_TIMEOUT_ENV) != null ? System.getenv(STARTUP_TIMEOUT_ENV) : STARTUP_TIMEOUT_DEFAULT)); + } + + /** + * Timeout when connecting to Docker. + * @return + */ + public static long getConnectTimeout() { + return Long.parseLong(System.getProperty(CONNECT_TIMEOUT_PROPERTY, + System.getenv(CONNECT_TIMEOUT_ENV) != null ? System.getenv(CONNECT_TIMEOUT_ENV) : CONNECT_TIMEOUT_DEFAULT)); + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestcontainersActor.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestcontainersActor.java new file mode 100644 index 0000000000..082eb3034f --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestcontainersActor.java @@ -0,0 +1,87 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers; + +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.okhttp.OkDockerHttpClient; +import org.citrusframework.TestActor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.shaded.com.github.dockerjava.core.DefaultDockerClientConfig; +import org.testcontainers.shaded.com.github.dockerjava.core.DockerClientConfig; +import org.testcontainers.shaded.com.github.dockerjava.core.DockerClientImpl; + +/** + * Test actor disabled when running a host where no Docker compatible engine is available. + */ +public class TestcontainersActor extends TestActor { + + /** Logger */ + private static final Logger logger = LoggerFactory.getLogger(TestcontainersActor.class); + + /** Docker's connection state, checks connectivity to Docker engine */ + private static AtomicBoolean connected; + + private final DockerClient dockerClient; + + public TestcontainersActor() { + this(null); + } + + public TestcontainersActor(DockerClient dockerClient) { + setName("testcontainers"); + + if (dockerClient != null) { + this.dockerClient = dockerClient; + } else { + DockerClientConfig clientConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build(); + this.dockerClient = DockerClientImpl.getInstance(clientConfig, + new OkDockerHttpClient.Builder().dockerHost(clientConfig.getDockerHost()).build() + ); + } + } + + @Override + public boolean isDisabled() { + synchronized (logger) { + if (connected == null) { + if (TestContainersSettings.isEnabled()) { + try { + Future future = Executors.newSingleThreadExecutor().submit(() -> { + dockerClient.pingCmd().exec(); + return true; + }); + + connected = new AtomicBoolean((future.get(TestContainersSettings.getConnectTimeout(), TimeUnit.MILLISECONDS))); + } catch (Exception e) { + logger.warn("Skipping Docker test execution as no proper Docker environment is available on host system!", e); + connected = new AtomicBoolean(false); + } + } else { + return false; + } + } + + return !connected.get(); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestcontainersHelper.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestcontainersHelper.java new file mode 100644 index 0000000000..d557b55a4d --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/TestcontainersHelper.java @@ -0,0 +1,28 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers; + +public final class TestcontainersHelper { + + private TestcontainersHelper() { + // prevent instantiation of utility class + } + + public static String getEnvVarName(String containerType, String variable) { + return String.format("%s%s_%s", TestContainersSettings.TESTCONTAINERS_VARIABLE_PREFIX, containerType, variable); + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/AbstractTestcontainersAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/AbstractTestcontainersAction.java new file mode 100644 index 0000000000..846f3db7a0 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/AbstractTestcontainersAction.java @@ -0,0 +1,59 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.actions; + +import org.citrusframework.AbstractTestActionBuilder; +import org.citrusframework.actions.AbstractTestAction; +import org.citrusframework.spi.ReferenceResolver; +import org.citrusframework.spi.ReferenceResolverAware; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractTestcontainersAction extends AbstractTestAction implements TestcontainersAction { + + /** Logger */ + protected final Logger logger = LoggerFactory.getLogger(getClass()); + + public AbstractTestcontainersAction(String name, Builder builder) { + super("testcontainers:" + name, builder); + } + + /** + * Action builder. + */ + public static abstract class Builder> extends AbstractTestActionBuilder implements ReferenceResolverAware { + + protected ReferenceResolver referenceResolver; + + public B withReferenceResolver(ReferenceResolver referenceResolver) { + this.referenceResolver = referenceResolver; + return self; + } + + @Override + public T build() { + return doBuild(); + } + + protected abstract T doBuild(); + + @Override + public void setReferenceResolver(ReferenceResolver referenceResolver) { + this.referenceResolver = referenceResolver; + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StartTestcontainersAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StartTestcontainersAction.java new file mode 100644 index 0000000000..d58b961cd5 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StartTestcontainersAction.java @@ -0,0 +1,224 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.actions; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.citrusframework.context.TestContext; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; + +import static org.citrusframework.testcontainers.TestcontainersHelper.getEnvVarName; +import static org.citrusframework.testcontainers.actions.TestcontainersActionBuilder.testcontainers; + +public class StartTestcontainersAction> extends AbstractTestcontainersAction { + + protected final String serviceName; + protected final String containerName; + private final C container; + private final boolean autoRemoveResources; + + public StartTestcontainersAction(AbstractBuilder, ?> builder) { + super("start", builder); + + this.serviceName = builder.serviceName; + this.containerName = builder.containerName; + this.container = builder.container; + this.autoRemoveResources = builder.autoRemoveResources; + } + + @Override + public void doExecute(TestContext context) { + container.start(); + + if (containerName != null && !context.getReferenceResolver().isResolvable(containerName)) { + context.getReferenceResolver().bind(containerName, container); + } + + exposeConnectionSettings(container, context); + + if (autoRemoveResources) { + context.doFinally(testcontainers() + .stop() + .container(container)); + } + } + + /** + * Sets the connection settings in current test context in the form of test variables. + * @param container + * @param context + */ + protected void exposeConnectionSettings(C container, TestContext context) { + if (container.getContainerId() != null) { + String dockerContainerId = container.getContainerId().substring(0, 12); + String dockerContainerName = container.getContainerName(); + + if (dockerContainerName.startsWith("/")) { + dockerContainerName = dockerContainerName.substring(1); + } + + String containerType = containerName.toUpperCase().replaceAll("-", "_").replaceAll("\\.", "_"); + context.setVariable(getEnvVarName(containerType, "HOST"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_IP"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_ID"), dockerContainerId); + context.setVariable(getEnvVarName(containerType, "CONTAINER_NAME"), dockerContainerName); + } + } + + protected C getContainer() { + return container; + } + + public static class Builder> extends AbstractBuilder, Builder> { + @Override + protected StartTestcontainersAction doBuild() { + return new StartTestcontainersAction<>(this); + } + } + + /** + * Abstract start action builder. + */ + public static abstract class AbstractBuilder, T extends StartTestcontainersAction, B extends AbstractBuilder> extends AbstractTestcontainersAction.Builder { + + protected String image; + protected String containerName; + protected String serviceName; + private final Map labels = new HashMap<>(); + protected final Map env = new HashMap<>(); + private final List commandLine = new ArrayList<>(); + protected C container; + protected Network network; + protected Duration startupTimeout = Duration.ofSeconds(TestContainersSettings.getStartupTimeout()); + private boolean autoRemoveResources = TestContainersSettings.isAutoRemoveResources(); + + public B containerName(String name) { + this.containerName = name; + return self; + } + + public B serviceName(String name) { + this.serviceName = name; + return self; + } + + public B image(String image) { + this.image = image; + return self; + } + + public B container(C container) { + this.container = container; + return self; + } + + public B container(String name, C container) { + this.containerName = name; + this.container = container; + return self; + } + + public B withStartupTimeout(int timeout) { + this.startupTimeout = Duration.ofSeconds(timeout); + return self; + } + + public B withStartupTimeout(Duration timeout) { + this.startupTimeout = timeout; + return self; + } + + public B withNetwork() { + network = Network.newNetwork(); + return self; + } + + public B withNetwork(Network network) { + this.network = network; + return self; + } + + public B withoutNetwork() { + network = null; + return self; + } + + public B withEnv(String key, String value) { + this.env.put(key, value); + return self; + } + + public B withEnv(Map env) { + this.env.putAll(env); + return self; + } + + public B withLabel(String label, String value) { + this.labels.put(label, value); + return self; + } + + public B withLabels(Map labels) { + this.labels.putAll(labels); + return self; + } + + public B withCommand(String... command) { + this.commandLine.addAll(List.of(command)); + return self; + } + + public B autoRemove(boolean enabled) { + this.autoRemoveResources = enabled; + return self; + } + + protected void prepareBuild() { + } + + @Override + public T build() { + prepareBuild(); + + if (container == null) { + container = (C) new GenericContainer<>(image); + + if (network != null) { + container.withNetwork(network); + container.withNetworkAliases(containerName); + } + + container.withStartupTimeout(startupTimeout); + } + + container.withLabels(labels); + container.withEnv(env); + + if (!commandLine.isEmpty()) { + container.withCommand(commandLine.toArray(String[]::new)); + } + + return doBuild(); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StopTestcontainersAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StopTestcontainersAction.java new file mode 100644 index 0000000000..f95bf34485 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/StopTestcontainersAction.java @@ -0,0 +1,69 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.actions; + +import org.citrusframework.context.TestContext; +import org.testcontainers.containers.GenericContainer; + +public class StopTestcontainersAction extends AbstractTestcontainersAction { + + private final String containerName; + private final GenericContainer container; + + public StopTestcontainersAction(Builder builder) { + super("stop", builder); + + this.containerName = builder.containerName; + this.container = builder.container; + } + + @Override + public void doExecute(TestContext context) { + if (container != null) { + container.stop(); + } else if (containerName != null && context.getReferenceResolver().isResolvable(containerName)) { + Object maybeContainer = context.getReferenceResolver().resolve(containerName); + if (maybeContainer instanceof GenericContainer genericContainer) { + genericContainer.stop(); + } + } + } + + /** + * Action builder. + */ + public static class Builder extends AbstractTestcontainersAction.Builder { + + protected String containerName; + private GenericContainer container; + + public Builder containerName(String name) { + this.containerName = name; + return this; + } + + public Builder container(GenericContainer container) { + this.container = container; + return this; + } + + @Override + public StopTestcontainersAction doBuild() { + return new StopTestcontainersAction(this); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersAction.java new file mode 100644 index 0000000000..51afc4293c --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersAction.java @@ -0,0 +1,27 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.actions; + +import org.citrusframework.TestAction; + +/** + * Base action representing interactions with Testcontainers. + */ +public interface TestcontainersAction extends TestAction { + +} + diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersActionBuilder.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersActionBuilder.java new file mode 100644 index 0000000000..269ce9534b --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/actions/TestcontainersActionBuilder.java @@ -0,0 +1,203 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.actions; + +import org.citrusframework.TestActionBuilder; +import org.citrusframework.testcontainers.aws2.StartLocalStackAction; +import org.citrusframework.testcontainers.kafka.StartKafkaAction; +import org.citrusframework.testcontainers.mongodb.StartMongoDBAction; +import org.citrusframework.testcontainers.postgresql.StartPostgreSQLAction; +import org.citrusframework.testcontainers.redpanda.StartRedpandaAction; +import org.citrusframework.util.ObjectHelper; +import org.testcontainers.containers.GenericContainer; + +public class TestcontainersActionBuilder implements TestActionBuilder.DelegatingTestActionBuilder { + + private AbstractTestcontainersAction.Builder delegate; + + /** + * Fluent API action building entry method used in Java DSL. + * @return + */ + public static TestcontainersActionBuilder testcontainers() { + return new TestcontainersActionBuilder(); + } + + /** + * Manage LocalStack testcontainers. + * @return + */ + public LocalStackActionBuilder localstack() { + return new LocalStackActionBuilder(); + } + + /** + * Manage PostgreSQL testcontainers. + * @return + */ + public PostgreSQLActionBuilder postgreSQL() { + return new PostgreSQLActionBuilder(); + } + + /** + * Manage MongoDB testcontainers. + * @return + */ + public MongoDBActionBuilder mongoDB() { + return new MongoDBActionBuilder(); + } + + /** + * Manage Redpanda testcontainers. + * @return + */ + public RedpandaActionBuilder redpanda() { + return new RedpandaActionBuilder(); + } + + /** + * Manage Redpanda testcontainers. + * @return + */ + public KafkaActionBuilder kafka() { + return new KafkaActionBuilder(); + } + + @Override + public TestcontainersAction build() { + ObjectHelper.assertNotNull(delegate, "Missing delegate action to build"); + return delegate.build(); + } + + @Override + public TestActionBuilder getDelegate() { + return delegate; + } + + public StartTestcontainersAction.Builder> start() { + StartTestcontainersAction.Builder> builder = new StartTestcontainersAction.Builder<>(); + delegate = builder; + return builder; + } + + public StopTestcontainersAction.Builder stop() { + StopTestcontainersAction.Builder builder = new StopTestcontainersAction.Builder(); + delegate = builder; + return builder; + } + + public class LocalStackActionBuilder { + /** + * Start LocalStack testcontainers instance. + */ + public StartLocalStackAction.Builder start() { + StartLocalStackAction.Builder builder = new StartLocalStackAction.Builder(); + delegate = builder; + return builder; + } + + /** + * Stop LocalStack testcontainers instance. + */ + public StopTestcontainersAction.Builder stop() { + StopTestcontainersAction.Builder builder = new StopTestcontainersAction.Builder(); + delegate = builder; + return builder; + } + } + + public class PostgreSQLActionBuilder { + /** + * Start PostgreSQL testcontainers instance. + */ + public StartPostgreSQLAction.Builder start() { + StartPostgreSQLAction.Builder builder = new StartPostgreSQLAction.Builder(); + delegate = builder; + return builder; + } + + /** + * Stop PostgreSQL testcontainers instance. + */ + public StopTestcontainersAction.Builder stop() { + StopTestcontainersAction.Builder builder = new StopTestcontainersAction.Builder(); + delegate = builder; + return builder; + } + } + + public class MongoDBActionBuilder { + /** + * Start MongoDB testcontainers instance. + */ + public StartMongoDBAction.Builder start() { + StartMongoDBAction.Builder builder = new StartMongoDBAction.Builder(); + delegate = builder; + return builder; + } + + /** + * Stop MongoDB testcontainers instance. + */ + public StopTestcontainersAction.Builder stop() { + StopTestcontainersAction.Builder builder = new StopTestcontainersAction.Builder(); + delegate = builder; + return builder; + } + } + + public class RedpandaActionBuilder { + /** + * Start Redpanda testcontainers instance. + */ + public StartRedpandaAction.Builder start() { + StartRedpandaAction.Builder builder = new StartRedpandaAction.Builder(); + delegate = builder; + return builder; + } + + /** + * Stop Redpanda testcontainers instance. + */ + public StopTestcontainersAction.Builder stop() { + StopTestcontainersAction.Builder builder = new StopTestcontainersAction.Builder(); + delegate = builder; + return builder; + } + } + + public class KafkaActionBuilder { + /** + * Start Kafka testcontainers instance. + */ + public StartKafkaAction.Builder start() { + StartKafkaAction.Builder builder = new StartKafkaAction.Builder(); + delegate = builder; + return builder; + } + + /** + * Stop Kafka testcontainers instance. + */ + public StopTestcontainersAction.Builder stop() { + StopTestcontainersAction.Builder builder = new StopTestcontainersAction.Builder(); + delegate = builder; + return builder; + } + } + +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/LocalStackContainer.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/LocalStackContainer.java new file mode 100644 index 0000000000..3d0f715831 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/LocalStackContainer.java @@ -0,0 +1,215 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.aws2; + +import java.net.InetAddress; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; + +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentials; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.regions.Region; + +public class LocalStackContainer extends GenericContainer { + + private static final int PORT = 4566; + + private static final String HOSTNAME_EXTERNAL_ENV = "HOSTNAME_EXTERNAL"; + + private static final String DOCKER_IMAGE_NAME = LocalStackSettings.getImageName(); + private static final String DOCKER_IMAGE_TAG = LocalStackSettings.VERSION_DEFAULT; + + private final Set services = new HashSet<>(); + private String secretKey = "secretkey"; + private String accessKey = "accesskey"; + private String region = Region.US_EAST_1.id(); + + public LocalStackContainer() { + this(DOCKER_IMAGE_TAG); + } + + public LocalStackContainer(String version, Service... services) { + this(DOCKER_IMAGE_NAME, version, services); + } + + public LocalStackContainer(String image, String version, Service... services) { + super(DockerImageName.parse(image).withTag(version)); + + withServices(services); + withExposedPorts(PORT); + waitingFor(Wait.forLogMessage(".*Ready\\.\n", 1)); + } + + public LocalStackContainer withServices(Service... services) { + this.services.addAll(Arrays.asList(services)); + return self(); + } + + @Override + protected void configure() { + super.configure(); + + if (services.isEmpty()) { + throw new CitrusRuntimeException("Must provide at least one service"); + } + + withEnv("SERVICE", services.stream().map(Service::serviceName).collect(Collectors.joining(","))); + + String hostnameExternalReason; + if (getEnvMap().containsKey(HOSTNAME_EXTERNAL_ENV)) { + // do nothing + hostnameExternalReason = "explicitly as environment variable"; + } else if (getNetwork() != null && getNetworkAliases().size() >= 1) { + withEnv(HOSTNAME_EXTERNAL_ENV, getNetworkAliases().get(getNetworkAliases().size() - 1)); // use the last network alias set + hostnameExternalReason = "to match last network alias on container with non-default network"; + } else { + withEnv(HOSTNAME_EXTERNAL_ENV, getHost()); + hostnameExternalReason = "to match host-routable address for container"; + } + + logger().info( + "{} environment variable set to {} ({})", + HOSTNAME_EXTERNAL_ENV, + getEnvMap().get(HOSTNAME_EXTERNAL_ENV), + hostnameExternalReason + ); + } + + public LocalStackContainer withSecretKey(String secretKey) { + this.secretKey = secretKey; + return self(); + } + + public LocalStackContainer withAccessKey(String accessKey) { + this.accessKey = accessKey; + return self(); + } + + public LocalStackContainer withRegion(String region) { + this.region = region; + return self(); + } + + /** + * Provides the default access key that is preconfigured on this container. + * @return the default access key for this container. + */ + public String getAccessKey() { + return this.accessKey; + } + + /** + * Provides the secret key that is preconfigured on this container. + * @return the default secret key for this container. + */ + public String getSecretKey() { + return this.secretKey; + } + + /** + * Provides the default region that is preconfigured on this container. + * @return the default region for this container. + */ + public String getRegion() { + return this.region; + } + + public AwsCredentialsProvider getCredentialsProvider() { + return () -> AwsBasicCredentials.create(accessKey, secretKey); + } + + /** + * Provides the connection properties to this container. + * Clients may use these to initialize. + * @return set of connection properties. + */ + public Properties getConnectionProperties() { + Properties properties = new Properties(); + + AwsCredentials credentials = getCredentialsProvider().resolveCredentials(); + + properties.put(LocalStackSettings.ACCESS_KEY_PROPERTY, credentials.accessKeyId()); + properties.put(LocalStackSettings.SECRET_KEY_PROPERTY, credentials.secretAccessKey()); + properties.put(LocalStackSettings.REGION_PROPERTY, Region.US_EAST_1.toString()); + properties.put(LocalStackSettings.HOST_PROPERTY, getHost() + ":" + getMappedPort(PORT)); + properties.put(LocalStackSettings.PROTOCOL_PROPERTY, "http"); + + return properties; + } + + public String getHostIpAddress() { + try { + return InetAddress.getByName(getHost()).getHostAddress(); + } catch (UnknownHostException e) { + logger().warn("Unable to resolve host ip address: {}", e.getMessage()); + return getHost(); + } + } + + public URI getServiceEndpoint() { + try { + return new URI("http://" + getHost() + ":" + getMappedPort(PORT)); + } catch (URISyntaxException e) { + throw new CitrusRuntimeException(String.format("Unable to determine the service endpoint: %s", e.getMessage()), e); + } + } + + public Service[] getServices() { + return services.toArray(Service[]::new); + } + + public enum Service { + CLOUD_WATCH("cloudwatch"), + DYNAMODB("dynamodb"), + EC2("ec2"), + EVENT_BRIDGE("eventbridge"), + IAM("iam"), + KINESIS("kinesis"), + KMS("kms"), + LAMBDA("lambda"), + S3("s3"), + SECRETS_MANAGER("secretsmanager"), + SNS("sns"), + SQS("sqs"), + STS("sts"); + + private final String serviceName; + + Service(String serviceName) { + this.serviceName = serviceName; + } + + public String getServiceName() { + return serviceName; + } + + public static String serviceName(Service service) { + return service.serviceName; + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/LocalStackSettings.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/LocalStackSettings.java new file mode 100644 index 0000000000..17c3e28a12 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/LocalStackSettings.java @@ -0,0 +1,170 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.aws2; + +import java.net.URI; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import org.citrusframework.context.TestContext; +import org.citrusframework.kubernetes.KubernetesSupport; +import org.citrusframework.testcontainers.TestContainersSettings; + +import static org.citrusframework.testcontainers.TestcontainersHelper.getEnvVarName; + +public class LocalStackSettings { + + private static final String LOCALSTACK_PROPERTY_PREFIX = TestContainersSettings.TESTCONTAINERS_PROPERTY_PREFIX + "localstack."; + private static final String LOCALSTACK_ENV_PREFIX = TestContainersSettings.TESTCONTAINERS_ENV_PREFIX + "LOCALSTACK_"; + + private static final String VERSION_PROPERTY = LOCALSTACK_PROPERTY_PREFIX + "version"; + private static final String VERSION_ENV = LOCALSTACK_ENV_PREFIX + "VERSION"; + public static final String VERSION_DEFAULT = "3.8.1"; + + private static final String IMAGE_NAME_PROPERTY = LOCALSTACK_PROPERTY_PREFIX + "image.name"; + private static final String IMAGE_NAME_ENV = LOCALSTACK_ENV_PREFIX + "IMAGE_NAME"; + private static final String IMAGE_NAME_DEFAULT = "localstack/localstack"; + + private static final String SERVICE_NAME_PROPERTY = LOCALSTACK_PROPERTY_PREFIX + "service.name"; + private static final String SERVICE_NAME_ENV = LOCALSTACK_ENV_PREFIX + "SERVICE_NAME"; + public static final String SERVICE_NAME_DEFAULT = "citrus-localstack"; + + private static final String CONTAINER_NAME_PROPERTY = LOCALSTACK_PROPERTY_PREFIX + "container.name"; + private static final String CONTAINER_NAME_ENV = LOCALSTACK_ENV_PREFIX + "CONTAINER_NAME"; + public static final String CONTAINER_NAME_DEFAULT = "aws2Container"; + + private static final String STARTUP_TIMEOUT_PROPERTY = LOCALSTACK_PROPERTY_PREFIX + "startup.timeout"; + private static final String STARTUP_TIMEOUT_ENV = LOCALSTACK_ENV_PREFIX + "STARTUP_TIMEOUT"; + + private static final String AWS_PROPERTY_PREFIX = "aws."; + + public static final String ACCESS_KEY_PROPERTY = AWS_PROPERTY_PREFIX + "access.key"; + public static final String SECRET_KEY_PROPERTY = AWS_PROPERTY_PREFIX + "secret.key"; + public static final String REGION_PROPERTY = AWS_PROPERTY_PREFIX + "region"; + public static final String HOST_PROPERTY = AWS_PROPERTY_PREFIX + "host"; + public static final String PROTOCOL_PROPERTY = AWS_PROPERTY_PREFIX + "protocol"; + + private LocalStackSettings() { + // prevent instantiation of utility class + } + + /** + * LocalStack image name. + * @return + */ + public static String getImageName() { + return System.getProperty(IMAGE_NAME_PROPERTY, + System.getenv(IMAGE_NAME_ENV) != null ? System.getenv(IMAGE_NAME_ENV) : IMAGE_NAME_DEFAULT); + } + + /** + * LocalStack version. + * @return default Docker image version. + */ + public static String getVersion() { + return System.getProperty(VERSION_PROPERTY, + System.getenv(VERSION_ENV) != null ? System.getenv(VERSION_ENV) : VERSION_DEFAULT); + } + + /** + * LocalStack service name. + * @return the service name. + */ + public static String getServiceName() { + return System.getProperty(SERVICE_NAME_PROPERTY, + System.getenv(SERVICE_NAME_ENV) != null ? System.getenv(SERVICE_NAME_ENV) : SERVICE_NAME_DEFAULT); + } + + /** + * LocalStack container name. + * @return the container name. + */ + public static String getContainerName() { + return System.getProperty(CONTAINER_NAME_PROPERTY, + System.getenv(CONTAINER_NAME_ENV) != null ? System.getenv(CONTAINER_NAME_ENV) : CONTAINER_NAME_DEFAULT); + } + + /** + * Time in seconds to wait for the container to startup and accept connections. + * @return + */ + public static int getStartupTimeout() { + return Optional.ofNullable(System.getProperty(STARTUP_TIMEOUT_PROPERTY, System.getenv(STARTUP_TIMEOUT_ENV))) + .map(Integer::parseInt) + .orElseGet(TestContainersSettings::getStartupTimeout); + } + + /** + * Exposes the container connection settings as test variables on the given context. + * @param container the container holding the connection settings. + * @param serviceName the service name of the container. + * @param context the test context to receive the test variables. + */ + public static void exposeConnectionSettings(LocalStackContainer container, String serviceName, TestContext context) { + if (container.getContainerId() != null) { + URI serviceEndpoint = container.getServiceEndpoint(); + + String dockerContainerId = container.getContainerId().substring(0, 12); + String dockerContainerName = container.getContainerName(); + + if (dockerContainerName.startsWith("/")) { + dockerContainerName = dockerContainerName.substring(1); + } + + String containerType = "LOCALSTACK"; + context.setVariable(getEnvVarName(containerType, "HOST"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_IP"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_ID"), dockerContainerId); + context.setVariable(getEnvVarName(containerType, "CONTAINER_NAME"), dockerContainerName); + context.setVariable(getEnvVarName(containerType, "REGION"), container.getRegion()); + context.setVariable(getEnvVarName(containerType, "ACCESS_KEY"), container.getAccessKey()); + context.setVariable(getEnvVarName(containerType, "SECRET_KEY"), container.getSecretKey()); + context.setVariable(getEnvVarName(containerType, "SERVICE_PORT"), serviceEndpoint.getPort()); + context.setVariable(getEnvVarName(containerType, "SERVICE_LOCAL_URL"), String.format("http://localhost:%s", serviceEndpoint.getPort())); + + if (!KubernetesSupport.isConnected(context) || !TestContainersSettings.isKubedockEnabled()) { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_URL"), String.format("http://localhost:%s", serviceEndpoint.getPort())); + } else { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_URL"), String.format("http://%s:%s", serviceName, serviceEndpoint.getPort())); + } + + Stream.of(container.getServices()).forEach(service -> { + String aws2ServiceName = service.getServiceName().toUpperCase(Locale.US); + + if (!KubernetesSupport.isConnected(context) || !TestContainersSettings.isKubedockEnabled()) { + context.setVariable(getEnvVarName(containerType, String.format("%s_URL", aws2ServiceName)), String.format("http://localhost:%s", serviceEndpoint.getPort())); + } else { + context.setVariable(getEnvVarName(containerType, String.format("%s_URL", aws2ServiceName)), String.format("http://%s:%s", serviceName, serviceEndpoint.getPort())); + } + + context.setVariable(getEnvVarName(containerType, String.format("%s_LOCAL_URL", aws2ServiceName)), String.format("http://localhost:%s", serviceEndpoint.getPort())); + context.setVariable(getEnvVarName(containerType, String.format("%s_PORT", aws2ServiceName)), serviceEndpoint.getPort()); + }); + + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_SERVICE_URL"), String.format("http://%s:%s", serviceName, serviceEndpoint.getPort())); + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_HOST"), serviceName); + + for (Map.Entry connectionProperty : container.getConnectionProperties().entrySet()) { + context.setVariable(connectionProperty.getKey().toString(), connectionProperty.getValue().toString()); + } + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/StartLocalStackAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/StartLocalStackAction.java new file mode 100644 index 0000000000..a46d3f3756 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/aws2/StartLocalStackAction.java @@ -0,0 +1,116 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.aws2; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import org.citrusframework.context.TestContext; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.citrusframework.testcontainers.actions.StartTestcontainersAction; +import org.testcontainers.containers.wait.strategy.Wait; + +public class StartLocalStackAction extends StartTestcontainersAction { + + private final Set services; + + public StartLocalStackAction(Builder builder) { + super(builder); + + this.services = builder.services; + } + + @Override + protected void exposeConnectionSettings(LocalStackContainer container, TestContext context) { + LocalStackSettings.exposeConnectionSettings(container, serviceName, context); + } + + /** + * Action builder. + */ + public static class Builder extends AbstractBuilder { + + private String localStackVersion = LocalStackSettings.getVersion(); + + private final Set services = new HashSet<>(); + + public Builder() { + withStartupTimeout(LocalStackSettings.getStartupTimeout()); + } + + public Builder version(String localStackVersion) { + this.localStackVersion = localStackVersion; + return this; + } + + public Builder withService(LocalStackContainer.Service service) { + this.services.add(service); + return this; + } + + public Builder withServices(LocalStackContainer.Service[] services) { + this.services.addAll(Arrays.asList(services)); + return this; + } + + public Builder withServices(Set services) { + this.services.addAll(services); + return this; + } + + @Override + protected void prepareBuild() { + if (containerName == null) { + containerName(LocalStackSettings.getContainerName()); + } + + if (serviceName == null) { + serviceName(LocalStackSettings.getServiceName()); + } + + if (image == null) { + image(LocalStackSettings.getImageName()); + } + + withLabel("app", "citrus"); + withLabel("com.joyrex2001.kubedock.name-prefix", serviceName); + withLabel("app.kubernetes.io/name", "build"); + withLabel("app.kubernetes.io/part-of", TestContainersSettings.getTestName()); + withLabel("app.openshift.io/connects-to", TestContainersSettings.getTestId()); + + LocalStackContainer localStack; + if (referenceResolver != null && referenceResolver.isResolvable(containerName, LocalStackContainer.class)) { + localStack = referenceResolver.resolve(containerName, LocalStackContainer.class); + } else { + localStack = new LocalStackContainer(image, localStackVersion) + .withServices(services.toArray(LocalStackContainer.Service[]::new)) + .withNetwork(network) + .withNetworkAliases(serviceName) + .waitingFor(Wait.forListeningPort() + .withStartupTimeout(startupTimeout)); + } + + container(localStack); + } + + @Override + public StartLocalStackAction doBuild() { + return new StartLocalStackAction(this); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/kafka/KafkaSettings.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/kafka/KafkaSettings.java new file mode 100644 index 0000000000..2421524854 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/kafka/KafkaSettings.java @@ -0,0 +1,142 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.kafka; + +import org.citrusframework.context.TestContext; +import org.citrusframework.kubernetes.KubernetesSupport; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.testcontainers.containers.KafkaContainer; + +import static org.citrusframework.testcontainers.TestcontainersHelper.getEnvVarName; + +public class KafkaSettings { + + private static final String KAFKA_PROPERTY_PREFIX = TestContainersSettings.TESTCONTAINERS_PROPERTY_PREFIX + "kafka."; + private static final String KAFKA_ENV_PREFIX = TestContainersSettings.TESTCONTAINERS_ENV_PREFIX + "KAFKA_"; + + public static final int KAFKA_PORT = KafkaContainer.KAFKA_PORT; + + private static final String VERSION_PROPERTY = KAFKA_PROPERTY_PREFIX + "version"; + private static final String VERSION_ENV = KAFKA_ENV_PREFIX + "VERSION"; + private static final String VERSION_DEFAULT = "7.7.1"; + + private static final String SERVICE_NAME_PROPERTY = KAFKA_PROPERTY_PREFIX + "service.name"; + private static final String SERVICE_NAME_ENV = KAFKA_ENV_PREFIX + "SERVICE_NAME"; + public static final String SERVICE_NAME_DEFAULT = "citrus-kafka"; + + private static final String CONTAINER_NAME_PROPERTY = KAFKA_PROPERTY_PREFIX + "container.name"; + private static final String CONTAINER_NAME_ENV = KAFKA_ENV_PREFIX + "CONTAINER_NAME"; + public static final String CONTAINER_NAME_DEFAULT = "kafkaContainer"; + + private static final String IMAGE_NAME_PROPERTY = KAFKA_PROPERTY_PREFIX + "image.name"; + private static final String IMAGE_NAME_ENV = KAFKA_ENV_PREFIX + "IMAGE_NAME"; + private static final String IMAGE_NAME_DEFAULT = "confluentinc/cp-kafka"; + + private static final String STARTUP_TIMEOUT_PROPERTY = KAFKA_PROPERTY_PREFIX + "startup.timeout"; + private static final String STARTUP_TIMEOUT_ENV = KAFKA_ENV_PREFIX + "STARTUP_TIMEOUT"; + private static final String STARTUP_TIMEOUT_DEFAULT = "180"; + + private KafkaSettings() { + // prevent instantiation of utility class + } + + /** + * Kafka image name. + * @return + */ + public static String getImageName() { + return System.getProperty(IMAGE_NAME_PROPERTY, + System.getenv(IMAGE_NAME_ENV) != null ? System.getenv(IMAGE_NAME_ENV) : IMAGE_NAME_DEFAULT); + } + + /** + * Kafka version setting. + * @return + */ + public static String getKafkaVersion() { + return System.getProperty(VERSION_PROPERTY, + System.getenv(VERSION_ENV) != null ? System.getenv(VERSION_ENV) : VERSION_DEFAULT); + } + + /** + * Kafka service name. + * @return + */ + public static String getServiceName() { + return System.getProperty(SERVICE_NAME_PROPERTY, + System.getenv(SERVICE_NAME_ENV) != null ? System.getenv(SERVICE_NAME_ENV) : SERVICE_NAME_DEFAULT); + } + + /** + * Kafka container name. + * @return + */ + public static String getContainerName() { + return System.getProperty(CONTAINER_NAME_PROPERTY, + System.getenv(CONTAINER_NAME_ENV) != null ? System.getenv(CONTAINER_NAME_ENV) : CONTAINER_NAME_DEFAULT); + } + + /** + * Time in seconds to wait for the container to startup and accept connections. + * @return + */ + public static int getStartupTimeout() { + return Integer.parseInt(System.getProperty(STARTUP_TIMEOUT_PROPERTY, + System.getenv(STARTUP_TIMEOUT_ENV) != null ? System.getenv(STARTUP_TIMEOUT_ENV) : STARTUP_TIMEOUT_DEFAULT)); + } + + /** + * Exposes the container connection settings as test variables on the given context. + * @param container the container holding the connection settings. + * @param serviceName the service name of the container. + * @param context the test context to receive the test variables. + */ + public static void exposeConnectionSettings(KafkaContainer container, String serviceName, TestContext context) { + if (container.getContainerId() != null) { + String dockerContainerId = container.getContainerId().substring(0, 12); + String dockerContainerName = container.getContainerName(); + + if (dockerContainerName.startsWith("/")) { + dockerContainerName = dockerContainerName.substring(1); + } + + String containerType = "KAFKA"; + context.setVariable(getEnvVarName(containerType, "HOST"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_IP"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_ID"), dockerContainerId); + context.setVariable(getEnvVarName(containerType, "CONTAINER_NAME"), dockerContainerName); + + context.setVariable(getEnvVarName(containerType, "LOCAL_BOOTSTRAP_SERVERS"), container.getBootstrapServers()); + context.setVariable(getEnvVarName(containerType, "SERVICE_PORT"), container.getMappedPort(KafkaSettings.KAFKA_PORT)); + context.setVariable(getEnvVarName(containerType, "PORT"), container.getMappedPort(KafkaSettings.KAFKA_PORT)); + context.setVariable(getEnvVarName(containerType, "SERVICE_LOCAL_BOOTSTRAP_SERVERS"), container.getBootstrapServers()); + + if (!KubernetesSupport.isConnected(context) || !TestContainersSettings.isKubedockEnabled()) { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_BOOTSTRAP_SERVERS"), container.getBootstrapServers()); + context.setVariable(getEnvVarName(containerType, "BOOTSTRAP_SERVERS"), container.getBootstrapServers()); + } else { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_BOOTSTRAP_SERVERS"), String.format("%s:%s", serviceName, container.getMappedPort(KafkaSettings.KAFKA_PORT))); + context.setVariable(getEnvVarName(containerType, "BOOTSTRAP_SERVERS"), String.format("%s:%s", serviceName, container.getMappedPort(KafkaSettings.KAFKA_PORT))); + } + + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_SERVICE_BOOTSTRAP_SERVERS"), String.format("%s:%s", serviceName, container.getMappedPort(KafkaSettings.KAFKA_PORT))); + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_HOST"), serviceName); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/kafka/StartKafkaAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/kafka/StartKafkaAction.java new file mode 100644 index 0000000000..cb10baa680 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/kafka/StartKafkaAction.java @@ -0,0 +1,91 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.kafka; + +import org.citrusframework.context.TestContext; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.citrusframework.testcontainers.actions.StartTestcontainersAction; +import org.citrusframework.testcontainers.postgresql.PostgreSQLSettings; +import org.testcontainers.containers.KafkaContainer; +import org.testcontainers.utility.DockerImageName; + +public class StartKafkaAction extends StartTestcontainersAction { + + public StartKafkaAction(Builder builder) { + super(builder); + } + + @Override + protected void exposeConnectionSettings(KafkaContainer container, TestContext context) { + KafkaSettings.exposeConnectionSettings(container, serviceName, context); + } + + /** + * Action builder. + */ + public static class Builder extends AbstractBuilder { + + private String kafkaVersion = KafkaSettings.getKafkaVersion(); + + public Builder() { + withStartupTimeout(PostgreSQLSettings.getStartupTimeout()); + } + + public Builder version(String kafkaVersion) { + this.kafkaVersion = kafkaVersion; + return this; + } + + @Override + protected void prepareBuild() { + if (containerName == null) { + containerName(KafkaSettings.getContainerName()); + } + + if (serviceName == null) { + serviceName(KafkaSettings.getServiceName()); + } + + if (image == null) { + image(KafkaSettings.getImageName()); + } + + withLabel("app", "citrus"); + withLabel("com.joyrex2001.kubedock.name-prefix", serviceName); + withLabel("app.kubernetes.io/name", "kafka"); + withLabel("app.kubernetes.io/part-of", TestContainersSettings.getTestName()); + withLabel("app.openshift.io/connects-to", TestContainersSettings.getTestId()); + + KafkaContainer kafkaContainer; + if (referenceResolver != null && referenceResolver.isResolvable(containerName, KafkaContainer.class)) { + kafkaContainer = referenceResolver.resolve(containerName, KafkaContainer.class); + } else { + kafkaContainer = new KafkaContainer(DockerImageName.parse(image).withTag(kafkaVersion)) + .withNetwork(network) + .withNetworkAliases(serviceName) + .withStartupTimeout(startupTimeout); + } + + container(kafkaContainer); + } + + @Override + public StartKafkaAction doBuild() { + return new StartKafkaAction(this); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/mongodb/MongoDBSettings.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/mongodb/MongoDBSettings.java new file mode 100644 index 0000000000..3a0f87edaa --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/mongodb/MongoDBSettings.java @@ -0,0 +1,129 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.mongodb; + +import org.citrusframework.context.TestContext; +import org.citrusframework.kubernetes.KubernetesSupport; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.testcontainers.containers.MongoDBContainer; + +import static org.citrusframework.testcontainers.TestcontainersHelper.getEnvVarName; + +public class MongoDBSettings { + + private static final String MONGODB_PROPERTY_PREFIX = TestContainersSettings.TESTCONTAINERS_PROPERTY_PREFIX + "mongodb."; + private static final String MONGODB_ENV_PREFIX = TestContainersSettings.TESTCONTAINERS_ENV_PREFIX + "MONGODB_"; + + private static final String VERSION_PROPERTY = MONGODB_PROPERTY_PREFIX + "version"; + private static final String VERSION_ENV = MONGODB_ENV_PREFIX + "VERSION"; + private static final String VERSION_DEFAULT = "4.0.10"; + + private static final String SERVICE_NAME_PROPERTY = MONGODB_PROPERTY_PREFIX + "service.name"; + private static final String SERVICE_NAME_ENV = MONGODB_ENV_PREFIX + "SERVICE_NAME"; + public static final String SERVICE_NAME_DEFAULT = "citrus-mongodb"; + + private static final String CONTAINER_NAME_PROPERTY = MONGODB_PROPERTY_PREFIX + "container.name"; + private static final String CONTAINER_NAME_ENV = MONGODB_ENV_PREFIX + "CONTAINER_NAME"; + public static final String CONTAINER_NAME_DEFAULT = "mongoDBContainer"; + + private static final String STARTUP_TIMEOUT_PROPERTY = MONGODB_PROPERTY_PREFIX + "startup.timeout"; + private static final String STARTUP_TIMEOUT_ENV = MONGODB_ENV_PREFIX + "STARTUP_TIMEOUT"; + private static final String STARTUP_TIMEOUT_DEFAULT = "180"; + + private MongoDBSettings() { + // prevent instantiation of utility class + } + + /** + * MongoDB version setting. + * @return + */ + public static String getMongoDBVersion() { + return System.getProperty(VERSION_PROPERTY, + System.getenv(VERSION_ENV) != null ? System.getenv(VERSION_ENV) : VERSION_DEFAULT); + } + + /** + * MongoDB service name. + * @return + */ + public static String getServiceName() { + return System.getProperty(SERVICE_NAME_PROPERTY, + System.getenv(SERVICE_NAME_ENV) != null ? System.getenv(SERVICE_NAME_ENV) : SERVICE_NAME_DEFAULT); + } + + /** + * MongoDB container name. + * @return + */ + public static String getContainerName() { + return System.getProperty(CONTAINER_NAME_PROPERTY, + System.getenv(CONTAINER_NAME_ENV) != null ? System.getenv(CONTAINER_NAME_ENV) : CONTAINER_NAME_DEFAULT); + } + + /** + * Time in seconds to wait for the container to startup and accept connections. + * @return + */ + public static int getStartupTimeout() { + return Integer.parseInt(System.getProperty(STARTUP_TIMEOUT_PROPERTY, + System.getenv(STARTUP_TIMEOUT_ENV) != null ? System.getenv(STARTUP_TIMEOUT_ENV) : STARTUP_TIMEOUT_DEFAULT)); + } + + /** + * Exposes the container connection settings as test variables on the given context. + * @param container the container holding the connection settings. + * @param serviceName the service name of the container. + * @param context the test context to receive the test variables. + */ + public static void exposeConnectionSettings(MongoDBContainer container, String serviceName, TestContext context) { + if (container.getContainerId() != null) { + String dockerContainerId = container.getContainerId().substring(0, 12); + String dockerContainerName = container.getContainerName(); + + if (dockerContainerName.startsWith("/")) { + dockerContainerName = dockerContainerName.substring(1); + } + + String containerType = "MONGODB"; + context.setVariable(getEnvVarName(containerType, "HOST"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_IP"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_ID"), dockerContainerId); + context.setVariable(getEnvVarName(containerType, "CONTAINER_NAME"), dockerContainerName); + + context.setVariable(getEnvVarName(containerType, "LOCAL_URL"), container.getReplicaSetUrl()); + context.setVariable(getEnvVarName(containerType, "SERVICE_PORT"), container.getMappedPort(27017)); + context.setVariable(getEnvVarName(containerType, "PORT"), container.getMappedPort(27017)); + context.setVariable(getEnvVarName(containerType, "SERVICE_LOCAL_URL"), container.getReplicaSetUrl()); + + if (!KubernetesSupport.isConnected(context) || !TestContainersSettings.isKubedockEnabled()) { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_URL"), container.getReplicaSetUrl()); + context.setVariable(getEnvVarName(containerType, "URL"), container.getReplicaSetUrl()); + } else { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_URL"), String.format("mongodb://%s:%d/test", serviceName, container.getMappedPort(27017))); + context.setVariable(getEnvVarName(containerType, "URL"), String.format("mongodb://%s:%d/test", serviceName, container.getMappedPort(27017))); + } + + context.setVariable(getEnvVarName(containerType, "CONNECTION_STRING"), container.getConnectionString()); + + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_SERVICE_URL"), String.format("mongodb://%s:%d/test", serviceName, container.getMappedPort(27017))); + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_HOST"), serviceName); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/mongodb/StartMongoDBAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/mongodb/StartMongoDBAction.java new file mode 100644 index 0000000000..accc783cd7 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/mongodb/StartMongoDBAction.java @@ -0,0 +1,94 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.mongodb; + +import org.citrusframework.context.TestContext; +import org.citrusframework.kubernetes.KubernetesSupport; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.citrusframework.testcontainers.actions.StartTestcontainersAction; +import org.citrusframework.testcontainers.postgresql.PostgreSQLSettings; +import org.testcontainers.containers.MongoDBContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.DockerImageName; + +public class StartMongoDBAction extends StartTestcontainersAction { + + public StartMongoDBAction(Builder builder) { + super(builder); + } + + @Override + protected void exposeConnectionSettings(MongoDBContainer container, TestContext context) { + MongoDBSettings.exposeConnectionSettings(container, serviceName, context); + } + + /** + * Action builder. + */ + public static class Builder extends AbstractBuilder { + + private String mongoDBVersion = MongoDBSettings.getMongoDBVersion(); + + public Builder() { + withStartupTimeout(PostgreSQLSettings.getStartupTimeout()); + } + + public Builder version(String mongoDBVersion) { + this.mongoDBVersion = mongoDBVersion; + return this; + } + + @Override + protected void prepareBuild() { + if (containerName == null) { + containerName(MongoDBSettings.getContainerName()); + } + + if (serviceName == null) { + serviceName(MongoDBSettings.getServiceName()); + } + + if (image == null) { + image("mongo"); + } + + withLabel("app", "citrus"); + withLabel("com.joyrex2001.kubedock.name-prefix", serviceName); + withLabel("app.kubernetes.io/name", "mongoDB"); + withLabel("app.kubernetes.io/part-of", TestContainersSettings.getTestName()); + withLabel("app.openshift.io/connects-to", TestContainersSettings.getTestId()); + + MongoDBContainer mongoDBContainer; + if (referenceResolver != null && referenceResolver.isResolvable(containerName, MongoDBContainer.class)) { + mongoDBContainer = referenceResolver.resolve(containerName, MongoDBContainer.class); + } else { + mongoDBContainer = new MongoDBContainer(DockerImageName.parse(image).withTag(mongoDBVersion)) + .withNetwork(network) + .withNetworkAliases(serviceName) + .waitingFor(Wait.forLogMessage("(?i).*waiting for connections.*", 1) + .withStartupTimeout(startupTimeout)); + } + + container(mongoDBContainer); + } + + @Override + public StartMongoDBAction doBuild() { + return new StartMongoDBAction(this); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/postgresql/PostgreSQLSettings.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/postgresql/PostgreSQLSettings.java new file mode 100644 index 0000000000..1c042fa062 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/postgresql/PostgreSQLSettings.java @@ -0,0 +1,167 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.postgresql; + +import org.citrusframework.context.TestContext; +import org.citrusframework.kubernetes.KubernetesSupport; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.testcontainers.containers.PostgreSQLContainer; + +import static org.citrusframework.testcontainers.TestcontainersHelper.getEnvVarName; + +public class PostgreSQLSettings { + + private static final String POSTGRESQL_PROPERTY_PREFIX = TestContainersSettings.TESTCONTAINERS_PROPERTY_PREFIX + "postgresql."; + private static final String POSTGRESQL_ENV_PREFIX = TestContainersSettings.TESTCONTAINERS_ENV_PREFIX + "POSTGRESQL_"; + + private static final String POSTGRESQL_VERSION_PROPERTY = POSTGRESQL_PROPERTY_PREFIX + "version"; + private static final String POSTGRESQL_VERSION_ENV = POSTGRESQL_ENV_PREFIX + "POSTGRESQL_VERSION"; + private static final String POSTGRESQL_VERSION_DEFAULT = PostgreSQLContainer.DEFAULT_TAG; + + private static final String SERVICE_NAME_PROPERTY = POSTGRESQL_PROPERTY_PREFIX + "service.name"; + private static final String SERVICE_NAME_ENV = POSTGRESQL_ENV_PREFIX + "SERVICE_NAME"; + public static final String SERVICE_NAME_DEFAULT = "citrus-postgresql"; + + private static final String CONTAINER_NAME_PROPERTY = POSTGRESQL_PROPERTY_PREFIX + "container.name"; + private static final String CONTAINER_NAME_ENV = POSTGRESQL_ENV_PREFIX + "CONTAINER_NAME"; + public static final String CONTAINER_NAME_DEFAULT = "postgreSQLContainer"; + + private static final String DATABASE_NAME_PROPERTY = POSTGRESQL_PROPERTY_PREFIX + "db.name"; + private static final String DATABASE_NAME_ENV = POSTGRESQL_ENV_PREFIX + "DB_NAME"; + private static final String DATABASE_NAME_DEFAULT = "test"; + + private static final String USERNAME_PROPERTY = POSTGRESQL_PROPERTY_PREFIX + "username"; + private static final String USERNAME_ENV = POSTGRESQL_ENV_PREFIX + "USERNAME"; + private static final String USERNAME_DEFAULT = "test"; + + private static final String PASSWORD_PROPERTY = POSTGRESQL_PROPERTY_PREFIX + "password"; + private static final String PASSWORD_ENV = POSTGRESQL_ENV_PREFIX + "PASSWORD"; + private static final String PASSWORD_DEFAULT = "test"; + + private static final String STARTUP_TIMEOUT_PROPERTY = POSTGRESQL_PROPERTY_PREFIX + "startup.timeout"; + private static final String STARTUP_TIMEOUT_ENV = POSTGRESQL_ENV_PREFIX + "STARTUP_TIMEOUT"; + private static final String STARTUP_TIMEOUT_DEFAULT = "180"; + + private PostgreSQLSettings() { + // prevent instantiation of utility class + } + + /** + * PostgreSQL service name. + * @return default service name. + */ + public static String getServiceName() { + return System.getProperty(SERVICE_NAME_PROPERTY, + System.getenv(SERVICE_NAME_ENV) != null ? System.getenv(SERVICE_NAME_ENV) : SERVICE_NAME_DEFAULT); + } + + /** + * PostgreSQL container name. + * @return default container name. + */ + public static String getContainerName() { + return System.getProperty(CONTAINER_NAME_PROPERTY, + System.getenv(CONTAINER_NAME_ENV) != null ? System.getenv(CONTAINER_NAME_ENV) : CONTAINER_NAME_DEFAULT); + } + + /** + * PostgreSQL database name. + * @return default database name. + */ + public static String getDatabaseName() { + return System.getProperty(DATABASE_NAME_PROPERTY, + System.getenv(DATABASE_NAME_ENV) != null ? System.getenv(DATABASE_NAME_ENV) : DATABASE_NAME_DEFAULT); + } + + /** + * PostgreSQL user name. + * @return default user name. + */ + public static String getUsername() { + return System.getProperty(USERNAME_PROPERTY, + System.getenv(USERNAME_ENV) != null ? System.getenv(USERNAME_ENV) : USERNAME_DEFAULT); + } + + /** + * PostgreSQL password. + * @return default password. + */ + public static String getPassword() { + return System.getProperty(PASSWORD_PROPERTY, + System.getenv(PASSWORD_ENV) != null ? System.getenv(PASSWORD_ENV) : PASSWORD_DEFAULT); + } + + /** + * PostgreSQL version setting. + * @return + */ + public static String getPostgreSQLVersion() { + return System.getProperty(POSTGRESQL_VERSION_PROPERTY, + System.getenv(POSTGRESQL_VERSION_ENV) != null ? System.getenv(POSTGRESQL_VERSION_ENV) : POSTGRESQL_VERSION_DEFAULT); + } + + /** + * Time in seconds to wait for the container to startup and accept connections. + * @return + */ + public static int getStartupTimeout() { + return Integer.parseInt(System.getProperty(STARTUP_TIMEOUT_PROPERTY, + System.getenv(STARTUP_TIMEOUT_ENV) != null ? System.getenv(STARTUP_TIMEOUT_ENV) : STARTUP_TIMEOUT_DEFAULT)); + } + + /** + * Exposes the container connection settings as test variables on the given context. + * @param container the container holding the connection settings. + * @param serviceName the service name of the container. + * @param context the test context to receive the test variables. + */ + public static void exposeConnectionSettings(PostgreSQLContainer container, String serviceName, TestContext context) { + if (container.getContainerId() != null) { + String dockerContainerId = container.getContainerId().substring(0, 12); + String dockerContainerName = container.getContainerName(); + + if (dockerContainerName.startsWith("/")) { + dockerContainerName = dockerContainerName.substring(1); + } + + String containerType = "POSTGRESQL"; + context.setVariable(getEnvVarName(containerType, "HOST"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_IP"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_ID"), dockerContainerId); + context.setVariable(getEnvVarName(containerType, "CONTAINER_NAME"), dockerContainerName); + + context.setVariable(getEnvVarName(containerType, "LOCAL_URL"), container.getJdbcUrl()); + context.setVariable(getEnvVarName(containerType, "SERVICE_LOCAL_URL"), container.getJdbcUrl()); + + if (!KubernetesSupport.isConnected(context) || !TestContainersSettings.isKubedockEnabled()) { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_URL"), container.getJdbcUrl()); + } else { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_URL"), container.getJdbcUrl().replace("localhost", serviceName)); + } + + context.setVariable(getEnvVarName(containerType, "DRIVER"), container.getDriverClassName()); + context.setVariable(getEnvVarName(containerType, "DB_NAME"), container.getDatabaseName()); + context.setVariable(getEnvVarName(containerType, "USERNAME"), container.getUsername()); + context.setVariable(getEnvVarName(containerType, "PASSWORD"), container.getPassword()); + + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_SERVICE_URL"), container.getJdbcUrl().replace("localhost", serviceName)); + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_HOST"), serviceName); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/postgresql/StartPostgreSQLAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/postgresql/StartPostgreSQLAction.java new file mode 100644 index 0000000000..b6038f0d46 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/postgresql/StartPostgreSQLAction.java @@ -0,0 +1,192 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.postgresql; + +import java.io.IOException; +import javax.script.ScriptException; + +import org.apache.commons.dbcp2.BasicDataSource; +import org.citrusframework.context.TestContext; +import org.citrusframework.exceptions.CitrusRuntimeException; +import org.citrusframework.kubernetes.KubernetesSupport; +import org.citrusframework.spi.Resource; +import org.citrusframework.spi.Resources; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.citrusframework.testcontainers.actions.StartTestcontainersAction; +import org.citrusframework.util.FileUtils; +import org.testcontainers.containers.PostgreSQLContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.ext.ScriptUtils; +import org.testcontainers.jdbc.JdbcDatabaseDelegate; +import org.testcontainers.utility.DockerImageName; + +public class StartPostgreSQLAction extends StartTestcontainersAction> { + + private final String dataSourceName; + private final String initScript; + private final Resource initScriptResource; + + public StartPostgreSQLAction(Builder builder) { + super(builder); + + this.dataSourceName = builder.dataSourceName; + this.initScript = builder.initScript; + this.initScriptResource = builder.initScriptResource; + } + + @Override + public void doExecute(TestContext context) { + super.doExecute(context); + + try { + String resolvedInitScript = ""; + if (initScript != null) { + resolvedInitScript = context.replaceDynamicContentInString(initScript); + } else if (initScriptResource != null) { + resolvedInitScript = context.replaceDynamicContentInString(FileUtils.readToString(initScriptResource)); + } + + if (!resolvedInitScript.isEmpty()) { + try { + ScriptUtils.executeDatabaseScript(new JdbcDatabaseDelegate(getContainer(), ""), "init.sql", resolvedInitScript); + } catch (ScriptException e) { + throw new CitrusRuntimeException("Failed to execute init script"); + } + } + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read init script", e); + } + } + + @Override + protected void exposeConnectionSettings(PostgreSQLContainer container, TestContext context) { + PostgreSQLSettings.exposeConnectionSettings(container, serviceName, context); + + BasicDataSource postgreSQLDataSource = new BasicDataSource(); + postgreSQLDataSource.setDriverClassName(getContainer().getDriverClassName()); + postgreSQLDataSource.setUrl(getContainer().getJdbcUrl()); + postgreSQLDataSource.setUsername(getContainer().getUsername()); + postgreSQLDataSource.setPassword(getContainer().getPassword()); + + context.getReferenceResolver().bind(dataSourceName, postgreSQLDataSource); + } + + /** + * Action builder. + */ + public static class Builder extends AbstractBuilder, StartPostgreSQLAction, Builder> { + + private String postgreSQLVersion = PostgreSQLSettings.getPostgreSQLVersion(); + + private String dataSourceName = "postgreSQL"; + private String databaseName = PostgreSQLSettings.getDatabaseName(); + private String username = PostgreSQLSettings.getUsername(); + private String password = PostgreSQLSettings.getPassword(); + + private String initScript; + private Resource initScriptResource; + + public Builder() { + withStartupTimeout(PostgreSQLSettings.getStartupTimeout()); + } + + public Builder version(String postgreSQLVersion) { + this.postgreSQLVersion = postgreSQLVersion; + return this; + } + + public Builder databaseName(String databaseName) { + this.databaseName = databaseName; + return this; + } + + public Builder dataSourceName(String dataSourceName) { + this.dataSourceName = dataSourceName; + return this; + } + + public Builder username(String username) { + this.username = username; + return this; + } + + public Builder password(String password) { + this.password = password; + return this; + } + + public Builder initScript(String initScript) { + this.initScript = initScript; + return this; + } + + public Builder initScript(Resource resource) { + this.initScriptResource = resource; + return this; + } + + public Builder loadInitScript(String resource) { + this.initScriptResource = Resources.create(resource); + return this; + } + + @Override + protected void prepareBuild() { + if (containerName == null) { + containerName(PostgreSQLSettings.getContainerName()); + } + + if (serviceName == null) { + serviceName(PostgreSQLSettings.getServiceName()); + } + + if (image == null) { + image("postgres"); + } + + env.putIfAbsent("PGDATA", "/var/lib/postgresql/data/mydata"); + + withLabel("app", "citrus"); + withLabel("com.joyrex2001.kubedock.name-prefix", serviceName); + withLabel("app.kubernetes.io/name", "postgresql"); + withLabel("app.kubernetes.io/part-of", TestContainersSettings.getTestName()); + withLabel("app.openshift.io/connects-to", TestContainersSettings.getTestId()); + + PostgreSQLContainer postgreSQLContainer; + if (referenceResolver != null && referenceResolver.isResolvable(containerName, PostgreSQLContainer.class)) { + postgreSQLContainer = referenceResolver.resolve(containerName, PostgreSQLContainer.class); + } else { + postgreSQLContainer = new PostgreSQLContainer<>( + DockerImageName.parse(image).withTag(postgreSQLVersion)) + .withUsername(username) + .withPassword(password) + .withDatabaseName(databaseName) + .withNetwork(network) + .withNetworkAliases(serviceName) + .waitingFor(Wait.forListeningPort() + .withStartupTimeout(startupTimeout)); + } + + container(postgreSQLContainer); + } + + @Override + public StartPostgreSQLAction doBuild() { + return new StartPostgreSQLAction(this); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/redpanda/RedpandaSettings.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/redpanda/RedpandaSettings.java new file mode 100644 index 0000000000..cd6320c9f4 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/redpanda/RedpandaSettings.java @@ -0,0 +1,142 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.redpanda; + +import org.citrusframework.context.TestContext; +import org.citrusframework.kubernetes.KubernetesSupport; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.testcontainers.redpanda.RedpandaContainer; + +import static org.citrusframework.testcontainers.TestcontainersHelper.getEnvVarName; + +public class RedpandaSettings { + + private static final String REDPANDA_PROPERTY_PREFIX = TestContainersSettings.TESTCONTAINERS_PROPERTY_PREFIX + "redpanda."; + private static final String REDPANDA_ENV_PREFIX = TestContainersSettings.TESTCONTAINERS_ENV_PREFIX + "REDPANDA_"; + + public static final int REDPANDA_PORT = 9092; + + private static final String VERSION_PROPERTY = REDPANDA_PROPERTY_PREFIX + "version"; + private static final String VERSION_ENV = REDPANDA_ENV_PREFIX + "VERSION"; + private static final String VERSION_DEFAULT = "v24.2.8"; + + private static final String SERVICE_NAME_PROPERTY = REDPANDA_PROPERTY_PREFIX + "service.name"; + private static final String SERVICE_NAME_ENV = REDPANDA_ENV_PREFIX + "SERVICE_NAME"; + public static final String SERVICE_NAME_DEFAULT = "citrus-redpanda"; + + private static final String CONTAINER_NAME_PROPERTY = REDPANDA_PROPERTY_PREFIX + "container.name"; + private static final String CONTAINER_NAME_ENV = REDPANDA_ENV_PREFIX + "CONTAINER_NAME"; + public static final String CONTAINER_NAME_DEFAULT = "redpandaContainer"; + + private static final String IMAGE_NAME_PROPERTY = REDPANDA_PROPERTY_PREFIX + "image.name"; + private static final String IMAGE_NAME_ENV = REDPANDA_ENV_PREFIX + "IMAGE_NAME"; + private static final String IMAGE_NAME_DEFAULT = "redpandadata/redpanda"; + + private static final String STARTUP_TIMEOUT_PROPERTY = REDPANDA_PROPERTY_PREFIX + "startup.timeout"; + private static final String STARTUP_TIMEOUT_ENV = REDPANDA_ENV_PREFIX + "STARTUP_TIMEOUT"; + private static final String STARTUP_TIMEOUT_DEFAULT = "180"; + + private RedpandaSettings() { + // prevent instantiation of utility class + } + + /** + * Redpanda version setting. + * @return + */ + public static String getImageName() { + return System.getProperty(IMAGE_NAME_PROPERTY, + System.getenv(IMAGE_NAME_ENV) != null ? System.getenv(IMAGE_NAME_ENV) : IMAGE_NAME_DEFAULT); + } + + /** + * Redpanda version setting. + * @return + */ + public static String getRedpandaVersion() { + return System.getProperty(VERSION_PROPERTY, + System.getenv(VERSION_ENV) != null ? System.getenv(VERSION_ENV) : VERSION_DEFAULT); + } + + /** + * Redpanda service name setting. + * @return + */ + public static String getServiceName() { + return System.getProperty(SERVICE_NAME_PROPERTY, + System.getenv(SERVICE_NAME_ENV) != null ? System.getenv(SERVICE_NAME_ENV) : SERVICE_NAME_DEFAULT); + } + + /** + * Redpanda container name setting. + * @return + */ + public static String getContainerName() { + return System.getProperty(CONTAINER_NAME_PROPERTY, + System.getenv(CONTAINER_NAME_ENV) != null ? System.getenv(CONTAINER_NAME_ENV) : CONTAINER_NAME_DEFAULT); + } + + /** + * Time in seconds to wait for the container to startup and accept connections. + * @return + */ + public static int getStartupTimeout() { + return Integer.parseInt(System.getProperty(STARTUP_TIMEOUT_PROPERTY, + System.getenv(STARTUP_TIMEOUT_ENV) != null ? System.getenv(STARTUP_TIMEOUT_ENV) : STARTUP_TIMEOUT_DEFAULT)); + } + + /** + * Exposes the container connection settings as test variables on the given context. + * @param container the container holding the connection settings. + * @param serviceName the service name of the container. + * @param context the test context to receive the test variables. + */ + public static void exposeConnectionSettings(RedpandaContainer container, String serviceName, TestContext context) { + if (container.getContainerId() != null) { + String dockerContainerId = container.getContainerId().substring(0, 12); + String dockerContainerName = container.getContainerName(); + + if (dockerContainerName.startsWith("/")) { + dockerContainerName = dockerContainerName.substring(1); + } + + String containerType = "REDPANDA"; + context.setVariable(getEnvVarName(containerType, "HOST"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_IP"), container.getHost()); + context.setVariable(getEnvVarName(containerType, "CONTAINER_ID"), dockerContainerId); + context.setVariable(getEnvVarName(containerType, "CONTAINER_NAME"), dockerContainerName); + + context.setVariable(getEnvVarName(containerType, "LOCAL_BOOTSTRAP_SERVERS"), container.getBootstrapServers()); + context.setVariable(getEnvVarName(containerType, "SERVICE_PORT"), container.getMappedPort(RedpandaSettings.REDPANDA_PORT)); + context.setVariable(getEnvVarName(containerType, "PORT"), container.getMappedPort(RedpandaSettings.REDPANDA_PORT)); + context.setVariable(getEnvVarName(containerType, "SERVICE_LOCAL_BOOTSTRAP_SERVERS"), container.getBootstrapServers()); + + if (!KubernetesSupport.isConnected(context) || !TestContainersSettings.isKubedockEnabled()) { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_BOOTSTRAP_SERVERS"), container.getBootstrapServers()); + context.setVariable(getEnvVarName(containerType, "BOOTSTRAP_SERVERS"), container.getBootstrapServers()); + } else { + context.setVariable(getEnvVarName(containerType, "SERVICE_NAME"), serviceName); + context.setVariable(getEnvVarName(containerType, "SERVICE_BOOTSTRAP_SERVERS"), String.format("%s:%s", serviceName, container.getMappedPort(RedpandaSettings.REDPANDA_PORT))); + context.setVariable(getEnvVarName(containerType, "BOOTSTRAP_SERVERS"), String.format("%s:%s", serviceName, container.getMappedPort(RedpandaSettings.REDPANDA_PORT))); + } + + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_SERVICE_BOOTSTRAP_SERVERS"), String.format("%s:%s", serviceName, container.getMappedPort(RedpandaSettings.REDPANDA_PORT))); + context.setVariable(getEnvVarName(containerType, "KUBE_DOCK_HOST"), serviceName); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/redpanda/StartRedpandaAction.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/redpanda/StartRedpandaAction.java new file mode 100644 index 0000000000..4986d41bf0 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/redpanda/StartRedpandaAction.java @@ -0,0 +1,101 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.redpanda; + +import org.citrusframework.context.TestContext; +import org.citrusframework.kubernetes.KubernetesSupport; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.citrusframework.testcontainers.actions.StartTestcontainersAction; +import org.citrusframework.testcontainers.postgresql.PostgreSQLSettings; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.redpanda.RedpandaContainer; +import org.testcontainers.utility.DockerImageName; + +public class StartRedpandaAction extends StartTestcontainersAction { + + public StartRedpandaAction(Builder builder) { + super(builder); + } + + @Override + protected void exposeConnectionSettings(RedpandaContainer container, TestContext context) { + RedpandaSettings.exposeConnectionSettings(container, serviceName, context); + } + + /** + * Action builder. + */ + public static class Builder extends AbstractBuilder { + + private String redpandaVersion = RedpandaSettings.getRedpandaVersion(); + + public Builder() { + withStartupTimeout(PostgreSQLSettings.getStartupTimeout()); + } + + public Builder version(String redpandaVersion) { + this.redpandaVersion = redpandaVersion; + return this; + } + + @Override + protected void prepareBuild() { + if (containerName == null) { + containerName(RedpandaSettings.getContainerName()); + } + + if (serviceName == null) { + serviceName(RedpandaSettings.getServiceName()); + } + + if (image == null) { + image(RedpandaSettings.getImageName()); + } + + withLabel("app", "citrus"); + withLabel("com.joyrex2001.kubedock.name-prefix", serviceName); + withLabel("app.kubernetes.io/name", "redpanda"); + withLabel("app.kubernetes.io/part-of", TestContainersSettings.getTestName()); + withLabel("app.openshift.io/connects-to", TestContainersSettings.getTestId()); + + RedpandaContainer redpandaContainer; + if (referenceResolver != null && referenceResolver.isResolvable(containerName, RedpandaContainer.class)) { + redpandaContainer = referenceResolver.resolve(containerName, RedpandaContainer.class); + } else { + redpandaContainer = new RedpandaContainer(DockerImageName.parse(image).withTag(redpandaVersion)) + .withNetwork(network) + .withNetworkAliases(serviceName) + .waitingFor(Wait.forLogMessage(".*Successfully started Redpanda!.*", 1) + .withStartupTimeout(startupTimeout)) + // TODO: Remove once Redpanda container works with Podman + . withCreateContainerCmdModifier(cmd -> { + cmd.withEntrypoint(); + cmd.withEntrypoint("/entrypoint-tc.sh"); + cmd.withUser("root:root"); + }) + .withCommand("redpanda", "start", "--mode=dev-container", "--smp=1", "--memory=1G"); + } + + container(redpandaContainer); + } + + @Override + public StartRedpandaAction doBuild() { + return new StartRedpandaAction(this); + } + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/ObjectFactory.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/ObjectFactory.java new file mode 100644 index 0000000000..4b39864b71 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/ObjectFactory.java @@ -0,0 +1,52 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.xml; + +import jakarta.xml.bind.annotation.XmlRegistry; + +/** + * This object contains factory methods for each + * Java content interface and Java element interface + * generated in the org.citrusframework.ftp.model package. + *

An ObjectFactory allows you to programatically + * construct new instances of the Java representation + * for XML content. The Java representation of XML + * content can consist of schema derived interfaces + * and classes representing the binding of schema + * type definitions, element declarations and model + * groups. Factory methods for each of these are + * provided in this class. + * + */ +@XmlRegistry +public class ObjectFactory { + + /** + * Create a new ObjectFactory that can be used to create new instances of schema derived classes for package: org.citrusframework.xml.actions + * + */ + public ObjectFactory() { + } + + /** + * Create an instance of {@link Testcontainers } + * + */ + public Testcontainers createTestcontainers() { + return new Testcontainers(); + } +} diff --git a/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Start.java b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Start.java new file mode 100644 index 0000000000..a6cd42fbf8 --- /dev/null +++ b/connectors/citrus-testcontainers/src/main/java/org/citrusframework/testcontainers/xml/Start.java @@ -0,0 +1,606 @@ +/* + * Copyright the original author or authors. + * + * Licensed 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.citrusframework.testcontainers.xml; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import jakarta.xml.bind.annotation.XmlAccessType; +import jakarta.xml.bind.annotation.XmlAccessorType; +import jakarta.xml.bind.annotation.XmlAttribute; +import jakarta.xml.bind.annotation.XmlElement; +import jakarta.xml.bind.annotation.XmlRootElement; +import jakarta.xml.bind.annotation.XmlType; +import jakarta.xml.bind.annotation.XmlValue; +import org.citrusframework.TestActor; +import org.citrusframework.spi.ReferenceResolver; +import org.citrusframework.spi.ReferenceResolverAware; +import org.citrusframework.spi.Resources; +import org.citrusframework.testcontainers.TestContainersSettings; +import org.citrusframework.testcontainers.actions.AbstractTestcontainersAction; +import org.citrusframework.testcontainers.actions.StartTestcontainersAction; +import org.citrusframework.testcontainers.aws2.LocalStackContainer; +import org.citrusframework.testcontainers.aws2.StartLocalStackAction; +import org.citrusframework.testcontainers.kafka.StartKafkaAction; +import org.citrusframework.testcontainers.mongodb.StartMongoDBAction; +import org.citrusframework.testcontainers.postgresql.StartPostgreSQLAction; +import org.citrusframework.testcontainers.redpanda.StartRedpandaAction; +import org.citrusframework.util.ObjectHelper; + +@XmlRootElement(name = "start") +public class Start extends AbstractTestcontainersAction.Builder, Start> implements ReferenceResolverAware { + + private StartTestcontainersAction.AbstractBuilder delegate; + + @XmlElement + public void setContainer(Container container) { + StartTestcontainersAction.Builder builder = new StartTestcontainersAction.Builder<>(); + configureStartActionBuilder(builder, container); + delegate = builder; + } + + @XmlElement(name = "localstack") + public void setLocalStack(LocalStack container) { + StartLocalStackAction.Builder builder = new StartLocalStackAction.Builder(); + if (container.getVersion() != null) { + builder.version(container.getVersion()); + } + + configureStartActionBuilder(builder, container); + + if (container.getServices() != null) { + container.getServices().getServices().forEach(service -> builder.withService(LocalStackContainer.Service.valueOf(service))); + } + + if (container.getServiceList() != null) { + Stream.of(container.getServiceList().split(",")).forEach(service -> builder.withService(LocalStackContainer.Service.valueOf(service))); + } + + delegate = builder; + } + + @XmlElement(name = "mongodb") + public void setMongoDB(MongoDB container) { + StartMongoDBAction.Builder builder = new StartMongoDBAction.Builder(); + if (container.getVersion() != null) { + builder.version(container.getVersion()); + } + + configureStartActionBuilder(builder, container); + + delegate = builder; + } + + @XmlElement(name = "kafka") + public void setKafka(Kafka container) { + StartKafkaAction.Builder builder = new StartKafkaAction.Builder(); + if (container.getVersion() != null) { + builder.version(container.getVersion()); + } + + configureStartActionBuilder(builder, container); + + delegate = builder; + } + + @XmlElement(name = "redpanda") + public void setRedpanda(Redpanda container) { + StartRedpandaAction.Builder builder = new StartRedpandaAction.Builder(); + if (container.getVersion() != null) { + builder.version(container.getVersion()); + } + + configureStartActionBuilder(builder, container); + + delegate = builder; + } + + @XmlElement(name = "postgresql") + public void setPostgreSQL(PostgreSQL container) { + StartPostgreSQLAction.Builder builder = new StartPostgreSQLAction.Builder(); + if (container.getVersion() != null) { + builder.version(container.getVersion()); + } + + configureStartActionBuilder(builder, container); + + if (container.getDataSourceName() != null) { + builder.dataSourceName(container.getDataSourceName()); + } + + if (container.getDatabase() != null) { + builder.databaseName(container.getDatabase()); + } + + if (container.getUsername() != null) { + builder.username(container.getUsername()); + } + + if (container.getPassword() != null) { + builder.password(container.getPassword()); + } + + if (container.getInitScript() != null) { + if (container.getInitScript().getFile() != null) { + builder.initScript(Resources.create(container.getInitScript().getFile())); + } + + if (container.getInitScript().getValue() != null) { + builder.initScript(container.getInitScript().getValue()); + } + } + + delegate = builder; + } + + @Override + public Start description(String description) { + delegate.description(description); + return this; + } + + @Override + public Start actor(TestActor actor) { + delegate.actor(actor); + return this; + } + + @Override + public void setReferenceResolver(ReferenceResolver referenceResolver) { + this.delegate.setReferenceResolver(referenceResolver); + } + + @Override + public StartTestcontainersAction doBuild() { + ObjectHelper.assertNotNull(delegate); + return delegate.build(); + } + + private void configureStartActionBuilder(StartTestcontainersAction.AbstractBuilder builder, Container container) { + builder.containerName(container.getName()); + builder.serviceName(container.getServiceName()); + builder.image(container.getImage()); + + builder.autoRemove(container.isAutoRemove()); + + if (container.getStartUpTimeout() > 0) { + builder.withStartupTimeout(container.getStartUpTimeout()); + } + + if (container.getCommand() != null) { + builder.withCommand(container.getCommand().split(" ")); + } + + if (container.getEnvironmentVariables() != null) { + container.getEnvironmentVariables().getVariables().forEach(variable -> { + builder.withEnv(variable.getName(), variable.getValue()); + }); + } + + if (container.getLabels() != null) { + container.getLabels().getLabels().forEach(label -> { + builder.withLabel(label.getName(), label.getValue()); + }); + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "labels", + "environmentVariables", + }) + public static class Container { + + @XmlAttribute + private String name; + + @XmlAttribute(name = "service-name") + private String serviceName; + + @XmlAttribute + private String image; + + @XmlAttribute(name = "startup-timeout") + private int startUpTimeout; + + @XmlAttribute + protected String command; + + @XmlAttribute(name = "auto-remove") + protected boolean autoRemove = TestContainersSettings.isAutoRemoveResources(); + + @XmlElement(name = "env") + protected EnvironmentVariables environmentVariables; + + @XmlElement + protected Labels labels; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public boolean isAutoRemove() { + return autoRemove; + } + + public void setAutoRemove(boolean autoRemove) { + this.autoRemove = autoRemove; + } + + public int getStartUpTimeout() { + return startUpTimeout; + } + + public void setStartUpTimeout(int startUpTimeout) { + this.startUpTimeout = startUpTimeout; + } + + public EnvironmentVariables getEnvironmentVariables() { + return environmentVariables; + } + + public void setEnvironmentVariables(EnvironmentVariables environmentVariables) { + this.environmentVariables = environmentVariables; + } + + public Labels getLabels() { + return labels; + } + + public void setLabels(Labels labels) { + this.labels = labels; + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "services" + }) + public static class LocalStack extends Container { + + @XmlAttribute + protected String version; + + @XmlAttribute(name = "services") + protected String serviceList; + + @XmlElement + protected Services services; + + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getServiceList() { + return serviceList; + } + + public void setServiceList(String serviceList) { + this.serviceList = serviceList; + } + + public Services getServices() { + return services; + } + + public void setServices(Services services) { + this.services = services; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "services" + }) + public static class Services { + + @XmlElement(name = "service") + private List services; + + public List getServices() { + if (services == null) { + services = new ArrayList<>(); + } + return services; + } + + public void setServices(List services) { + this.services = services; + } + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + }) + public static class MongoDB extends Container { + + @XmlAttribute + protected String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + }) + public static class Kafka extends Container { + + @XmlAttribute + protected String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + }) + public static class Redpanda extends Container { + + @XmlAttribute + protected String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "initScript" + }) + public static class PostgreSQL extends Container { + + @XmlAttribute + protected String version; + + @XmlAttribute(name = "datasource-name") + protected String dataSourceName; + + @XmlAttribute + protected String database; + + @XmlAttribute + protected String username; + + @XmlAttribute + protected String password; + + @XmlElement(name = "init-script") + protected InitScript initScript; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getDataSourceName() { + return dataSourceName; + } + + public void setDataSourceName(String dataSourceName) { + this.dataSourceName = dataSourceName; + } + + public String getDatabase() { + return database; + } + + public void setDatabase(String database) { + this.database = database; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public InitScript getInitScript() { + return initScript; + } + + public void setInitScript(InitScript initScript) { + this.initScript = initScript; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "") + public static class InitScript { + + @XmlAttribute + protected String file; + @XmlValue + protected String value; + + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "variables" + }) + public static class EnvironmentVariables { + + @XmlElement(name = "variable") + private List variables; + + public void setVariables(List variables) { + this.variables = variables; + } + + public List getVariables() { + if (variables == null) { + variables = new ArrayList<>(); + } + return variables; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "") + public static class Variable { + + @XmlAttribute(name = "name", required = true) + protected String name; + @XmlAttribute(name = "value", required = true) + protected String value; + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + } + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "labels" + }) + public static class Labels { + + @XmlElement(name = "label") + private List