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

com.avast.gradle.dockercompose.DockerExecutor.groovy Maven / Gradle / Ivy

The newest version!
package com.avast.gradle.dockercompose

import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.process.ExecOperations
import org.gradle.process.ExecSpec
import org.yaml.snakeyaml.Yaml

import javax.inject.Inject

class DockerExecutor {
    private final ComposeSettings settings
    private final ExecOperations exec

    private static final Logger logger = Logging.getLogger(DockerExecutor.class);

    @Inject
    DockerExecutor(ComposeSettings settings, ExecOperations exec) {
        this.settings = settings
        this.exec = exec
    }

    String execute(String... args) {
        def exec = this.exec
        def settings = this.settings
        new ByteArrayOutputStream().withStream { os ->
            def er = exec.exec { ExecSpec e ->
                e.environment = System.getenv() + settings.environment.get()
                def finalArgs = [settings.dockerExecutable.get()]
                finalArgs.addAll(args)
                e.commandLine finalArgs
                e.standardOutput = os
                e.ignoreExitValue = true
            }
            def stdout = os.toString().trim()
            if (er.exitValue != 0) {
                throw new RuntimeException("Exit-code ${er.exitValue} when calling ${settings.dockerExecutable.get()}, stdout: $stdout")
            }
            stdout
        }
    }

    List getDockerInfo() {
        def asString = execute('info')
        logger.debug("Docker info: $asString")
        asString.readLines()
    }

    String getDockerPlatform() {
        String osType = getDockerInfo().collect { it.trim() }.find { it.startsWith('OSType:') }
        osType == null || osType.empty ? System.getProperty("os.name") : osType.substring('OSType:'.length()).trim()
    }

    String getContainerPlatform(Map inspection) {
        def platform = inspection.Platform as String
        platform ?: getDockerPlatform()
    }

    Map getInspection(String containerId) {
        getInspections(containerId).values().find()
    }

    Map> getInspections(String... containersIds) {
        def asString = execute(*['inspect', *containersIds])
        logger.debug("Inspections for containers ${containersIds.join(', ')}: $asString")
        Map[] inspections = new Yaml().load(asString)
        def r = inspections.collectEntries { [it.Id, it] }
        def notFoundInspections = containersIds.findAll { !r.containsKey(it) }
        if (notFoundInspections) {
            throw new RuntimeException('docker inspect didn\'t return inspection for these containers: ' + notFoundInspections.join(', '))
        }
        r
    }

    Map getNetworkInspection(String networkName) {
        def asString = execute('network', 'inspect', networkName)
        logger.debug("Inspection for network $networkName: $asString")
        (new Yaml().load(asString))[0] as Map
    }

    String getNetworkGateway(String networkName) {
        def networkInspection = getNetworkInspection(networkName)
        if (networkInspection) {
            Map ipam = networkInspection.IPAM
            if (ipam) {
                Map[] ipamConfig = ipam.Config
                if (ipamConfig && ipamConfig.size() > 0) {
                    return ipamConfig[0].Gateway
                }
            }
        }
        null
    }

    String getNetworkDriver(String networkName) {
        def networkInspection = getNetworkInspection(networkName)
        networkInspection ? networkInspection.Driver as String : ""
    }

    String getContainerLogs(String containerId) {
        execute('logs', '--follow=false', containerId)
    }

    ServiceHost getContainerHost(Map inspection, String serviceName, Logger logger = this.logger) {
        String servicesHost = settings.environment.get()['SERVICES_HOST'] ?: System.getenv('SERVICES_HOST')
        if (servicesHost) {
            logger.lifecycle("SERVICES_HOST environment variable detected - will be used as hostname of service $serviceName ($servicesHost)'")
            return new ServiceHost(host: servicesHost, type: ServiceHostType.RemoteDockerHost)
        }
        String dockerHost = settings.environment.get()['DOCKER_HOST'] ?: System.getenv('DOCKER_HOST')
        if (dockerHost) {
            def host = dockerHost.toURI().host ?: 'localhost'
            logger.lifecycle("DOCKER_HOST environment variable detected - will be used as hostname of service $serviceName ($host)'")
            return new ServiceHost(host: host, type: ServiceHostType.RemoteDockerHost)
        }
        if (isWSL()) {
            return new ServiceHost(host: 'localhost', type: ServiceHostType.LocalHost)
        }
        Map networkSettings = inspection.NetworkSettings
        Map networks = networkSettings.Networks
        Map.Entry firstNetworkPair = networks.find()
        if (isWindows() && getContainerPlatform(inspection).toLowerCase().contains('win') && firstNetworkPair && "nat".equalsIgnoreCase(getNetworkDriver(firstNetworkPair.key))) {
            logger.lifecycle("Will use direct access to the container of $serviceName")
            return new ServiceHost(host: firstNetworkPair.value.IPAddress, type: ServiceHostType.DirectContainerAccess)
        }
        if (isMac() || isWindows()) {
            logger.lifecycle("Will use localhost as host of $serviceName")
            return new ServiceHost(host: 'localhost', type: ServiceHostType.LocalHost)
        }
        String networkMode = (String)inspection.HostConfig.NetworkMode ?: ''
        if (networkMode.startsWith('container:')) {
            String linkedContainerId = networkMode.substring('container:'.length())
            logger.lifecycle("Reading container host of $serviceName from linked container $linkedContainerId")
            return getContainerHost(getInspection(linkedContainerId), linkedContainerId, logger)
        }
        String gateway
        if (networks && networks.every { it.key.toLowerCase().equals("host") }) {
            gateway = 'localhost'
            logger.lifecycle("Will use $gateway as host of $serviceName because it is using HOST network")
            return new ServiceHost(host: gateway, type: ServiceHostType.Host)
        } else if (networks && networks.size() > 0) {
            gateway = firstNetworkPair.value.Gateway
            if (!gateway) {
                logger.lifecycle("Gateway cannot be read from container inspection - trying to read from network inspection (network '${firstNetworkPair.key}')")
                gateway = getNetworkGateway(firstNetworkPair.key)
            }
            logger.lifecycle("Will use $gateway (network ${firstNetworkPair.key}) as host of $serviceName")
            return new ServiceHost(host: gateway, type: ServiceHostType.NetworkGateway)
        }
        if (networkSettings.Gateway) { // networks not specified (older Docker versions)
            gateway = networkSettings.Gateway
            logger.lifecycle("Will use $gateway as host of $serviceName")
            return new ServiceHost(host: gateway, type: ServiceHostType.NetworkGateway)
        }
        logger.warn("Will use 'localhost' as host of $serviceName (as a fallback)")
        return new ServiceHost(host: 'localhost', type: ServiceHostType.LocalHost)
    }

    Map getTcpPortsMapping(String serviceName, Map inspection, ServiceHost host) {
        getPortsMapping("TCP", serviceName, inspection, host)
    }

    Map getUdpPortsMapping(String serviceName, Map inspection, ServiceHost host) {
        getPortsMapping("UDP", serviceName, inspection, host)
    }

    Map getPortsMapping(String protocol, String serviceName, Map inspection, ServiceHost host) {
        Map ports = [:]
        inspection.NetworkSettings.Ports.each { String exposedPortWithProtocol, forwardedPortsInfos ->
            def (String exposedPortAsString, String pr) = exposedPortWithProtocol.split('/')
            if (!protocol.equalsIgnoreCase(pr)) {
                return // from closure
            }
            int exposedPort = exposedPortAsString as int
            if (!forwardedPortsInfos || forwardedPortsInfos.isEmpty()) {
                logger.debug("No forwarded $protocol port for service '$serviceName:$exposedPort'")
            } else {
                switch (host.type) {
                    case ServiceHostType.LocalHost:
                    case ServiceHostType.NetworkGateway:
                    case ServiceHostType.RemoteDockerHost:
                        if (forwardedPortsInfos.size() > 1) {
                            logger.warn("More forwarded $protocol ports for service '$serviceName:$exposedPort $forwardedPortsInfos'. Will use the first one.")
                        }
                        def forwardedPortInfo = forwardedPortsInfos.first()
                        int forwardedPort = forwardedPortInfo.HostPort as int
                        logger.info("Exposed $protocol port on service '$serviceName:$exposedPort' will be available as $forwardedPort")
                        ports.put(exposedPort, forwardedPort)
                        break
                    case ServiceHostType.Host:
                        logger.info("Exposed $protocol port on service '$serviceName:$exposedPort' will be available as $exposedPort because it uses HOST network")
                        ports.put(exposedPort, exposedPort)
                        break;
                    case ServiceHostType.DirectContainerAccess:
                        logger.info("Exposed $protocol port on service '$serviceName:$exposedPort' will be available as $exposedPort because it uses direct access to the container")
                        ports.put(exposedPort, exposedPort)
                        break;
                    default:
                        throw new IllegalArgumentException("Unknown ServiceHostType '${host.type}' for service '$serviceName'")
                        break
                }
            }
        }
        ports
    }

    private static boolean isMac() {
        System.getProperty("os.name").toLowerCase().startsWith("mac")
    }

    private static boolean isWindows() {
        System.getProperty("os.name").toLowerCase().startsWith("win")
    }

    private static boolean isWSL() {
        System.getProperty("os.version").toLowerCase().contains('wsl')
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy