diff --git a/pom.xml b/pom.xml index 7bdf6cc..a6574a0 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ jar testcontainers-annotations - 1.0.5 + 1.0.6 https://github.com/rbkmoney/testcontainers-annotations diff --git a/src/main/java/com/rbkmoney/testcontainers/annotations/ceph/CephTestcontainerExtension.java b/src/main/java/com/rbkmoney/testcontainers/annotations/ceph/CephTestcontainerExtension.java index 9479b96..c00f92e 100644 --- a/src/main/java/com/rbkmoney/testcontainers/annotations/ceph/CephTestcontainerExtension.java +++ b/src/main/java/com/rbkmoney/testcontainers/annotations/ceph/CephTestcontainerExtension.java @@ -124,8 +124,8 @@ public class CephTestcontainerExtension String[] properties) { var container = THREAD_CONTAINER.get(); TestPropertyValues.of( - "storage.endpoint=" + container.getContainerIpAddress() + ":" - + container.getMappedPort(8080), + "storage.endpoint=" + container.getContainerIpAddress() + ":" + + container.getMappedPort(8080), "storage.signingRegion=" + signingRegion, "storage.accessKey=" + loadDefaultLibraryProperty(ACCESS_KEY), "storage.secretKey=" + loadDefaultLibraryProperty(SECRET_KEY), diff --git a/src/main/java/com/rbkmoney/testcontainers/annotations/ceph/CephTestcontainerFactory.java b/src/main/java/com/rbkmoney/testcontainers/annotations/ceph/CephTestcontainerFactory.java index 2e2893f..3355030 100644 --- a/src/main/java/com/rbkmoney/testcontainers/annotations/ceph/CephTestcontainerFactory.java +++ b/src/main/java/com/rbkmoney/testcontainers/annotations/ceph/CephTestcontainerFactory.java @@ -4,12 +4,11 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; import lombok.Synchronized; import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitStrategy; import org.testcontainers.utility.DockerImageName; import java.time.Duration; +import static com.rbkmoney.testcontainers.annotations.util.GenericContainerUtil.getWaitStrategy; import static com.rbkmoney.testcontainers.annotations.util.SpringApplicationPropertiesLoader.loadDefaultLibraryProperty; @NoArgsConstructor(access = AccessLevel.PRIVATE) @@ -56,23 +55,11 @@ public class CephTestcontainerFactory { .withEnv("CEPH_DEMO_ACCESS_KEY", loadDefaultLibraryProperty(ACCESS_KEY)) .withEnv("CEPH_DEMO_SECRET_KEY", loadDefaultLibraryProperty(SECRET_KEY)) .withEnv("CEPH_DEMO_BUCKET", "TEST") - .waitingFor(cephHealthCheck())) { + .waitingFor(getWaitStrategy("/api/v0.1/health", 200, 5000, Duration.ofMinutes(1)))) { return container; } } - private WaitStrategy cephHealthCheck() { - return getWaitStrategy("/api/v0.1/health", 200, 5000, Duration.ofMinutes(1)); - } - - private static WaitStrategy getWaitStrategy(String path, Integer statusCode, Integer port, Duration duration) { - return new HttpWaitStrategy() - .forPath(path) - .forPort(port) - .forStatusCode(statusCode) - .withStartupTimeout(duration); - } - private static class SingletonHolder { private static final CephTestcontainerFactory INSTANCE = new CephTestcontainerFactory(); diff --git a/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainer.java b/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainer.java new file mode 100644 index 0000000..c626ef6 --- /dev/null +++ b/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainer.java @@ -0,0 +1,28 @@ +package com.rbkmoney.testcontainers.annotations.minio; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(MinioTestcontainerExtension.class) +public @interface MinioTestcontainer { + + /** + * properties = {"postgresql.make.happy=true",...} + */ + String[] properties() default {}; + + String signingRegion() default "RU"; + + String clientProtocol() default "HTTP"; + + String clientMaxErrorRetry() default "10"; + + String bucketName() default "TEST"; + +} diff --git a/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerExtension.java b/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerExtension.java new file mode 100644 index 0000000..ab0aac6 --- /dev/null +++ b/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerExtension.java @@ -0,0 +1,139 @@ +package com.rbkmoney.testcontainers.annotations.minio; + +import lombok.extern.slf4j.Slf4j; +import lombok.var; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestInstancePostProcessor; +import org.junit.platform.commons.support.AnnotationSupport; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.testcontainers.containers.GenericContainer; + +import java.util.List; +import java.util.Optional; + +import static com.rbkmoney.testcontainers.annotations.minio.MinioTestcontainerFactory.MINIO_PASSWORD; +import static com.rbkmoney.testcontainers.annotations.minio.MinioTestcontainerFactory.MINIO_USER; +import static com.rbkmoney.testcontainers.annotations.util.GenericContainerUtil.startContainer; +import static com.rbkmoney.testcontainers.annotations.util.SpringApplicationPropertiesLoader.loadDefaultLibraryProperty; + +@Slf4j +public class MinioTestcontainerExtension + implements TestInstancePostProcessor, BeforeAllCallback, AfterAllCallback { + + private static final ThreadLocal> THREAD_CONTAINER = new ThreadLocal<>(); + + @Override + public void postProcessTestInstance(Object testInstance, ExtensionContext context) { + var annotation = findMinioTestcontainerSingletonAnnotation(context); + if (!annotation.isPresent()) { + return; + } + var container = MinioTestcontainerFactory.singletonContainer(); + if (!container.isRunning()) { + startContainer(container); + } + THREAD_CONTAINER.set(container); + } + + @Override + public void beforeAll(ExtensionContext context) { + var annotation = findMinioTestcontainerAnnotation(context); + if (!annotation.isPresent()) { + return; + } + var container = MinioTestcontainerFactory.container(); + if (!container.isRunning()) { + startContainer(container); + } + THREAD_CONTAINER.set(container); + } + + @Override + public void afterAll(ExtensionContext context) { + if (findMinioTestcontainerAnnotation(context).isPresent()) { + var container = THREAD_CONTAINER.get(); + if (container != null && container.isRunning()) { + container.stop(); + } + THREAD_CONTAINER.remove(); + } else if (findMinioTestcontainerSingletonAnnotation(context).isPresent()) { + THREAD_CONTAINER.remove(); + } + } + + private static Optional findMinioTestcontainerAnnotation(ExtensionContext context) { + return AnnotationSupport.findAnnotation(context.getElement(), MinioTestcontainer.class); + } + + private static Optional findMinioTestcontainerAnnotation(Class testClass) { + return AnnotationSupport.findAnnotation(testClass, MinioTestcontainer.class); + } + + private static Optional findMinioTestcontainerSingletonAnnotation( + ExtensionContext context) { + return AnnotationSupport.findAnnotation(context.getElement(), MinioTestcontainerSingleton.class); + } + + private static Optional findMinioTestcontainerSingletonAnnotation( + Class testClass) { + return AnnotationSupport.findAnnotation(testClass, MinioTestcontainerSingleton.class); + } + + public static class MinioTestcontainerContextCustomizerFactory implements ContextCustomizerFactory { + + @Override + public ContextCustomizer createContextCustomizer( + Class testClass, + List configAttributes) { + return (context, mergedConfig) -> { + var minioTestcontainerAnnotation = findMinioTestcontainerAnnotation(testClass); + if (minioTestcontainerAnnotation.isPresent()) { + var annotation = minioTestcontainerAnnotation.get(); + init( + context, + annotation.signingRegion(), + annotation.clientProtocol(), + annotation.clientMaxErrorRetry(), + annotation.bucketName(), + annotation.properties()); + } else { + findMinioTestcontainerSingletonAnnotation(testClass).ifPresent( + annotation -> init( + context, + annotation.signingRegion(), + annotation.clientProtocol(), + annotation.clientMaxErrorRetry(), + annotation.bucketName(), + annotation.properties())); + } + }; + } + + private void init( + ConfigurableApplicationContext context, + String signingRegion, + String clientProtocol, + String clientMaxErrorRetry, + String bucketName, + String[] properties) { + var container = THREAD_CONTAINER.get(); + TestPropertyValues.of( + "storage.endpoint=" + container.getContainerIpAddress() + ":" + + container.getMappedPort(9000), + "storage.signingRegion=" + signingRegion, + "storage.accessKey=" + loadDefaultLibraryProperty(MINIO_USER), + "storage.secretKey=" + loadDefaultLibraryProperty(MINIO_PASSWORD), + "storage.clientProtocol=" + clientProtocol, + "storage.clientMaxErrorRetry=" + clientMaxErrorRetry, + "storage.bucketName=" + bucketName) + .and(properties) + .applyTo(context); + } + } +} diff --git a/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerFactory.java b/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerFactory.java new file mode 100644 index 0000000..06b11d4 --- /dev/null +++ b/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerFactory.java @@ -0,0 +1,63 @@ +package com.rbkmoney.testcontainers.annotations.minio; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import lombok.Synchronized; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + +import java.time.Duration; + +import static com.rbkmoney.testcontainers.annotations.util.GenericContainerUtil.getWaitStrategy; +import static com.rbkmoney.testcontainers.annotations.util.SpringApplicationPropertiesLoader.loadDefaultLibraryProperty; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class MinioTestcontainerFactory { + + public static final String MINIO_USER = "testcontainers.minio.user"; + public static final String MINIO_PASSWORD = "testcontainers.minio.password"; + private static final String MINIO_IMAGE_NAME = "minio/minio"; + private static final String TAG_PROPERTY = "testcontainers.minio.tag"; + + private GenericContainer minioContainer; + + public static GenericContainer container() { + return instance().create(); + } + + public static GenericContainer singletonContainer() { + return instance().getOrCreateSingletonContainer(); + } + + private static MinioTestcontainerFactory instance() { + return SingletonHolder.INSTANCE; + } + + @Synchronized + private GenericContainer getOrCreateSingletonContainer() { + if (minioContainer != null) { + return minioContainer; + } + minioContainer = create(); + return minioContainer; + } + + private GenericContainer create() { + try (GenericContainer container = new GenericContainer<>( + DockerImageName + .parse(MINIO_IMAGE_NAME) + .withTag(loadDefaultLibraryProperty(TAG_PROPERTY))) + .withNetworkAliases("minio") + .withEnv("MINIO_ROOT_USER", loadDefaultLibraryProperty(MINIO_USER)) + .withEnv("MINIO_ROOT_PASSWORD", loadDefaultLibraryProperty(MINIO_PASSWORD)) + .waitingFor(getWaitStrategy("/minio/health/live", 200, 9000, Duration.ofMinutes(1)))) { + return container; + } + } + + private static class SingletonHolder { + + private static final MinioTestcontainerFactory INSTANCE = new MinioTestcontainerFactory(); + + } +} diff --git a/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerSingleton.java b/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerSingleton.java new file mode 100644 index 0000000..907ee4c --- /dev/null +++ b/src/main/java/com/rbkmoney/testcontainers/annotations/minio/MinioTestcontainerSingleton.java @@ -0,0 +1,30 @@ +package com.rbkmoney.testcontainers.annotations.minio; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.transaction.annotation.Transactional; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +@ExtendWith(MinioTestcontainerExtension.class) +@Transactional +public @interface MinioTestcontainerSingleton { + + /** + * properties = {"postgresql.make.happy=true",...} + */ + String[] properties() default {}; + + String signingRegion() default "RU"; + + String clientProtocol() default "HTTP"; + + String clientMaxErrorRetry() default "10"; + + String bucketName() default "TEST"; + +} diff --git a/src/main/java/com/rbkmoney/testcontainers/annotations/util/GenericContainerUtil.java b/src/main/java/com/rbkmoney/testcontainers/annotations/util/GenericContainerUtil.java index 37c7abf..d86ac8b 100644 --- a/src/main/java/com/rbkmoney/testcontainers/annotations/util/GenericContainerUtil.java +++ b/src/main/java/com/rbkmoney/testcontainers/annotations/util/GenericContainerUtil.java @@ -1,8 +1,11 @@ package com.rbkmoney.testcontainers.annotations.util; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.containers.wait.strategy.WaitStrategy; import org.testcontainers.lifecycle.Startables; +import java.time.Duration; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThat; @@ -15,4 +18,12 @@ public class GenericContainerUtil { assertThat(container.isRunning()) .isTrue(); } + + public static WaitStrategy getWaitStrategy(String path, Integer statusCode, Integer port, Duration duration) { + return new HttpWaitStrategy() + .forPath(path) + .forPort(port) + .forStatusCode(statusCode) + .withStartupTimeout(duration); + } } diff --git a/src/main/resources/META-INF/spring.factories b/src/main/resources/META-INF/spring.factories index fe35cd1..39c4e02 100644 --- a/src/main/resources/META-INF/spring.factories +++ b/src/main/resources/META-INF/spring.factories @@ -3,4 +3,5 @@ org.springframework.test.context.ContextCustomizerFactory=\ com.rbkmoney.testcontainers.annotations.postgresql.PostgresqlTestcontainerExtension.PostgresqlTestcontainerContextCustomizerFactory,\ com.rbkmoney.testcontainers.annotations.kafka.KafkaTestcontainerExtension.KafkaTestcontainerContextCustomizerFactory,\ com.rbkmoney.testcontainers.annotations.clickhouse.ClickhouseTestcontainerExtension.ClickhouseTestcontainerContextCustomizerFactory,\ -com.rbkmoney.testcontainers.annotations.ceph.CephTestcontainerExtension.CephTestcontainerContextCustomizerFactory +com.rbkmoney.testcontainers.annotations.ceph.CephTestcontainerExtension.CephTestcontainerContextCustomizerFactory,\ +com.rbkmoney.testcontainers.annotations.minio.MinioTestcontainerExtension.MinioTestcontainerContextCustomizerFactory diff --git a/src/main/resources/testcontainers-annotations.yml b/src/main/resources/testcontainers-annotations.yml index 16733ea..45321fb 100644 --- a/src/main/resources/testcontainers-annotations.yml +++ b/src/main/resources/testcontainers-annotations.yml @@ -9,3 +9,7 @@ testcontainers: tag: 'v3.0.5-stable-3.0-luminous-centos-7' accessKey: 'test' secretKey: 'test' + minio: + tag: 'latest' + user: 'user' + password: 'password'