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

io.quarkiverse.unleash.devservices.UnleashDevServiceProcessor Maven / Gradle / Ivy

The newest version!
package io.quarkiverse.unleash.devservices;

import static io.quarkiverse.unleash.UnleashProcessor.FEATURE_NAME;
import static io.quarkus.runtime.LaunchMode.DEVELOPMENT;

import java.io.Closeable;
import java.time.Duration;
import java.util.*;
import java.util.function.Supplier;

import org.jboss.logging.Logger;
import org.testcontainers.containers.BindMode;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.testcontainers.utility.DockerImageName;

import io.quarkiverse.unleash.UnleashBuildTimeConfig;
import io.quarkus.deployment.IsNormal;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.*;
import io.quarkus.deployment.console.ConsoleInstalledBuildItem;
import io.quarkus.deployment.console.StartupLogCompressor;
import io.quarkus.deployment.dev.devservices.GlobalDevServicesConfig;
import io.quarkus.deployment.logging.LoggingSetupBuildItem;
import io.quarkus.devservices.common.ConfigureUtil;
import io.quarkus.devservices.common.ContainerAddress;
import io.quarkus.devservices.common.ContainerLocator;
import io.quarkus.runtime.configuration.ConfigUtils;

public class UnleashDevServiceProcessor {

    private static final Logger log = Logger.getLogger(UnleashDevServiceProcessor.class);

    private static final String DEFAULT_DOCKER_IMAGE = "unleashorg/unleash-server:5.9.3";
    private static final String IMPORT_FILE_PATH = "/tmp/unleash-import-file.yml";
    public static final String PROP_UNLEASH_URL = "quarkus.unleash.url";
    private static final String DEV_SERVICE_LABEL = "quarkus-dev-service-unleash";
    public static final int DEFAULT_UNLEASH_PORT = 4242;
    private static final ContainerLocator unleashContainerLocator = new ContainerLocator(DEV_SERVICE_LABEL,
            DEFAULT_UNLEASH_PORT);
    static volatile UnleashRunningDevService devService;
    static volatile UnleashDevServiceCfg cfg;
    static volatile boolean first = true;

    @BuildStep(onlyIfNot = IsNormal.class, onlyIf = { GlobalDevServicesConfig.Enabled.class })
    public DevServicesResultBuildItem startUnleashContainers(LaunchModeBuildItem launchMode,
            List devServicesSharedNetworkBuildItem,
            UnleashBuildTimeConfig buildTimeConfig,
            UnleashDbDevServicesProviderBuildItem dbSettings,
            Optional consoleInstalledBuildItem,
            CuratedApplicationShutdownBuildItem closeBuildItem,
            DockerStatusBuildItem dockerStatusBuildItem,
            LoggingSetupBuildItem loggingSetupBuildItem, GlobalDevServicesConfig devServicesConfig) {

        UnleashDevServiceCfg configuration = getConfiguration(buildTimeConfig);

        if (devService != null) {
            boolean shouldShutdownTheBroker = !configuration.equals(cfg);
            if (!shouldShutdownTheBroker) {
                return devService.toBuildItem();
            }
            stopContainer();
            cfg = null;
        }
        StartupLogCompressor compressor = new StartupLogCompressor(
                (launchMode.isTest() ? "(test) " : "") + "Unleash Dev Services Starting:",
                consoleInstalledBuildItem, loggingSetupBuildItem);
        try {
            devService = startContainer(dockerStatusBuildItem, configuration, dbSettings, launchMode,
                    !devServicesSharedNetworkBuildItem.isEmpty(),
                    devServicesConfig.timeout);

            if (devService == null) {
                compressor.closeAndDumpCaptured();
            } else {
                compressor.close();
            }
        } catch (Throwable t) {
            compressor.closeAndDumpCaptured();
            throw new RuntimeException(t);
        }

        if (devService == null) {
            return null;
        }

        // Configure the watch dog
        if (first) {
            first = false;
            Runnable closeTask = () -> {
                if (devService != null) {
                    stopContainer();
                }
                first = true;
                devService = null;
                cfg = null;
            };
            closeBuildItem.addCloseTask(closeTask, true);
        }
        cfg = configuration;

        if (devService.isOwner()) {
            String tmp = devService.getConfig().get(PROP_UNLEASH_URL);
            log.infof("The unleash is ready to accept connections on %s", tmp);
            log.infof("The unleash admin URL %s", tmp.replaceAll("api", ""));
        }

        return devService.toBuildItem();
    }

    private UnleashRunningDevService startContainer(DockerStatusBuildItem dockerStatusBuildItem,
            UnleashDevServiceCfg config, UnleashDbDevServicesProviderBuildItem dbSettings,
            LaunchModeBuildItem launchMode, boolean useSharedNetwork, Optional timeout) {

        if (!config.devServicesEnabled) {
            // explicitly disabled
            log.debug("Not starting dev services for Unleash as it has been disabled in the config");
            return null;
        }

        if (ConfigUtils.isPropertyPresent(PROP_UNLEASH_URL)) {
            log.debug("Not starting dev services for Unleash as '" + PROP_UNLEASH_URL + "' have been provided");
            return null;
        }

        if (!dockerStatusBuildItem.isContainerRuntimeAvailable()) {
            log.warn(
                    "Docker isn't working, please configure the unleash URL property ("
                            + PROP_UNLEASH_URL + ").");
            return null;
        }

        final Optional maybeContainerAddress = unleashContainerLocator.locateContainer(config.serviceName,
                config.shared,
                launchMode.getLaunchMode());

        // Starting to unleash service
        final Supplier defaultUnleashSupplier = () -> {

            DockerImageName image = DockerImageName.parse(config.imageName);

            UnleashContainer container = new UnleashContainer(
                    image,
                    config.fixedExposedPort,
                    launchMode.getLaunchMode() == DEVELOPMENT ? config.serviceName : null,
                    useSharedNetwork,
                    config.importFile,
                    dbSettings);
            timeout.ifPresent(container::withStartupTimeout);

            // enabled or disable container logs
            if (config.log) {
                container.withLogConsumer(ContainerLogger.create(config.serviceName));
            }

            // enable test-container reuse
            if (config.reuse) {
                container.withReuse(true);
            }

            container.start();

            String adminClient = container.getExternalAddress(DEFAULT_UNLEASH_PORT);

            return new UnleashRunningDevService(FEATURE_NAME, container.getContainerId(),
                    new ContainerShutdownCloseable(container, FEATURE_NAME),
                    configMap(container.getUrl(), adminClient));
        };

        return maybeContainerAddress
                .map(containerAddress -> new UnleashRunningDevService(FEATURE_NAME,
                        containerAddress.getId(),
                        null, configMap(containerAddress.getUrl(), null)))
                .orElseGet(defaultUnleashSupplier);
    }

    private static Map configMap(String url, String adminClient) {
        Map config = new HashMap<>();
        config.put(PROP_UNLEASH_URL, url);
        if (adminClient != null) {
            config.put("quarkiverse.unleash.devservices.admin.url", adminClient);
        }
        return config;
    }

    private void stopContainer() {
        if (devService != null) {
            try {
                devService.close();
            } catch (Throwable e) {
                log.error("Failed to stop the Zeebe broker", e);
            } finally {
                devService = null;
            }
        }
    }

    public static class UnleashRunningDevService extends DevServicesResultBuildItem.RunningDevService {

        public UnleashRunningDevService(String name, String containerId, Closeable closeable, Map config) {
            super(name, containerId, closeable, config);
        }
    }

    private UnleashDevServiceCfg getConfiguration(UnleashBuildTimeConfig cfg) {
        UnleashDevServicesConfig devServicesConfig = cfg.devService();
        return new UnleashDevServiceCfg(devServicesConfig);
    }

    private static final class UnleashDevServiceCfg {

        private final boolean devServicesEnabled;
        private final String imageName;
        private final Integer fixedExposedPort;
        private final boolean shared;
        private final String serviceName;

        private final String importFile;

        private final boolean reuse;

        private final boolean log;

        public UnleashDevServiceCfg(UnleashDevServicesConfig config) {
            this.devServicesEnabled = config.enabled();
            this.imageName = config.imageName().orElse(DEFAULT_DOCKER_IMAGE);
            this.fixedExposedPort = config.port().orElse(0);
            this.shared = config.shared();
            this.serviceName = config.serviceName();
            this.reuse = config.reuse();
            this.importFile = config.importFile().orElse(null);
            this.log = config.log();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }
            UnleashDevServiceCfg that = (UnleashDevServiceCfg) o;
            return devServicesEnabled == that.devServicesEnabled && Objects.equals(imageName, that.imageName)
                    && Objects.equals(fixedExposedPort, that.fixedExposedPort);
        }

        @Override
        public int hashCode() {
            return Objects.hash(devServicesEnabled, imageName, fixedExposedPort);
        }
    }

    private static class UnleashContainer extends GenericContainer {

        private final boolean useSharedNetwork;
        private final int fixedExposedPort;

        private String hostName = null;

        public UnleashContainer(DockerImageName image, int fixedExposedPort, String serviceName,
                boolean useSharedNetwork, String importFile, UnleashDbDevServicesProviderBuildItem dbSettings) {
            super(image);
            log.debugf("Unleash docker image %s", image);
            this.fixedExposedPort = fixedExposedPort;
            this.useSharedNetwork = useSharedNetwork;

            this.withExposedPorts(DEFAULT_UNLEASH_PORT);

            // set up the database for unleash service
            this.withEnv("DATABASE_HOST", dbSettings.host)
                    .withEnv("LOG_LEVEL", "info")
                    .withEnv("DATABASE_NAME", dbSettings.name)
                    .withEnv("DATABASE_USERNAME", dbSettings.username)
                    .withEnv("DATABASE_PASSWORD", dbSettings.password)
                    .withEnv("DATABASE_PORT", "" + dbSettings.port)
                    .withEnv("AUTH_TYPE", "none")
                    .withEnv("DATABASE_SSL", "false");

            if (serviceName != null) {
                this.withLabel(DEV_SERVICE_LABEL, serviceName);
            }

            // import data file
            if (importFile != null) {
                log.infof("Unleash startup import file: %s", importFile);
                this.withEnv("IMPORT_FILE", IMPORT_FILE_PATH);
                this.withFileSystemBind(importFile, IMPORT_FILE_PATH, BindMode.READ_ONLY);
            }

            // wait for start
            this.waitingFor(Wait.forHttp("/"));
        }

        public String getExternalAddress(final int port) {
            return this.getHost() + ":" + this.getMappedPort(port);
        }

        @Override
        protected void configure() {
            super.configure();

            if (useSharedNetwork) {
                hostName = ConfigureUtil.configureSharedNetwork(this, "unleash");
                return;
            } else {
                withNetwork(Network.SHARED);
            }

            if (fixedExposedPort > 0) {
                addFixedExposedPort(fixedExposedPort, DEFAULT_UNLEASH_PORT);
            } else {
                addExposedPort(DEFAULT_UNLEASH_PORT);
            }
        }

        public int getPort() {
            if (useSharedNetwork) {
                return DEFAULT_UNLEASH_PORT;
            }
            if (fixedExposedPort > 0) {
                return fixedExposedPort;
            }
            return super.getFirstMappedPort();
        }

        public String getUnleashHost() {
            return useSharedNetwork ? hostName : super.getHost();
        }

        public String getUrl() {
            return String.format("http://%s:%d/api", this.getUnleashHost(), this.getPort());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy