diff --git a/.github/workflows/ubuntu-master.yml b/.github/workflows/ubuntu-master.yml index f4dcf1f..45f9b4c 100644 --- a/.github/workflows/ubuntu-master.yml +++ b/.github/workflows/ubuntu-master.yml @@ -30,4 +30,5 @@ jobs: env: TARANTOOL_SERVER_USER: root TARANTOOL_SERVER_GROUP: root + URI: ${{ secrets.URI }} run: mvn -B verify --file pom.xml diff --git a/pom.xml b/pom.xml index ad5b812..0d736f8 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ org.junit.jupiter junit-jupiter - 5.6.2 + 5.7.0 test @@ -91,6 +91,11 @@ 1.2.3 test + + com.github.docker-java + docker-java + 3.2.13 + diff --git a/src/main/java/org/testcontainers/containers/TarantoolContainer.java b/src/main/java/org/testcontainers/containers/TarantoolContainer.java index 85579bf..214f4ed 100644 --- a/src/main/java/org/testcontainers/containers/TarantoolContainer.java +++ b/src/main/java/org/testcontainers/containers/TarantoolContainer.java @@ -56,6 +56,11 @@ public TarantoolContainer(String dockerImageName) { clientHelper = new TarantoolContainerClientHelper(this); } + public TarantoolContainer(TarantoolImageParams tarantoolImageParams) { + super(TarantoolContainerImageHelper.getImage(tarantoolImageParams)); + clientHelper = new TarantoolContainerClientHelper(this); + } + public TarantoolContainer(Future image) { super(image); clientHelper = new TarantoolContainerClientHelper(this); diff --git a/src/main/java/org/testcontainers/containers/TarantoolContainerImageHelper.java b/src/main/java/org/testcontainers/containers/TarantoolContainerImageHelper.java new file mode 100644 index 0000000..e6a4242 --- /dev/null +++ b/src/main/java/org/testcontainers/containers/TarantoolContainerImageHelper.java @@ -0,0 +1,82 @@ +package org.testcontainers.containers; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.BuildImageResultCallback; +import com.github.dockerjava.api.model.Image; +import com.github.dockerjava.core.DockerClientBuilder; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; + +/** + * Class for working with docker directly + * + * @author Oleg Kuznetsov + */ +class TarantoolContainerImageHelper { + + private static final DockerClient dockerClient = DockerClientBuilder.getInstance().build(); + + private TarantoolContainerImageHelper() { + } + + /** + * Checks image for existing by name and build if it not exist + * + * @param imageParams parameters for building tarantool image + * @return image name + */ + static String getImage(TarantoolImageParams imageParams) { + final String sdkVersion = imageParams.getSdkVersion(); + + if (StringUtils.isEmpty(sdkVersion)) { + throw new IllegalArgumentException("SDK version is null or empty!"); + } + + if (!hasImage(sdkVersion)) { + buildImage(imageParams); + } + + return sdkVersion; + } + + /** + * Builds image from parameters + * + * @param imageParams parameters for building tarantool image + */ + private static void buildImage(TarantoolImageParams imageParams) { + final String sdkVersion = imageParams.getSdkVersion(); + final String uri = System.getenv("URI"); + + if (StringUtils.isEmpty(uri)) { + throw new IllegalStateException("URI environment variable must be specified!"); + } + + dockerClient.buildImageCmd(imageParams.getDockerfile()) + .withTags(new HashSet<>(Collections.singletonList(sdkVersion))) + .withBuildArg("SDK_VERSION", sdkVersion) + .withBuildArg("URI", uri) + .exec(new BuildImageResultCallback()) + .awaitImageId(); + } + + /** + * Checks image for existing by name + * + * @param imageName image name for searching + * @return true if image exist and false if not + */ + private static boolean hasImage(String imageName) { + final List images = dockerClient.listImagesCmd().exec(); + return images.stream() + .map(Image::getRepoTags) + .map(Arrays::asList) + .flatMap(Collection::stream) + .anyMatch(tag -> tag.equals(imageName + ":latest")); + } +} diff --git a/src/main/java/org/testcontainers/containers/TarantoolImageParams.java b/src/main/java/org/testcontainers/containers/TarantoolImageParams.java new file mode 100644 index 0000000..2c8de3f --- /dev/null +++ b/src/main/java/org/testcontainers/containers/TarantoolImageParams.java @@ -0,0 +1,61 @@ +package org.testcontainers.containers; + +import java.io.File; +import java.net.URISyntaxException; + +/** + * Tarantool image parameters holder + * + * @author Oleg Kuznetsov + */ +public class TarantoolImageParams { + + private final String sdkVersion; + private final File dockerfile; + + /** + * Basic constructor for tarantool image parameters + * + * @param sdkVersion version of tarantool sdk which will be downloaded from specified in env variables URI, + * for example: tarantool-enterprise-bundle-2.8.3-21-g7d35cd2be-r470 + */ + public TarantoolImageParams(String sdkVersion) { + this.sdkVersion = sdkVersion; + try { + this.dockerfile = new File(TarantoolImageParams.class.getClassLoader() + .getResource("sdk/Dockerfile").toURI()); + } catch (URISyntaxException e) { + throw new IllegalStateException("Can't access to Dockerfile for testcontainers"); + } + } + + /** + * Custom constructor for tarantool image parameters + * + * @param sdkVersion version of tarantool sdk which will be downloaded from specified in env variables URI, + * for example: tarantool-enterprise-bundle-2.8.3-21-g7d35cd2be-r470 + * @param dockerfile dockerfile for building custom tarantool image + */ + public TarantoolImageParams(String sdkVersion, File dockerfile) { + this.sdkVersion = sdkVersion; + this.dockerfile = dockerfile; + } + + /** + * Getter for sdk version + * + * @return sdk version + */ + public String getSdkVersion() { + return sdkVersion; + } + + /** + * Getter for dockerfile + * + * @return dockerfile + */ + public File getDockerfile() { + return dockerfile; + } +} diff --git a/src/main/resources/sdk/Dockerfile b/src/main/resources/sdk/Dockerfile new file mode 100644 index 0000000..2db13a1 --- /dev/null +++ b/src/main/resources/sdk/Dockerfile @@ -0,0 +1,25 @@ +FROM centos:7 + +ARG TARANTOOL_WORKDIR="/app" +ARG TARANTOOL_RUNDIR="/tmp/run" +ARG TARANTOOL_DATADIR="/tmp/data" +ARG SDK_TGT_DIR="/sdk" +ARG URI="" +ARG SDK_VERSION="" +ARG SDK_TGZ=$SDK_VERSION.tar.gz + +ENV URI=$URI +ENV SDK_VERSION=$SDK_VERSION +ENV SDK_TGT_DIR=$SDK_TGT_DIR +ENV TARANTOOL_WORKDIR=$TARANTOOL_WORKDIR +ENV TARANTOOL_RUNDIR=$TARANTOOL_RUNDIR +ENV TARANTOOL_DATADIR=$TARANTOOL_DATADIR + +RUN curl https://curl.se/ca/cacert.pem -o /etc/pki/tls/certs/ca-bundle.crt && \ + yum -y install wget && \ + wget $URI/$SDK_TGZ && \ + mkdir ./tmp_sdk && tar -xf $SDK_TGZ -C ./tmp_sdk && \ + mv ./tmp_sdk/tarantool-enterprise $SDK_TGT_DIR && rm $SDK_TGZ && \ + cp $SDK_TGT_DIR/tarantool /usr/bin/tarantool + +WORKDIR $TARANTOOL_WORKDIR diff --git a/src/main/resources/server.lua b/src/main/resources/server.lua index e921ee1..cc6580b 100644 --- a/src/main/resources/server.lua +++ b/src/main/resources/server.lua @@ -5,6 +5,6 @@ box.cfg { log_level = 6, } -- API user will be able to login with this password -box.schema.user.create('api_user', { password = 'secret' }) +box.schema.user.create('api_user', { password = 'secret', if_not_exists = true }) -- API user will be able to create spaces, add or remove data, execute functions -box.schema.user.grant('api_user', 'read,write,execute', 'universe') +box.schema.user.grant('api_user', 'read,write,execute', 'universe', nil, { if_not_exists = true }) diff --git a/src/test/java/org/testcontainers/containers/TarantoolCartridgeContainerReplicasetsTest.java b/src/test/java/org/testcontainers/containers/TarantoolCartridgeContainerReplicasetsTest.java index aa03637..2efbb2c 100644 --- a/src/test/java/org/testcontainers/containers/TarantoolCartridgeContainerReplicasetsTest.java +++ b/src/test/java/org/testcontainers/containers/TarantoolCartridgeContainerReplicasetsTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; import org.testcontainers.containers.output.Slf4jLogConsumer; -import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.MountableFile; import java.time.Duration; @@ -36,7 +35,7 @@ public void test_ClusterContainer_StartsSuccessfully_ifFilesAreCopiedUnderRoot() container.start(); CartridgeContainerTestUtils.executeProfileReplaceSmokeTest(container); - if(container.isRunning()) + if (container.isRunning()) container.stop(); } } diff --git a/src/test/java/org/testcontainers/containers/TarantoolCartridgeStaticContainerTest.java b/src/test/java/org/testcontainers/containers/TarantoolCartridgeStaticContainerTest.java index 0049aad..090667a 100644 --- a/src/test/java/org/testcontainers/containers/TarantoolCartridgeStaticContainerTest.java +++ b/src/test/java/org/testcontainers/containers/TarantoolCartridgeStaticContainerTest.java @@ -26,7 +26,7 @@ public class TarantoolCartridgeStaticContainerTest { LoggerFactory.getLogger(TarantoolCartridgeStaticContainerTest.class))); @Test - public void test_StaticClusterContainer_StartsSuccessfully_ifDirectoryBinndingIsUsed() throws Exception { + public void test_StaticClusterContainer_StartsSuccessfully_ifDirectoryBindingIsUsed() throws Exception { CartridgeContainerTestUtils.executeProfileReplaceSmokeTest(container); } } diff --git a/src/test/java/org/testcontainers/containers/TarantoolSdkContainerTest.java b/src/test/java/org/testcontainers/containers/TarantoolSdkContainerTest.java new file mode 100644 index 0000000..ee5ee96 --- /dev/null +++ b/src/test/java/org/testcontainers/containers/TarantoolSdkContainerTest.java @@ -0,0 +1,67 @@ +package org.testcontainers.containers; + + +import io.tarantool.driver.TarantoolVersion; +import io.tarantool.driver.api.TarantoolClient; +import io.tarantool.driver.api.TarantoolClientFactory; +import io.tarantool.driver.api.TarantoolResult; +import io.tarantool.driver.api.tuple.TarantoolTuple; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.net.URISyntaxException; +import java.util.List; + +/** + * @author Oleg Kuznetsov + */ +public class TarantoolSdkContainerTest { + + @Test + void test_should_createTarantoolContainerFromSdk() { + try (final TarantoolContainer tarantoolContainer = new TarantoolContainer( + new TarantoolImageParams("tarantool-enterprise-bundle-2.8.3-21-g7d35cd2be-r470") + )) { + tarantoolContainer.start(); + + final TarantoolClient> client = + TarantoolClientFactory.createClient() + .withCredentials("api_user", "secret") + .withAddress(tarantoolContainer.getHost(), tarantoolContainer.getMappedPort(3301)) + .build(); + + final List result = client.eval("return 'test'").join(); + final TarantoolVersion version = client.getVersion(); + + Assertions.assertEquals("test", result.get(0)); + Assertions.assertTrue(version.toString().startsWith("Tarantool 2.8.3 (Binary)")); + } + } + + @Test + void test_should_createTarantoolContainerFromSdk_ifDockerfileSpecified() throws URISyntaxException { + final File dockerfile = new File( + TarantoolSdkContainerTest.class.getClassLoader().getResource("testsdk/Dockerfile").toURI() + ); + + try (final TarantoolContainer tarantoolContainer = new TarantoolContainer( + new TarantoolImageParams("testsdk", dockerfile)) + .withDirectoryBinding("testsdk")) { + + tarantoolContainer.start(); + + final TarantoolClient> client = + TarantoolClientFactory.createClient() + .withCredentials("api_user", "secret") + .withAddress(tarantoolContainer.getHost(), tarantoolContainer.getMappedPort(3301)) + .build(); + + final List result = client.eval("return 'test'").join(); + final TarantoolVersion version = client.getVersion(); + + Assertions.assertEquals("test", result.get(0)); + Assertions.assertTrue(version.toString().startsWith("Tarantool 2.7.3 (Binary)")); + } + } +} diff --git a/src/test/java/org/testcontainers/containers/TarantoolStaticContainerTest.java b/src/test/java/org/testcontainers/containers/TarantoolStaticContainerTest.java index 3ea9bd0..3cdd6c3 100644 --- a/src/test/java/org/testcontainers/containers/TarantoolStaticContainerTest.java +++ b/src/test/java/org/testcontainers/containers/TarantoolStaticContainerTest.java @@ -15,7 +15,7 @@ public class TarantoolStaticContainerTest { @Container - private static TarantoolContainer container = new TarantoolContainer(); + private static final TarantoolContainer container = new TarantoolContainer(); @Test public void testExecuteCommand() throws Exception { diff --git a/src/test/resources/testsdk/Dockerfile b/src/test/resources/testsdk/Dockerfile new file mode 100644 index 0000000..d1bf0b8 --- /dev/null +++ b/src/test/resources/testsdk/Dockerfile @@ -0,0 +1,24 @@ +FROM centos:7 + +ARG TARANTOOL_WORKDIR="/app" +ARG TARANTOOL_RUNDIR="/tmp/run" +ARG TARANTOOL_DATADIR="/tmp/data" +ARG SDK_TGT_DIR="/sdk" +ARG URI="" +ARG SDK_TGZ="tarantool-enterprise-bundle-2.7.3-0-gdddf926c3-r443.tar.gz" + +ENV URI=$URI +ENV SDK_VERSION="tarantool-enterprise-bundle-2.7.3-0-gdddf926c3-r443" +ENV SDK_TGT_DIR=$SDK_TGT_DIR +ENV TARANTOOL_WORKDIR=$TARANTOOL_WORKDIR +ENV TARANTOOL_RUNDIR=$TARANTOOL_RUNDIR +ENV TARANTOOL_DATADIR=$TARANTOOL_DATADIR + +RUN curl https://curl.se/ca/cacert.pem -o /etc/pki/tls/certs/ca-bundle.crt && \ + yum -y install wget && \ + wget $URI/$SDK_TGZ && \ + mkdir ./tmp_sdk && tar -xf $SDK_TGZ -C ./tmp_sdk && \ + mv ./tmp_sdk/tarantool-enterprise $SDK_TGT_DIR && rm $SDK_TGZ && \ + cp $SDK_TGT_DIR/tarantool /usr/bin/tarantool + +WORKDIR $TARANTOOL_WORKDIR diff --git a/src/test/resources/testsdk/server.lua b/src/test/resources/testsdk/server.lua new file mode 100644 index 0000000..cc6580b --- /dev/null +++ b/src/test/resources/testsdk/server.lua @@ -0,0 +1,10 @@ +box.cfg { + listen = 3301, + memtx_memory = 128 * 1024 * 1024, -- 128 Mb + -- log = 'file:/tmp/tarantool.log', + log_level = 6, +} +-- API user will be able to login with this password +box.schema.user.create('api_user', { password = 'secret', if_not_exists = true }) +-- API user will be able to create spaces, add or remove data, execute functions +box.schema.user.grant('api_user', 'read,write,execute', 'universe', nil, { if_not_exists = true })