3

我的应用程序使用Minio进行 S3 兼容的对象存储,我想通过 Testcontainers 在我的集成测试中使用Minio docker 映像

minio/minio对于一些非常基本的测试,我使用docker 映像运行 GenericContainer,除了MINIO_ACCESS_KEYand之外没有任何配置MINIO_SECRET_KEY。然后我的测试使用 Minio 的Java Client SDK。这些工作正常并且行为与预期一样。

但是对于其他集成测试,我需要在 Mino 中设置单独的用户。据我所知,用户只能使用Admin API添加到 Minio ,没有 Java 客户端,只有minio/mcdocker 镜像(用于服务器mc的 docker 镜像中没有 CLI )。minio/minio

在命令行上,我可以像这样使用 Admin API:

$ docker run --interactive --tty --detach --entrypoint=/bin/sh --name minio_admin minio/mc

--interactive --tty保持容器运行有点小技巧,所以我以后可以运行这样的命令:

$ docker exec --interactive --tty minio_admin mc admin user add ...

使用 Testcontainers,我尝试这样做:

public void testAdminApi() throws Exception {
    GenericContainer mc = new GenericContainer("minio/mc")
            .withCommand("/bin/sh")
            .withCreateContainerCmdModifier(new Consumer<CreateContainerCmd>() {
                @Override
                public void accept(CreateContainerCmd cmd) {
                    cmd
                            .withAttachStdin(true)
                            .withStdinOpen(true)
                            .withTty(true);
                }
            });

    mc.start();
    log.info("mc is running: {}", mc.isRunning());

    String command = "mc";
    Container.ExecResult result = mc.execInContainer(command);
    log.info("Executing command '{}' returned exit code '{}' and stdout '{}'", command, result.getExitCode(), result.getStdout());

    assertEquals(0, result.getExitCode());
}

日志显示容器正在启动,但对其执行命令会返回退出代码 126 并声称它处于停止状态:

[minio/mc:latest] - Starting container with ID: 4f96fc7583fe62290925472c4c6b329fbeb7a55b38a3c0ad41ee797db1431841
[minio/mc:latest] - Container minio/mc:latest is starting: 4f96fc7583fe62290925472c4c6b329fbeb7a55b38a3c0ad41ee797db1431841
[minio/mc:latest] - Container minio/mc:latest started
minio.MinioAdminTests - mc is running: true
org.testcontainers.containers.ExecInContainerPattern - /kind_volhard: Running "exec" command: mc
minio.MinioAdminTests - Executing command 'mc' returned exit code '126'
  and stdout 'cannot exec in a stopped state: unknown'

java.lang.AssertionError: Expected: 0, Actual: 126

在摆弄了几个小时之后,我的想法已经不多了。任何人都可以帮忙吗?

4

4 回答 4

3

感谢@glebsts 和@bsideup,我能够让我的集成测试正常工作。这是如何添加用户的最小示例:

public class MinioIntegrationTest {

    private static final String ADMIN_ACCESS_KEY = "admin";
    private static final String ADMIN_SECRET_KEY = "12345678";
    private static final String USER_ACCESS_KEY = "bob";
    private static final String USER_SECRET_KEY = "87654321";

    private static GenericContainer minioServer;
    private static String minioServerUrl;

    @BeforeAll
    static void setUp() throws Exception {
        int port = 9000;
        minioServer = new GenericContainer("minio/minio")
                .withEnv("MINIO_ACCESS_KEY", ADMIN_ACCESS_KEY)
                .withEnv("MINIO_SECRET_KEY", ADMIN_SECRET_KEY)
                .withCommand("server /data")
                .withExposedPorts(port)
                .waitingFor(new HttpWaitStrategy()
                        .forPath("/minio/health/ready")
                        .forPort(port)
                        .withStartupTimeout(Duration.ofSeconds(10)));
        minioServer.start();

        Integer mappedPort = minioServer.getFirstMappedPort();
        Testcontainers.exposeHostPorts(mappedPort);
        minioServerUrl = String.format("http://%s:%s", minioServer.getContainerIpAddress(), mappedPort);

        // Minio Java SDK uses s3v4 protocol by default, need to specify explicitly for mc
        String cmdTpl = "mc config host add myminio http://host.testcontainers.internal:%s %s %s --api s3v4 && "
                + "mc admin user add myminio %s %s readwrite";
        String cmd = String.format(cmdTpl, mappedPort, ADMIN_ACCESS_KEY, ADMIN_SECRET_KEY, USER_ACCESS_KEY, USER_SECRET_KEY);

        GenericContainer mcContainer = new GenericContainer<>("minio/mc")
                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())
                .withCreateContainerCmdModifier(containerCommand -> containerCommand
                        .withTty(true)
                        .withEntrypoint("/bin/sh", "-c", cmd));
        mcContainer.start();
    }

    @Test
    public void canCreateBucketWithAdminUser() throws Exception {
        MinioClient client = new MinioClient(minioServerUrl, ADMIN_ACCESS_KEY, ADMIN_SECRET_KEY);
        client.ignoreCertCheck();

        String bucketName = "foo";
        client.makeBucket(bucketName);
        assertTrue(client.bucketExists(bucketName));
    }

    @Test
    public void canCreateBucketWithNonAdminUser() throws Exception {
        MinioClient client = new MinioClient(minioServerUrl, USER_ACCESS_KEY, USER_SECRET_KEY);
        client.ignoreCertCheck();

        String bucketName = "bar";
        client.makeBucket(bucketName);
        assertTrue(client.bucketExists(bucketName));
    }

    @AfterAll
    static void shutDown() {
        if (minioServer.isRunning()) {
            minioServer.stop();
        }
    }
}
于 2019-04-02T19:51:08.153 回答
2

正如@bsideup 建议的那样,您可以使用一次性策略,即在 这里。UPD:添加了工作测试。重要的是要知道

容器启动时,执行入口点+命令(这就是一般的Docker,与Testcontainers无关)。来源来自 TC github

public class TempTest {
    @Rule
    public Network network = Network.newNetwork();

    private String runMcCommand(String cmd) throws TimeoutException {
        GenericContainer container = new GenericContainer<>("minio/mc")
                .withCommand(cmd)
                .withNetwork(network)
                .withStartupCheckStrategy(new OneShotStartupCheckStrategy())
                .withCreateContainerCmdModifier(command -> command.withTty(true));
        container.start();
        WaitingConsumer waitingConsumer = new WaitingConsumer();
        ToStringConsumer toStringConsumer = new ToStringConsumer();
        Consumer<OutputFrame> composedConsumer = toStringConsumer.andThen(waitingConsumer);
        container.followOutput(composedConsumer);
        waitingConsumer.waitUntilEnd(4, TimeUnit.SECONDS);
        return toStringConsumer.toUtf8String();
    }

    private void showCommandOutput(String cmd) throws TimeoutException {
        String res = runMcCommand(cmd);
        System.out.printf("Cmd '%s' result:\n----\n%s\n----%n", cmd, res);
    }

    @Test
    public void testAdminApi() throws Exception {
        showCommandOutput("ls");
        showCommandOutput("version");
    }
}

另一种选择是使用minio/mc的dockerfile的内容,它很小,修改执行的命令(默认为一次性“mc”),每次测试运行一次自己的容器,与一次性容器相比,将节省有时如果您需要执行多个命令:

@Rule
public Network network = Network.newNetwork();

@Rule
public GenericContainer mc = new GenericContainer(new ImageFromDockerfile()
  .withDockerfileFromBuilder(builder ->
    builder
      .from("alpine:3.7")
      .run("apk add --no-cache ca-certificates && apk add --no-cache --virtual .build-deps curl && curl https://dl.minio.io/client/mc/release/linux-amd64/mc > /usr/bin/mc && chmod +x /usr/bin/mc && apk del .build-deps")
      .cmd("/bin/sh", "-c", "while sleep 3600; do :; done")
      .build())
    )
  .withNetwork(network);

public void myTest() {
  mc.execInContainer("mc blah");
  mc.execInContainer("mc foo");
}

基本上,它运行安装了 mc 的图像,并休眠 1 小时,这对于您的测试来说已经足够了。在它运行时,您可以执行命令等。完成后,它会被杀死。你的 minio 容器可以在同一个网络中。

于 2019-03-29T20:02:19.253 回答
1

您可以运行一个一次性容器(使用OneShotStartupCheckStrategy),mcwithCommand("your command")连接到与您正在运行的 minio 服务器相同的网络(请参阅网络)。

于 2019-03-29T14:50:45.367 回答
0

对于那些正在寻找带有 minio 对象服务器集成测试的 s3 的人。上面的大多数答案都没有 docker-compose 实现。

码头工人撰写文件:

version: '3.7'
services:
  minio-service:
    image: quay.io/minio/minio
    command: minio server /data
    ports:
      - "9000:9000"
    environment:
      MINIO_ROOT_USER: minio
      MINIO_ROOT_PASSWORD: minio123

实际的 IntegrationTest 类:

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.S3Object;
import org.junit.jupiter.api.*;
import org.testcontainers.containers.DockerComposeContainer;

import java.io.File;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class MinioIntegrationTest {

    private static final DockerComposeContainer minioContainer = new DockerComposeContainer<>(new File("src/test/resources/docker-compose.yml"))
            .withExposedService("minio-service", 9000);
    private static final String MINIO_ENDPOINT = "http://localhost:9000";
    private static final String ACCESS_KEY = "minio";
    private static final String SECRET_KEY = "minio123";
    private AmazonS3 s3Client;

    @BeforeAll
    void setupMinio() {
        minioContainer.start();
        initializeS3Client();
    }

    @AfterAll
    void closeMinio() {
        minioContainer.close();
    }

    private void initializeS3Client() {
        String name = Regions.US_EAST_1.getName();
        AwsClientBuilder.EndpointConfiguration endpoint = new AwsClientBuilder.EndpointConfiguration(MINIO_ENDPOINT, name);
         s3Client = AmazonS3ClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY)))
                .withEndpointConfiguration(endpoint)
                .withPathStyleAccessEnabled(true)
                .build();
    }

    @Test
    void shouldReturnActualContentBasedOnBucketName() throws Exception{
        String bucketName = "test-bucket";
        String key = "s3-test";
        String content = "Minio Integration test";
        s3Client.createBucket(bucketName);
        s3Client.putObject(bucketName, key, content);
        S3Object object = s3Client.getObject(bucketName, key);
        byte[] actualContent = new byte[22];
        object.getObjectContent().read(actualContent);
        Assertions.assertEquals(content, new String(actualContent));
    }
}
于 2021-10-06T09:51:48.567 回答