diff --git a/src/main/scala/sbtdocker/DockerKeys.scala b/src/main/scala/sbtdocker/DockerKeys.scala index fee08b6..8d56183 100644 --- a/src/main/scala/sbtdocker/DockerKeys.scala +++ b/src/main/scala/sbtdocker/DockerKeys.scala @@ -6,6 +6,7 @@ object DockerKeys { val docker = taskKey[ImageId]("Build a Docker image.") val dockerBuildAndPush = taskKey[Map[ImageName, ImageDigest]]("Build a Docker image and pushes it to a registry.") val dockerPush = taskKey[Map[ImageName, ImageDigest]]("Push a already built Docker image to a registry.") + val dockerRmi = taskKey[Map[ImageName, Seq[ImageDigest]]]("Delete built images.") @deprecated("Use imageNames instead.", "1.0.0") val imageName = taskKey[ImageName]("Name of the built image.") diff --git a/src/main/scala/sbtdocker/DockerRmi.scala b/src/main/scala/sbtdocker/DockerRmi.scala new file mode 100644 index 0000000..2719b15 --- /dev/null +++ b/src/main/scala/sbtdocker/DockerRmi.scala @@ -0,0 +1,61 @@ +package sbtdocker + +import sbt._ + +import scala.sys.process.{Process, ProcessLogger} + +object DockerRmi { + + /** + * Delete Docker images. + * + * @param dockerPath path to the docker binary + * @param imageNames names of the images to delete + * @param log logger + */ + def apply(dockerPath: String, imageNames: Seq[ImageName], log: Logger): Map[ImageName, Seq[ImageDigest]] = { + imageNames.map { imageName => + apply(dockerPath, imageName, log) + }.toMap + } + + /** + * Delete a Docker image. + * + * @param dockerPath path to the docker binary + * @param imageName name of the image to delete + * @param log logger + */ + def apply(dockerPath: String, imageName: ImageName, log: Logger): (ImageName, Seq[ImageDigest]) = { + log.info(s"Deleting docker image with name: '$imageName'") + + var lines = Seq.empty[String] + val processLog = ProcessLogger( + { line => + log.info(line) + lines :+= line + }, + { line => + log.info(line) + lines :+= line + } + ) + + val command = dockerPath :: "rmi" :: "-f" :: imageName.toString :: Nil + log.debug(s"Running command: '${command.mkString(" ")}'") + + val process = Process(command) + val exitCode = process ! processLog + if (exitCode != 0) throw new DockerRmiException(s"Failed to run 'docker rmi' on image $imageName. Exit code $exitCode") + + val DeletedImageDigestSha256 = ".*Deleted: ([^:]*):([0-9a-f]+).*".r + + val deletedImages = lines.collect { + case DeletedImageDigestSha256(algo, digest) => ImageDigest(algo, digest) + } + + (imageName, deletedImages) + } +} + +class DockerRmiException(message: String) extends RuntimeException(message) diff --git a/src/main/scala/sbtdocker/DockerSettings.scala b/src/main/scala/sbtdocker/DockerSettings.scala index 2bcd7fc..2f9182a 100644 --- a/src/main/scala/sbtdocker/DockerSettings.scala +++ b/src/main/scala/sbtdocker/DockerSettings.scala @@ -25,6 +25,13 @@ object DockerSettings { DockerPush(dockerPath, imageNames, log) }, + dockerRmi := { + val log = Keys.streams.value.log + val dockerPath = (docker / DockerKeys.dockerPath).value + val imageNames = (docker / DockerKeys.imageNames).value + + DockerRmi(dockerPath, imageNames, log) + }, dockerBuildAndPush := Def.taskDyn { docker.value Def.task { diff --git a/src/sbt-test/sbt-docker/rmi/build.sbt b/src/sbt-test/sbt-docker/rmi/build.sbt new file mode 100644 index 0000000..648ed78 --- /dev/null +++ b/src/sbt-test/sbt-docker/rmi/build.sbt @@ -0,0 +1,52 @@ +enablePlugins(DockerPlugin) + +name := "scripted-rmi" + +organization := "sbtdocker" + +version := "0.1.0" + +// Define a Dockerfile +docker / dockerfile := { + val jarFile = (Compile / packageBin / Keys.`package`).value + val classpath = (Compile / managedClasspath).value + val mainclass = (Compile / packageBin / mainClass).value.getOrElse { + sys.error("Expected exactly one main class") + } + val jarTarget = s"/app/${jarFile.getName}" + // Add all files on the classpath + val files = classpath.files.map(file => file -> s"/app/${file.getName}").toMap + // Make a colon separated classpath with the JAR file + val classpathString = files.values.mkString(":") + ":" + jarTarget + new Dockerfile { + from("openjdk:8-jre") + // Add all files that is on the classpath + files.foreach { + case (source, destination) => + add(source, destination) + } + // Add the JAR and set the entry point + add(jarFile, jarTarget) + entryPoint("java", "-cp", classpathString, mainclass) + } +} + +// Set a custom image name +docker / imageNames := { + val imageName = ImageName( + namespace = Some(organization.value), + repository = name.value, + tag = Some("v" + version.value)) + Seq(imageName, imageName.copy(tag = Some("latest"))) +} + +val check = taskKey[Unit]("Check") + +check := { + val names = (docker / imageNames).value + names.foreach { imageName => + val process = scala.sys.process.Process("docker", Seq("run", "--rm", imageName.toString)) + val out = process.!! + if (out.trim != "Hello World") sys.error("Unexpected output: " + out) + } +} diff --git a/src/sbt-test/sbt-docker/rmi/project/build.properties b/src/sbt-test/sbt-docker/rmi/project/build.properties new file mode 100644 index 0000000..0837f7a --- /dev/null +++ b/src/sbt-test/sbt-docker/rmi/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.13 diff --git a/src/sbt-test/sbt-docker/rmi/project/plugins.sbt b/src/sbt-test/sbt-docker/rmi/project/plugins.sbt new file mode 100644 index 0000000..c16d993 --- /dev/null +++ b/src/sbt-test/sbt-docker/rmi/project/plugins.sbt @@ -0,0 +1,9 @@ +{ + sys.props.get("plugin.version") match { + case Some(v) => + addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % v) + case None => + sys.error("The system property 'plugin.version' is not defined. " + + "Specify this property using the scriptedLaunchOpts -D.") + } +} diff --git a/src/sbt-test/sbt-docker/rmi/src/main/scala/simple/HelloWorld.scala b/src/sbt-test/sbt-docker/rmi/src/main/scala/simple/HelloWorld.scala new file mode 100644 index 0000000..93d4b02 --- /dev/null +++ b/src/sbt-test/sbt-docker/rmi/src/main/scala/simple/HelloWorld.scala @@ -0,0 +1,5 @@ +package simple + +object HelloWorld extends App { + println("Hello World") +} diff --git a/src/sbt-test/sbt-docker/rmi/test b/src/sbt-test/sbt-docker/rmi/test new file mode 100644 index 0000000..003ce0d --- /dev/null +++ b/src/sbt-test/sbt-docker/rmi/test @@ -0,0 +1,4 @@ +> docker +> check +> dockerRmi +-> check