com.intershop.gradle.icm.docker.tasks.BuildImage.kt Maven / Gradle / Ivy
Show all versions of icm-docker-plugin Show documentation
/*
* Copyright 2020 Intershop Communications AG.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.intershop.gradle.icm.docker.tasks
import com.bmuschko.gradle.docker.DockerRegistryCredentials
import com.bmuschko.gradle.docker.internal.OutputCollector
import com.bmuschko.gradle.docker.tasks.AbstractDockerRemoteApiTask
import com.bmuschko.gradle.docker.tasks.RegistryCredentialsAware
import com.github.dockerjava.api.command.BuildImageResultCallback
import com.github.dockerjava.api.exception.DockerException
import com.github.dockerjava.api.model.BuildResponseItem
import com.intershop.gradle.icm.docker.ICMDockerPlugin.Companion.BUILD_IMG_REGISTRY
import com.intershop.gradle.icm.docker.utils.BuildImageRegistry
import groovy.lang.Closure
import org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.file.FileSystemOperations
import org.gradle.api.file.ProjectLayout
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.MapProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import org.gradle.api.services.BuildServiceRegistry
import org.gradle.api.services.internal.BuildServiceRegistryInternal
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.Optional
import org.gradle.api.tasks.OutputFile
import org.gradle.util.ConfigureUtil
import java.io.File
import java.io.IOException
import javax.inject.Inject
open class BuildImage
@Inject constructor(objectFactory: ObjectFactory,
@Internal var projectLayout: ProjectLayout,
@Internal var fsOps: FileSystemOperations):
AbstractDockerRemoteApiTask(), RegistryCredentialsAware {
private val registryCredentials: DockerRegistryCredentials =
objectFactory.newInstance(DockerRegistryCredentials::class.java)
/**
* The target Docker registry credentials for usage with a task.
*/
override fun getRegistryCredentials(): DockerRegistryCredentials {
return registryCredentials
}
/**
* Configures the target Docker registry credentials for use with a task.
*/
override fun registryCredentials(action: Action?) {
action!!.execute(registryCredentials)
}
/**
* Set the credentials for the task.
*
* @param c closure with Docker registry credentials.
*/
fun registryCredentials(c: Closure) {
ConfigureUtil.configure(c, registryCredentials)
}
/**
* Additional files to build the image.
*/
@get:InputFiles
val srcFiles: ConfigurableFileCollection = objectFactory.fileCollection()
/**
* The Dockerfile to use to build the image. If null, will use 'Dockerfile' in the
* build context, i.e. "Dockerfile" in the srcfiles.
*/
@get:InputFile
@get:Optional
val dockerfile: RegularFileProperty = project.objects.fileProperty()
/**
* The images including repository, image name and tag used e.g. {@code vieux/apache:2.0}.
*/
@get:Input
@get:Optional
val images: SetProperty = project.objects.setProperty(String::class.java)
/**
* When {@code true}, do not use docker cache when building the image.
*/
@get:Input
@get:Optional
val noCache:Property = project.objects.property(Boolean::class.java)
/**
* When {@code true}, remove intermediate containers after a successful build.
*/
@get:Input
@get:Optional
val remove:Property = project.objects.property(Boolean::class.java)
/**
* When {@code true}, suppress the build output and print image ID on success.
*/
@get:Input
@get:Optional
val quiet:Property = project.objects.property(Boolean::class.java)
/**
* When {@code true}, always attempt to pull a newer version of the image.
*/
@get:Input
@get:Optional
val pull:Property = project.objects.property(Boolean::class.java)
/**
* Labels to attach as metadata for to the image.
*/
@get:Input
@get:Optional
val labels: MapProperty = project.objects.mapProperty(String::class.java, String::class.java)
/**
* Networking mode for the RUN instructions during build.
*/
@get:Input
@get:Optional
val network:Property = project.objects.property(String::class.java)
/**
* Build-time variables to pass to the image build.
*/
@get:Input
@get:Optional
val buildArgs:MapProperty = project.objects.mapProperty(String::class.java, String::class.java)
/**
* Images to consider as cache sources.
*/
@get:Input
@get:Optional
val cacheFrom:SetProperty = project.objects.setProperty(String::class.java)
/**
* Size of {@code /dev/shm} in bytes.
* The size must be greater than 0.
* If omitted the system uses 64MB.
*/
@get:Input
@get:Optional
val shmSize:Property = project.objects.property(Long::class.java)
/**
* With this parameter it is possible to build a special stage in a multi-stage Docker file.
*
* This feature is only available for use with Docker 17.05 and higher.
*/
@get:Input
@get:Optional
val target:Property = project.objects.property(String::class.java)
/**
* Build-time additional host list to pass to the image build in the format {@code host:ip}.
*/
@get:Input
@get:Optional
val extraHosts:SetProperty = project.objects.setProperty(String::class.java)
/**
* Output file containing the image ID of the built image.
* Defaults to "$buildDir/.docker/$taskpath-imageId.txt".
* If path contains ':' it will be replaced by '_'.
*/
@get:OutputFile
val imageIdFile:RegularFileProperty = project.objects.fileProperty()
/**
* The id of the image built.
*/
@Internal
val imageId:Property = project.objects.property(String::class.java)
@get:Input
val dirname: Property = objectFactory.property(String::class.java)
@get:Internal
val enabled: Property = objectFactory.property(Boolean::class.java)
init {
images.empty()
noCache.set(false)
remove.set(true)
quiet.set(false)
pull.set(false)
cacheFrom.empty()
val safeTaskPath = this.path.replaceFirst("^:", "").replace(":", "_")
imageIdFile.set(project.layout.buildDirectory.file(".docker/${safeTaskPath}-imageId.txt"))
buildArgs.empty()
outputs.upToDateWhen {
val file = imageIdFile.get().asFile
val returnValue =
if(file.exists()) {
try {
val fileImageId = file.readText()
dockerClient.inspectImageCmd(fileImageId).exec()
imageId.set(fileImageId)
true
} catch (e: DockerException) {
e.printStackTrace()
false
}
} else {
false
}
returnValue
}
onlyIf {
val returnValue = enabled.getOrElse(false)
if(! returnValue) {
project.logger.quiet("Task {} skipped, because it is not enabled.")
}
returnValue
}
}
override fun runRemoteCommand() {
val finalDir = dirname.getOrElse("docker")
val imgBuildDir = projectLayout.buildDirectory.dir("buildimage/${finalDir}").get().asFile
val defaultDockerFile = File(imgBuildDir, "Dockerfile")
imgBuildDir.mkdirs()
logger.quiet("Building image using context '{}'.", imgBuildDir.absolutePath)
fsOps.copy {
if (dockerfile.orNull != null) {
logger.quiet("Dockerfile '{}' will be copied to working dir: {}", dockerfile.get().asFile, imgBuildDir)
it.from(dockerfile)
}
it.from(srcFiles)
it.into(imgBuildDir)
}
val buildImageCmd = dockerClient.buildImageCmd().withBaseDirectory(imgBuildDir)
buildImageCmd.withDockerfile(defaultDockerFile)
val serviceRegistry = services.get(BuildServiceRegistryInternal::class.java)
val buildImgRegistry = getBuildService(serviceRegistry, BUILD_IMG_REGISTRY)
if (images.orNull != null) {
val tagListString = images.get().joinToString(separator = ",") { "'${it}'" }
logger.quiet("Using images {}.", tagListString)
if(buildImgRegistry != null) {
buildImgRegistry.addImages(images.get().toList())
} else {
throw GradleException("Buildservice registry is not correct configured.")
}
buildImageCmd.withTags(images.get())
}
buildImageCmd.withNoCache(noCache.get())
buildImageCmd.withRemove(remove.get())
buildImageCmd.withQuiet(quiet.get())
buildImageCmd.withPull(pull.get())
if(network.isPresent && network.getOrElse("").isNotEmpty()) {
buildImageCmd.withNetworkMode(network.get())
}
if(labels.get().isNotEmpty()) {
buildImageCmd.withLabels(labels.get())
}
if(shmSize.isPresent) { // 0 is valid input
buildImageCmd.withShmsize(shmSize.get())
}
if(target.isPresent) {
buildImageCmd.withTarget(target.get())
}
val authConfigurations = registryAuthLocator.lookupAllAuthConfigs(registryCredentials)
buildImageCmd.withBuildAuthConfigs(authConfigurations)
if (buildArgs.get().isNotEmpty()) {
buildArgs.get().forEach { (key, value) ->
buildImageCmd.withBuildArg(key, value)
}
}
if (cacheFrom.get().isNotEmpty()) {
buildImageCmd.withCacheFrom(cacheFrom.get())
}
if (extraHosts.get().isNotEmpty()) {
buildImageCmd.withExtraHosts(extraHosts.get())
}
val createdImageId = buildImageCmd.exec(createCallback(nextHandler)).awaitImageId()
imageId.set(createdImageId)
imageIdFile.get().asFile.writeText(createdImageId)
logger.quiet("Created image with ID '{}'.", createdImageId)
}
private fun createCallback(nextHandler: Action?): BuildImageResultCallback {
if (nextHandler != null) {
return object : BuildImageResultCallback() {
override fun onNext(item: BuildResponseItem) {
try {
nextHandler.execute(item)
} catch (e: Exception) {
logger.error("Failed to handle build response", e)
return
}
super.onNext(item)
}
}
}
return object : BuildImageResultCallback() {
val collector = OutputCollector { s -> logger.quiet(s) }
override fun onNext(item: BuildResponseItem) {
try {
val possibleStream = item.stream
if (possibleStream != null) {
collector.accept(possibleStream)
}
} catch(e: Exception) {
logger.error("Failed to handle build response", e)
return
}
super.onNext(item)
}
@Throws(IOException::class)
override fun close() {
collector.close()
super.close()
}
}
}
private fun getBuildService(registry: BuildServiceRegistry, name: String): BuildImageRegistry? {
val registration = registry.registrations.findByName(name)
?: throw GradleException ("Unable to find build service with name '$name'.")
val buildservice = registration.service.get()
return if(buildservice is BuildImageRegistry) {
buildservice
} else {
null
}
}
}