com.avast.gradle.dockercompose.tasks.ComposeUp.groovy Maven / Gradle / Ivy
package com.avast.gradle.dockercompose.tasks
import com.avast.gradle.dockercompose.ComposeExecutor
import com.avast.gradle.dockercompose.ContainerInfo
import com.avast.gradle.dockercompose.DockerExecutor
import com.avast.gradle.dockercompose.ServiceHost
import com.avast.gradle.dockercompose.ServiceInfo
import com.avast.gradle.dockercompose.ServiceInfoCache
import groovy.json.JsonSlurper
import org.gradle.api.DefaultTask
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import java.time.Duration
import java.time.Instant
abstract class ComposeUp extends DefaultTask {
@Internal
Boolean wasReconnected = false // for tests
@Internal
DockerExecutor dockerExecutor
@Internal
abstract Property getStopContainers()
@Internal
abstract Property getForceRecreate()
@Internal
abstract Property getNoRecreate()
@Internal
abstract MapProperty getScale()
@Internal
abstract ListProperty getUpAdditionalArgs()
@Internal
abstract ListProperty getStartedServices()
@Internal
abstract RegularFileProperty getComposeLogToFile()
@Internal
abstract Property getWaitForTcpPorts()
@Internal
abstract Property getRetainContainersOnStartupFailure()
@Internal
abstract Property getCaptureContainersOutput()
@Internal
abstract RegularFileProperty getCaptureContainersOutputToFile()
@Internal
abstract DirectoryProperty getCaptureContainersOutputToFiles()
@Internal
abstract Property getWaitAfterHealthyStateProbeFailure()
@Internal
abstract Property getCheckContainersRunning()
@Internal
abstract Property getWaitForHealthyStateTimeout()
@Internal
abstract ListProperty getTcpPortsToIgnoreWhenWaiting()
@Internal
abstract Property getWaitForTcpPortsDisconnectionProbeTimeout()
@Internal
abstract Property getWaitForTcpPortsTimeout()
@Internal
abstract Property getWaitAfterTcpProbeFailure()
@Internal
abstract Property getServiceInfoCache()
@Internal
abstract Property getComposeExecutor()
private Map servicesInfos = [:]
@Internal
Map getServicesInfos() {
servicesInfos
}
ComposeUp() {
group = 'docker'
description = 'Builds and starts containers of docker-compose project'
}
@TaskAction
void up() {
if (!stopContainers.get()) {
def cachedServicesInfos = serviceInfoCache.get().get({ getStateForCache() })
if (cachedServicesInfos) {
servicesInfos = cachedServicesInfos
logger.lifecycle('Cached services infos loaded while \'stopContainers\' is set to \'false\'.')
wasReconnected = true
startCapturing()
printExposedPorts()
return
}
}
serviceInfoCache.get().clear()
wasReconnected = false
String[] args = ['up', '-d']
if (composeExecutor.get().shouldRemoveOrphans()) {
args += '--remove-orphans'
}
if (forceRecreate.get()) {
args += '--force-recreate'
args += '--renew-anon-volumes'
} else if (noRecreate.get()) {
args += '--no-recreate'
}
if (composeExecutor.get().isScaleSupported()) {
args += scale.get().collect { service, value ->
['--scale', "$service=$value"]
}.flatten()
}
args += upAdditionalArgs.get()
args += startedServices.get()
try {
def composeLog = null
if (composeLogToFile.isPresent()) {
File logFile = composeLogToFile.get().asFile
logger.debug "Logging docker-compose up to: $logFile"
logFile.parentFile.mkdirs()
composeLog = new FileOutputStream(logFile)
}
composeExecutor.get().executeWithCustomOutputWithExitValue(composeLog, args)
def servicesToLoad = composeExecutor.get().getServiceNames()
servicesInfos = loadServicesInfo(servicesToLoad).collectEntries { [(it.name): (it)] }
startCapturing()
waitForHealthyContainers(servicesInfos.values())
if (waitForTcpPorts.get()) {
servicesInfos = waitForOpenTcpPorts(servicesInfos.values()).collectEntries { [(it.name): (it)] }
}
printExposedPorts()
if (!stopContainers.get()) {
serviceInfoCache.get().set(servicesInfos, getStateForCache())
} else {
serviceInfoCache.get().clear()
}
}
catch (Exception e) {
logger.debug("Failed to start-up Docker containers", e)
if (!retainContainersOnStartupFailure.get()) {
serviceInfoCache.get().startupFailed = true
}
throw e
}
}
protected void printExposedPorts() {
if (!servicesInfos.values().any { si -> si.tcpPorts.any() }) {
return
}
int nameMaxLength = Math.max('Name'.length(), servicesInfos.values().collect { it.containerInfos.values().collect { it.instanceName.length() } }.flatten().max())
int containerPortMaxLenght = 'Container Port'.length()
int mappingMaxLength = Math.max('Mapping'.length(), servicesInfos.values().collect { it.containerInfos.values().collect { ci -> ci.tcpPorts.collect { p -> "${ci.host}:${p.value}".length() } } }.flatten().max())
logger.lifecycle('+-' + '-'.multiply(nameMaxLength) + '-+-' + '-'.multiply(containerPortMaxLenght) + '-+-' + '-'.multiply(mappingMaxLength) + '-+')
logger.lifecycle('| Name' + ' '.multiply(nameMaxLength - 'Name'.length()) + ' | Container Port' + ' '.multiply(containerPortMaxLenght - 'Container Port'.length()) + ' | Mapping' + ' '.multiply(mappingMaxLength - 'Mapping'.length()) + ' |')
logger.lifecycle('+-' + '-'.multiply(nameMaxLength) + '-+-' + '-'.multiply(containerPortMaxLenght) + '-+-' + '-'.multiply(mappingMaxLength) + '-+')
servicesInfos.values().forEach { si ->
if (si.containerInfos.values().any { it.tcpPorts.any() }) {
si.containerInfos.values().forEach { ci ->
ci.tcpPorts.entrySet().forEach { p ->
String mapping = "${ci.host}:${p.value}".toString()
logger.lifecycle('| ' + ci.instanceName + ' '.multiply(nameMaxLength - ci.instanceName.length()) + ' | ' + p.key + ' '.multiply(containerPortMaxLenght - p.key.toString().length()) + ' | ' + mapping + ' '.multiply(mappingMaxLength - mapping.length()) + ' |')
}
}
logger.lifecycle('+-' + '-'.multiply(nameMaxLength) + '-+-' + '-'.multiply(containerPortMaxLenght) + '-+-' + '-'.multiply(mappingMaxLength) + '-+')
}
}
}
protected void startCapturing() {
if (captureContainersOutput.get()) {
composeExecutor.get().captureContainersOutput(logger.&lifecycle)
}
if (captureContainersOutputToFile.isPresent()) {
def logFile = captureContainersOutputToFile.get().asFile
logFile.parentFile.mkdirs()
composeExecutor.get().captureContainersOutput({ logFile.append(it + '\n') })
}
if (captureContainersOutputToFiles.isPresent()) {
def logDir = captureContainersOutputToFiles.get().asFile
logDir.mkdirs()
logDir.listFiles().each { it.delete() }
servicesInfos.keySet().each {
def logFile = logDir.toPath().resolve("${it}.log").toFile()
composeExecutor.get().captureContainersOutput({ logFile.append(it + '\n') }, it)
}
}
}
@Internal
protected def getStateForCache() {
String processesAsString = composeExecutor.get().execute('ps', '--format', 'json')
String processesState = processesAsString
try {
// Status field contains something like "Up 8 seconds", so we have to strip the duration.
Object[] processes = new JsonSlurper().parseText(processesAsString)
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy