2
votes

Configuration du serveur Minio pour une utilisation avec Testcontainers

Mon application utilise Minio pour le stockage d'objets compatible S3, et j'aimerais utiliser l'image du docker Minio dans mes tests d'intégration via Testcontainers .

Pour certains tests très basiques, j'exécute un GenericContainer en utilisant le image docker minio / minio et aucune configuration sauf MINIO_ACCESS_KEY et MINIO_SECRET_KEY . Mes tests utilisent ensuite le Java Client SDK de Minio. Celles-ci fonctionnent bien et se comportent comme prévu.

Mais pour d'autres tests d'intégration, je dois configurer des utilisateurs séparés dans Mino. Pour autant que je sache, les utilisateurs ne peuvent être ajoutés à Minio qu'en utilisant Admin API , pour laquelle il n'y a pas de client Java, uniquement l'image docker minio / mc (la CLI mc n'est pas disponible dans le minio / minio image docker utilisée pour le serveur).

Sur la ligne de commande, je peux utiliser l'API Admin comme ceci:

[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

Le --interactive --tty est un peu un hack pour maintenir le conteneur en cours d'exécution afin que je puisse exécuter plus tard des commandes comme celle-ci:

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());
}

Utilisation Testcontainers, j'essaie de faire la même chose comme ceci:

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

Les journaux montrent le conteneur en cours de démarrage, mais l'exécution d'une commande contre lui renvoie le code de sortie 126 et prétend qu'il est arrêté état:

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

Après avoir bidouillé avec ça pendant des heures, je suis à court d'idées. Quelqu'un peut-il aider?


0 commentaires

3 Réponses :


1
votes

Vous pouvez exécuter un conteneur unique (utilisez OneShotStartupCheckStrategy ) avec mc et withCommand ("votre commande") , connecté au même réseau que le serveur minio que vous utilisez (voir Réseau ).


0 commentaires

2
votes

Comme @bsideup l'a suggéré, vous pouvez utiliser une stratégie one-shot, c'est à dire comme dans ici . UPD: test de fonctionnement ajouté. Voici important de savoir que

Lorsque le conteneur est lancé, il exécute la commande entrypoint + (c'est Docker en général et n'a rien à voir avec Testcontainers). Source du github TC

@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");
}

Une autre option consiste à utiliser le contenu de dockerfile de minio / mc, qui est petit, modifie la commande exécutée (unique" mc "par défaut) et exécute son propre conteneur une fois par test, ce qui, par rapport à un conteneur unique, sera gagnez du temps si vous avez besoin d'exécuter plusieurs commandes:

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");
    }
}

Fondamentalement, il exécute l'image avec mc installé, et dort pendant 1h, ce qui est suffisant pour vos tests. Pendant qu'il s'exécute, vous pouvez exécuter des commandes, etc. Après avoir terminé, il est tué. Votre conteneur minio peut être dans le même réseau.


2 commentaires

Fondamentalement, ce "sommeil" est le même que vous faites en laissant le conteneur détaché et en cours d'exécution.


exemple ajouté avec des conteneurs uniques. Fonctionne sur ma machine (tm)



2
votes

Grâce à @glebsts et @bsideup, j'ai pu faire fonctionner mes tests d'intégration. Voici un exemple minimal de la façon d'ajouter un utilisateur:

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();
        }
    }
}


2 commentaires

* @ BeforeAll /! AfterAll n'a aucun sens car les conteneurs de test fonctionnent bien avec @ClassRule et vous n'avez pas besoin de nettoyage manuel * écrasement du point d'entrée - douteux, tout cela est fait à cause du collage de supercordes géantes; * La copie de la constante codée en dur depuis genericContainer est douteuse


* pourquoi auriez-vous besoin d'exposer le port? La Documentation sur la mise en réseau décrit les cas où vous pourriez avoir besoin mais ici, vous avez à la fois minio et mc fonctionnant dans des conteneurs, les mettre dans le même réseau les isolerait mieux, et vous n'avez pas besoin de vous fier à l'URL du texte lorsque vous pouvez également avoir une adresse IP interne; * votre minio n'est pas nettoyé entre les tests, env est sale. Cela peut conduire à tester les interdépendances, ou trop de douleur, c'est-à-dire à ajouter un test qui ne nécessite aucun utilisateur ajouté