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

io.quarkus.devservices.deployment.DevServicesProcessor Maven / Gradle / Ivy

package io.quarkus.devservices.deployment;

import static io.quarkus.deployment.dev.testing.MessageFormat.BOLD;
import static io.quarkus.deployment.dev.testing.MessageFormat.GREEN;
import static io.quarkus.deployment.dev.testing.MessageFormat.NO_BOLD;
import static io.quarkus.deployment.dev.testing.MessageFormat.NO_UNDERLINE;
import static io.quarkus.deployment.dev.testing.MessageFormat.RED;
import static io.quarkus.deployment.dev.testing.MessageFormat.RESET;
import static io.quarkus.deployment.dev.testing.MessageFormat.UNDERLINE;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.testcontainers.DockerClientFactory;

import com.github.dockerjava.api.model.Container;
import com.github.dockerjava.api.model.ContainerNetwork;
import com.github.dockerjava.api.model.ContainerNetworkSettings;

import io.quarkus.deployment.IsDevelopment;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.ConsoleCommandBuildItem;
import io.quarkus.deployment.builditem.DevServicesLauncherConfigResultBuildItem;
import io.quarkus.deployment.builditem.DevServicesResultBuildItem;
import io.quarkus.deployment.builditem.DockerStatusBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.console.ConsoleCommand;
import io.quarkus.deployment.console.ConsoleStateManager;
import io.quarkus.deployment.dev.devservices.ContainerInfo;
import io.quarkus.deployment.dev.devservices.DevServiceDescriptionBuildItem;
import io.quarkus.dev.spi.DevModeType;
import io.quarkus.runtime.util.ContainerRuntimeUtil;
import io.quarkus.runtime.util.ContainerRuntimeUtil.ContainerRuntime;

public class DevServicesProcessor {

    private static final String EXEC_FORMAT = "%s exec -it %s /bin/bash";

    static volatile ConsoleStateManager.ConsoleContext context;
    static volatile boolean logForwardEnabled = false;
    static Map containerLogForwarders = new HashMap<>();

    @BuildStep(onlyIf = { IsDevelopment.class })
    public List config(
            DockerStatusBuildItem dockerStatusBuildItem,
            BuildProducer commandBuildItemBuildProducer,
            LaunchModeBuildItem launchModeBuildItem,
            Optional devServicesLauncherConfig,
            List devServicesResults) {
        List serviceDescriptions = buildServiceDescriptions(
                dockerStatusBuildItem, devServicesResults, devServicesLauncherConfig);

        for (DevServiceDescriptionBuildItem devService : serviceDescriptions) {
            if (devService.hasContainerInfo()) {
                containerLogForwarders.compute(devService.getContainerInfo().getId(),
                        (id, forwarder) -> Objects.requireNonNullElseGet(forwarder,
                                () -> new ContainerLogForwarder(devService)));
            }
        }

        // Build commands if we are in local dev mode
        if (launchModeBuildItem.getDevModeType().orElse(null) != DevModeType.LOCAL) {
            return serviceDescriptions;
        }

        commandBuildItemBuildProducer.produce(
                new ConsoleCommandBuildItem(new DevServicesCommand(serviceDescriptions)));

        if (context == null) {
            context = ConsoleStateManager.INSTANCE.createContext("Dev Services");
        }
        context.reset(
                new ConsoleCommand('c', "Show Dev Services containers", null, () -> {
                    List descriptions = buildServiceDescriptions(
                            dockerStatusBuildItem, devServicesResults, devServicesLauncherConfig);
                    StringBuilder builder = new StringBuilder();
                    builder.append("\n\n")
                            .append(RED + "==" + RESET + " " + UNDERLINE + "Dev Services" + NO_UNDERLINE)
                            .append("\n\n");
                    for (DevServiceDescriptionBuildItem devService : descriptions) {
                        printDevService(builder, devService, true);
                        builder.append("\n");
                    }
                    System.out.println(builder);
                }),
                new ConsoleCommand('g', "Follow Dev Services logs in the console",
                        new ConsoleCommand.HelpState(() -> logForwardEnabled ? GREEN : RED,
                                () -> logForwardEnabled ? "enabled" : "disabled"),
                        this::toggleLogForwarders));
        return serviceDescriptions;
    }

    private List buildServiceDescriptions(
            DockerStatusBuildItem dockerStatusBuildItem,
            List devServicesResults,
            Optional devServicesLauncherConfig) {
        // Fetch container infos
        Set containerIds = devServicesResults.stream()
                .map(DevServicesResultBuildItem::getContainerId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        Map containerInfos = fetchContainerInfos(dockerStatusBuildItem, containerIds);
        // Build descriptions
        Set configKeysFromDevServices = new HashSet<>();
        List descriptions = new ArrayList<>();
        for (DevServicesResultBuildItem buildItem : devServicesResults) {
            configKeysFromDevServices.addAll(buildItem.getConfig().keySet());
            descriptions.add(toDevServiceDescription(buildItem, containerInfos.get(buildItem.getContainerId())));
        }
        // Sort descriptions by name
        descriptions.sort(Comparator.comparing(DevServiceDescriptionBuildItem::getName));
        // Add description from other dev service configs as last
        if (devServicesLauncherConfig.isPresent()) {
            Map config = new TreeMap<>(devServicesLauncherConfig.get().getConfig());
            for (String key : configKeysFromDevServices) {
                config.remove(key);
            }
            if (!config.isEmpty()) {
                descriptions.add(new DevServiceDescriptionBuildItem("Additional Dev Services config", null, config));
            }
        }
        return descriptions;
    }

    private Map fetchContainerInfos(DockerStatusBuildItem dockerStatusBuildItem,
            Set containerIds) {
        if (containerIds.isEmpty() || !dockerStatusBuildItem.isDockerAvailable()) {
            return Collections.emptyMap();
        }
        return DockerClientFactory.lazyClient().listContainersCmd()
                .withIdFilter(containerIds)
                .withShowAll(true)
                .exec()
                .stream()
                .collect(Collectors.toMap(Container::getId, Function.identity()));
    }

    private DevServiceDescriptionBuildItem toDevServiceDescription(DevServicesResultBuildItem buildItem, Container container) {
        if (container == null) {
            return new DevServiceDescriptionBuildItem(buildItem.getName(), null, buildItem.getConfig());
        } else {
            return new DevServiceDescriptionBuildItem(buildItem.getName(), toContainerInfo(container), buildItem.getConfig());
        }
    }

    private ContainerInfo toContainerInfo(Container container) {
        return new ContainerInfo(container.getId(), container.getNames(), container.getImage(),
                container.getStatus(), getNetworks(container), container.getLabels(), getExposedPorts(container));
    }

    private static String[] getNetworks(Container container) {
        ContainerNetworkSettings networkSettings = container.getNetworkSettings();
        if (networkSettings == null) {
            return null;
        }
        Map networks = networkSettings.getNetworks();
        if (networks == null) {
            return null;
        }
        return networks.entrySet().stream()
                .map(e -> {
                    List aliases = e.getValue().getAliases();
                    if (aliases == null) {
                        return e.getKey();
                    }
                    return e.getKey() + " (" + String.join(",", aliases) + ")";
                })
                .toArray(String[]::new);
    }

    private ContainerInfo.ContainerPort[] getExposedPorts(Container container) {
        return Arrays.stream(container.getPorts())
                .map(c -> new ContainerInfo.ContainerPort(c.getIp(), c.getPrivatePort(), c.getPublicPort(), c.getType()))
                .toArray(ContainerInfo.ContainerPort[]::new);
    }

    private synchronized void toggleLogForwarders() {
        if (logForwardEnabled) {
            for (ContainerLogForwarder logForwarder : containerLogForwarders.values()) {
                if (logForwarder.isRunning()) {
                    logForwarder.close();
                }
            }
            logForwardEnabled = false;
        } else {
            for (ContainerLogForwarder logForwarder : containerLogForwarders.values()) {
                logForwarder.start();
            }
            logForwardEnabled = true;
        }
    }

    public static void printDevService(StringBuilder builder, DevServiceDescriptionBuildItem devService, boolean withStatus) {
        builder.append(BOLD).append(devService.getName()).append(NO_BOLD);
        builder.append("\n");

        if (devService.hasContainerInfo()) {
            builder.append(String.format("  %-18s", "Container: "))
                    .append(devService.getContainerInfo().getId(), 0, 12)
                    .append(devService.getContainerInfo().formatNames())
                    .append("  ")
                    .append(devService.getContainerInfo().getImageName())
                    .append("\n");
            builder.append(String.format("  %-18s", "Network: "))
                    .append(devService.getContainerInfo().formatNetworks())
                    .append(" - ")
                    .append(devService.getContainerInfo().formatPorts())
                    .append("\n");

            ContainerRuntime containerRuntime = ContainerRuntimeUtil.detectContainerRuntime(false);
            if (containerRuntime != null) {
                builder.append(String.format("  %-18s", "Exec command: "))
                        .append(String.format(EXEC_FORMAT,
                                containerRuntime.getExecutableName(),
                                devService.getContainerInfo().getShortId()))
                        .append("\n");
            }
        }

        if (!devService.getConfigs().isEmpty()) {
            builder.append(String.format("  %-18s", "Injected config: "));
            boolean indent = false;
            for (Entry devServiceConfigEntry : devService.getConfigs().entrySet()) {
                if (indent) {
                    builder.append(String.format("  %-18s", " "));
                }
                builder.append(
                        String.format("- %s=%s\n",
                                devServiceConfigEntry.getKey(), devServiceConfigEntry.getValue()));
                indent = true;
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy