All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.tbk.spring.testcontainer.eps.config.ElectrumPersonalServerContainerAutoConfiguration Maven / Gradle / Ivy

package org.tbk.spring.testcontainer.eps.config;

import com.github.dockerjava.api.command.CreateContainerCmd;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.tbk.spring.testcontainer.bitcoind.BitcoindContainer;
import org.tbk.spring.testcontainer.bitcoind.config.BitcoindContainerAutoConfiguration;
import org.tbk.spring.testcontainer.core.CustomHostPortWaitStrategy;
import org.tbk.spring.testcontainer.core.MoreTestcontainers;
import org.tbk.spring.testcontainer.eps.ElectrumPersonalServerContainer;
import org.testcontainers.Testcontainers;
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.utility.DockerImageName;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;

@Slf4j
@AutoConfiguration
@EnableConfigurationProperties(ElectrumPersonalServerContainerProperties.class)
@ConditionalOnProperty(value = "org.tbk.spring.testcontainer.eps.enabled", havingValue = "true")
@AutoConfigureAfter(BitcoindContainerAutoConfiguration.class)
public class ElectrumPersonalServerContainerAutoConfiguration {

    private static final int hardcodedRpcPort = 50002;

    private static final List hardcodedStandardPorts = ImmutableList.builder()
            .add(hardcodedRpcPort)
            .build();

    private final ElectrumPersonalServerContainerProperties properties;

    public ElectrumPersonalServerContainerAutoConfiguration(ElectrumPersonalServerContainerProperties properties) {
        this.properties = requireNonNull(properties);
    }

    @Bean(name = "electrumPersonalServerContainer", destroyMethod = "stop")
    @ConditionalOnMissingBean(ElectrumPersonalServerContainer.class)
    @ConditionalOnBean(BitcoindContainer.class)
    ElectrumPersonalServerContainer electrumPersonalServerContainerWithBitcoindTestcontainer(BitcoindContainer bitcoindContainer) {
        String epsBitcoindConfig = buildEpsBitcoindConfig(bitcoindContainer);

        return createStartedContainer(epsBitcoindConfig);
    }

    @Bean(name = "electrumPersonalServerContainer", destroyMethod = "stop")
    @ConditionalOnMissingBean(ElectrumPersonalServerContainer.class)
    ElectrumPersonalServerContainer electrumPersonalServerContainer() {
        boolean isLocalhost = "localhost".equals(this.properties.getRpchost());
        boolean isLoopback = "127.0.0.1".equals(this.properties.getRpchost());
        boolean isWildcard = "0.0.0.0".equals(this.properties.getRpchost());

        boolean connectsToHost = isLocalhost || isLoopback || isWildcard;
        if (connectsToHost) {
            Testcontainers.exposeHostPorts(this.properties.getRpcport());
        }

        String epsBitcoindConfig = connectsToHost ?
                buildLocalEpsBitcoindConfig() :
                buildEpsBitcoindConfig();

        return createStartedContainer(epsBitcoindConfig);
    }

    private WaitStrategy initPhaseWaitStrategy() {
        return new LogMessageWaitStrategy()
                // we are waiting for a message like: "INFO:2020-12-29 15:57:37,721: Done.\n"
                .withRegEx("INFO:.+: Done\\.\n")
                .withTimes(1);
    }

    private WaitStrategy mainPhaseWaitStrategy() {
        return CustomHostPortWaitStrategy.builder()
                .addPort(hardcodedRpcPort)
                .build();
    }

    private ElectrumPersonalServerContainer createStartedContainer(String epsBitcoindConfig) {

        ElectrumPersonalServerContainer initPhaseContainer = createContainer(epsBitcoindConfig)
                .withStartupTimeout(Duration.ofMinutes(3))
                .waitingFor(initPhaseWaitStrategy());

        initPhaseContainer.start();

        // electrum personal server exits after everything is set up correctly - we need to restart it again afterwards.
        initPhaseContainer.stop();

        ElectrumPersonalServerContainer mainPhaseContainer = initPhaseContainer
                .withStartupTimeout(Duration.ofMinutes(2))
                .waitingFor(mainPhaseWaitStrategy());

        // restart again - this time it should stay running
        mainPhaseContainer.start();

        // expose all mapped ports of the host so other containers can communication with the container
        MoreTestcontainers.exposeAllPortsToOtherContainers(mainPhaseContainer);

        return mainPhaseContainer;
    }

    private ElectrumPersonalServerContainer createContainer(String epsBitcoindConfig) {
        Map env = buildEnvMap(epsBitcoindConfig);

        DockerImageName dockerImageName = this.properties.getImage()
                .orElseThrow(() -> new RuntimeException("Container image must not be empty"));

        ElectrumPersonalServerContainer container = new ElectrumPersonalServerContainer<>(dockerImageName)
                .withCreateContainerCmdModifier(cmdModifier(dockerImageName))
                .withExposedPorts(hardcodedStandardPorts.toArray(new Integer[]{}))
                .withEnv(env);

        return container;
    }

    private Consumer cmdModifier(DockerImageName dockerImageName) {
        String dockerContainerName = String.format("%s-%s", dockerImageName.getUnversionedPart(),
                        Integer.toHexString(System.identityHashCode(this)))
                .replace("/", "-");

        return MoreTestcontainers.cmdModifiers().withName(dockerContainerName);
    }

    private Map buildEnvMap(String epsBitcoindConfig) {
        Map environmentWithDefaults = this.properties.getEnvironmentWithDefaults();

        String epsConfigKey = "EPS_CONFIG";

        String userGivenEpsConfig = environmentWithDefaults.getOrDefault(epsConfigKey, "");
        String enhancedEpsConfig = epsBitcoindConfig + "\n" + userGivenEpsConfig;

        Map envWithoutEpsConfig = environmentWithDefaults.entrySet().stream()
                .filter(it -> !epsConfigKey.equals(it.getKey()))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

        return ImmutableMap.builder()
                .putAll(envWithoutEpsConfig)
                .put(epsConfigKey, enhancedEpsConfig)
                .build();
    }

    private String buildLocalEpsBitcoindConfig() {
        return buildEpsBitcoindConfig(
                MoreTestcontainers.testcontainersInternalHost(),
                this.properties.getRpcport()
        );
    }

    private String buildEpsBitcoindConfig() {
        return buildEpsBitcoindConfig(
                this.properties.getRpchost(),
                this.properties.getRpcport()
        );
    }

    private String buildEpsBitcoindConfig(BitcoindContainer bitcoindContainer) {
        return buildEpsBitcoindConfig(
                MoreTestcontainers.testcontainersInternalHost(),
                bitcoindContainer.getMappedPort(this.properties.getRpcport())
        );
    }

    private String buildEpsBitcoindConfig(String host, int port) {
        String template = "[bitcoin-rpc]\n"
                          + "host = %s\n"
                          + "port = %d\n"
                          + "rpc_user = %s\n"
                          + "rpc_password = %s\n";

        return String.format(template, host, port, this.properties.getRpcuser(), this.properties.getRpcpass());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy