Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Docker] add multiple docker tags support #1138

Merged
merged 13 commits into from
Jul 13, 2018
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ libraryDependencies += "jline" % "jline" % "2.11"
addSbtPlugin("org.foundweekends" % "sbt-bintray" % "0.5.1")

// For code formatting
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.10")
addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15")
2 changes: 1 addition & 1 deletion src/main/scala/com/typesafe/sbt/packager/Hashing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object Hashing {
try {
def read(): Unit = in.read(buffer) match {
case x if x <= 0 => ()
case size => md.update(buffer, 0, size); read()
case size => md.update(buffer, 0, size); read()
}
read()
} finally in.close()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ trait MaintainerScriptHelper {
* @return maintainerScripts with appended `scripts`
* @see [[maintainerScriptsAppendFromFile]]
*/
def maintainerScriptsAppend(
current: Map[String, Seq[String]] = Map.empty,
replacements: Seq[(String, String)] = Nil
)(scripts: (String, String)*): Map[String, Seq[String]] = {
def maintainerScriptsAppend(current: Map[String, Seq[String]] = Map.empty, replacements: Seq[(String, String)] = Nil)(
scripts: (String, String)*
): Map[String, Seq[String]] = {
val appended = scripts.map {
case (key, script) =>
key -> TemplateWriter.generateScriptFromLines((current.getOrElse(key, Seq.empty) :+ script), replacements)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ trait BashStartScriptKeys {
|""".stripMargin
)

val bashScriptDefines = TaskKey[Seq[String]](
"bashScriptDefines",
"A list of definitions that should be written to the bash file template."
)
val bashScriptDefines =
TaskKey[Seq[String]]("bashScriptDefines", "A list of definitions that should be written to the bash file template.")
val bashScriptExtraDefines = TaskKey[Seq[String]](
"bashScriptExtraDefines",
"A list of extra definitions that should be written to the bash file template."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,8 @@ object BashStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator wit
else
""

override protected[this] def createReplacementsForMainScript(
mainClass: String,
mainClasses: Seq[String],
config: SpecializedScriptConfig
): Seq[(String, String)] =
override protected[this] def createReplacementsForMainScript(mainClass: String,
mainClasses: Seq[String],
config: SpecializedScriptConfig): Seq[(String, String)] =
Seq("app_mainclass" -> mainClass, "available_main_classes" -> usageMainClassReplacement(mainClasses)) ++ config.replacements
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ object BatStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator with
def apply(name: String): Seq[(String, String)] =
Seq("APP_NAME" -> name, "APP_ENV_NAME" -> NameHelper.makeEnvFriendlyName(name))

def appDefines(mainClass: String, config: BatScriptConfig, replacements: Seq[(String, String)]): (String, String) = {
def appDefines(mainClass: String,
config: BatScriptConfig,
replacements: Seq[(String, String)]): (String, String) = {
val defines = Seq(makeWindowsRelativeClasspathDefine(config.scriptClasspath), Defines.mainClass(mainClass)) ++
config.configLocation.map(Defines.configFileDefine) ++
config.extraDefines
Expand Down Expand Up @@ -140,11 +142,9 @@ object BatStartScriptPlugin extends AutoPlugin with ApplicationIniGenerator with
}
}

override protected[this] def createReplacementsForMainScript(
mainClass: String,
mainClasses: Seq[String],
config: SpecializedScriptConfig
): Seq[(String, String)] =
override protected[this] def createReplacementsForMainScript(mainClass: String,
mainClasses: Seq[String],
config: SpecializedScriptConfig): Seq[(String, String)] =
config.replacements :+ Replacements.appDefines(mainClass, config, config.replacements)

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ object StartScriptMainClassConfig {
(mainClass, additionalMainClasses) match {
// only one main - create the default script
case (Some(main), Seq()) => SingleMain(main)
case (None, Seq(main)) => SingleMain(main)
case (None, Seq(main)) => SingleMain(main)
// main explicitly set and multiple discoveredMainClasses
case (Some(main), additional) => ExplicitMainWithAdditional(main, additional)
// no main class at all
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,9 +382,7 @@ trait DebianPluginLike {

private[debian] final def validateUserGroupNames(user: String, streams: TaskStreams): Unit = {
if ((UserNamePattern findFirstIn user).isEmpty) {
streams.log.warn(
"The user or group '" + user + "' may contain invalid characters for Debian based distributions"
)
streams.log.warn("The user or group '" + user + "' may contain invalid characters for Debian based distributions")
}
if (user.length > 32) {
streams.log.warn(
Expand Down
6 changes: 2 additions & 4 deletions src/main/scala/com/typesafe/sbt/packager/debian/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,8 @@ trait DebianKeys {
SettingKey[Seq[String]]("debian-package-dependencies", "Packages that this debian package depends on.")
val debianPackageProvides =
SettingKey[Seq[String]]("debian-package-provides", "Packages that are provided by the currently packaged one.")
val debianPackageRecommends = SettingKey[Seq[String]](
"debian-package-recommends",
"Packages recommended to use with the currently packaged one."
)
val debianPackageRecommends =
SettingKey[Seq[String]]("debian-package-recommends", "Packages recommended to use with the currently packaged one.")
val debianPackageInfo =
SettingKey[PackageInfo]("debian-package-info", "Information (name, version, etc.) about a debian package.")
val debianPackageMetadata =
Expand Down
17 changes: 12 additions & 5 deletions src/main/scala/com/typesafe/sbt/packager/docker/DockerAlias.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,18 @@ package com.typesafe.sbt.packager.docker
* @param tag Optional tag for the image, e.g. the version
*/
case class DockerAlias(registryHost: Option[String], username: Option[String], name: String, tag: Option[String]) {
protected val untagged = registryHost.map(_ + "/").getOrElse("") + username.map(_ + "/").getOrElse("") + name

/** Tag with (optional) given version */
val versioned = untagged + tag.map(":" + _).getOrElse("")
def withRegistryHost(registryHost: Option[String]): DockerAlias = copy(registryHost = registryHost)

/** Tag with version 'latest' */
val latest = s"$untagged:latest"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RIP binary API...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really time to add MiMa :(

def withUsername(username: Option[String]): DockerAlias = copy(username = username)

def withName(name: String): DockerAlias = copy(name = name)

def withTag(tag: Option[String]): DockerAlias = copy(tag = tag)

override def toString: String =
registryHost.map(_ + "/").getOrElse("") +
username.map(_ + "/").getOrElse("") +
name +
tag.map(":" + _).getOrElse("")
}
47 changes: 26 additions & 21 deletions src/main/scala/com/typesafe/sbt/packager/docker/DockerPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,21 +78,26 @@ object DockerPlugin extends AutoPlugin {
(dockerRepository in Docker).value,
(dockerUsername in Docker).value,
(packageName in Docker).value,
Some((version in Docker).value)
Option((version in Docker).value)
),
dockerUpdateLatest := false,
dockerAliases := {
val alias = dockerAlias.value
if (dockerUpdateLatest.value) {
Seq(alias, alias.withTag(Option("latest")))
} else {
Seq(alias)
}
},
dockerEntrypoint := Seq(s"${(defaultLinuxInstallLocation in Docker).value}/bin/${executableScriptName.value}"),
dockerCmd := Seq(),
dockerExecCommand := Seq("docker"),
dockerVersion := Try(Process(dockerExecCommand.value ++ Seq("version", "--format", "'{{.Server.Version}}'")).!!).toOption
.map(_.trim)
.flatMap(DockerVersion.parse),
dockerBuildOptions := Seq("--force-rm") ++ Seq("-t", dockerAlias.value.versioned) ++ (
if (dockerUpdateLatest.value)
Seq("-t", dockerAlias.value.latest)
else
Seq()
),
dockerBuildOptions := Seq("--force-rm") ++ dockerAliases.value.flatMap { alias =>
Seq("-t", alias.toString)
},
dockerRmiCommand := dockerExecCommand.value ++ Seq("rmi"),
dockerBuildCommand := dockerExecCommand.value ++ Seq("build") ++ dockerBuildOptions.value ++ Seq("."),
dockerCommands := {
Expand All @@ -119,26 +124,26 @@ object DockerPlugin extends AutoPlugin {
publishLocal := {
val log = streams.value.log
publishLocalDocker(stage.value, dockerBuildCommand.value, log)
log.info(s"Built image ${dockerAlias.value.versioned}")
log.info(
s"Built image ${dockerAlias.value.withTag(None).toString} with tags [${dockerAliases.value.flatMap(_.tag).mkString(", ")}]"
)
},
publish := {
val _ = publishLocal.value
val alias = dockerAlias.value
val alias = dockerAliases.value
val log = streams.value.log
val execCommand = dockerExecCommand.value
publishDocker(execCommand, alias.versioned, log)
if (dockerUpdateLatest.value) {
publishDocker(execCommand, alias.latest, log)
alias.foreach { aliasValue =>
publishDocker(execCommand, aliasValue.toString, log)
}
},
clean := {
val alias = dockerAlias.value
val alias = dockerAliases.value
val log = streams.value.log
val rmiCommand = dockerRmiCommand.value
// clean up images
rmiDocker(rmiCommand, alias.versioned, log)
if (dockerUpdateLatest.value) {
rmiDocker(rmiCommand, alias.latest, log)
alias.foreach { aliasValue =>
rmiDocker(rmiCommand, aliasValue.toString, log)
}
},
sourceDirectory := sourceDirectory.value / "docker",
Expand Down Expand Up @@ -325,12 +330,12 @@ object DockerPlugin extends AutoPlugin {
case s if s.startsWith("Sending build context") =>
log.debug(s) // 1.0
case s if !s.trim.isEmpty => log.error(s)
case s =>
case s =>
}

override def out(inf: => String): Unit = inf match {
case s if !s.trim.isEmpty => log.info(s)
case s =>
case s =>
}

override def buffer[T](f: => T): T = f
Expand All @@ -350,7 +355,7 @@ object DockerPlugin extends AutoPlugin {
def rmiDockerLogger(log: Logger) = new sys.process.ProcessLogger {
override def err(err: => String): Unit = err match {
case s if !s.trim.isEmpty => log.error(s)
case s =>
case s =>
}

override def out(inf: => String): Unit = log.info(inf)
Expand All @@ -377,15 +382,15 @@ object DockerPlugin extends AutoPlugin {

override def err(err: => String): Unit = err match {
case s if !s.trim.isEmpty => log.error(s)
case s =>
case s =>
}

override def out(inf: => String): Unit =
inf match {
case s if s.startsWith("Please login") =>
loginRequired.compareAndSet(false, true)
case s if !loginRequired.get && !s.trim.isEmpty => log.info(s)
case s =>
case s =>
}

override def buffer[T](f: => T): T = f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package com.typesafe.sbt.packager.docker

import java.nio.file.Paths

import com.spotify.docker.client.DockerClient.BuildParam
import com.spotify.docker.client.messages.ProgressMessage
import com.spotify.docker.client.{DefaultDockerClient, DockerClient, ProgressHandler}
import com.spotify.docker.client.DockerClient.BuildParam
import sbt._
import sbt.Keys._
import com.typesafe.sbt.packager.universal.UniversalPlugin.autoImport.stage
import sbt.Keys._
import sbt._

/**
* == DockerSpotifyClientPlugin Plugin ==
Expand Down Expand Up @@ -58,26 +58,25 @@ object DockerSpotifyClientPlugin extends AutoPlugin {

def publishLocalDocker: Def.Initialize[Task[Unit]] = Def.task {
val context = stage.value
val tag = dockerAlias.value.versioned
val latest = dockerUpdateLatest.value
val primaryAlias = dockerAlias.value
val aliases = dockerAliases.value
val log = streams.value.log

val dockerDirectory = context.toString
val docker: DockerClient = DefaultDockerClient.fromEnv().build()

log.info(s"PublishLocal using Docker API ${docker.version().apiVersion()}")

docker.build(Paths.get(dockerDirectory), tag, new ProgressHandler() {
def progress(message: ProgressMessage): Unit =
docker.build(Paths.get(dockerDirectory), primaryAlias.toString, new ProgressHandler {
override def progress(message: ProgressMessage): Unit =
Option(message.error()) match {
case Some(error) if error.nonEmpty => log.error(message.error())
case _ => Option(message.stream()) foreach (v => log.info(v))
case _ => Option(message.stream()) foreach (v => log.info(v))
}
}, BuildParam.forceRm())

if (latest) {
val name = tag.substring(0, tag.lastIndexOf(":")) + ":latest"
docker.tag(tag, name, true)
aliases.foreach { tag =>
docker.tag(primaryAlias.toString, tag.toString, true)
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/com/typesafe/sbt/packager/docker/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ trait DockerKeys {
val dockerUsername = SettingKey[Option[String]]("dockerUsername", "Username for published Docker image")
val dockerAlias =
SettingKey[DockerAlias]("dockerAlias", "Docker alias for the built image")
val dockerAliases =
SettingKey[Seq[DockerAlias]]("dockerAliases", "Docker aliases for the built image")
val dockerUpdateLatest =
SettingKey[Boolean]("dockerUpdateLatest", "Set to update latest tag")
val dockerEntrypoint = SettingKey[Seq[String]]("dockerEntrypoint", "Entrypoint arguments passed in exec form")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ trait LinuxMappingDSL {
def configWithNoReplace(mappings: Seq[LinuxPackageMapping]): Seq[LinuxPackageMapping] =
mappings.map {
case mapping if mapping.fileData.config != "false" => mapping.withConfig("noreplace")
case mapping => mapping
case mapping => mapping
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ object RpmHelper {
IO.withTemporaryDirectory { tmpRpmBuildDir =>
val args: Seq[String] = (spec.setarch match {
case Some(arch) => Seq("setarch", arch)
case None => Seq()
case None => Seq()
}) ++ Seq(
"rpmbuild",
"-bb",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ case class RpmSpec(meta: RpmMetadata,
val sb = new StringBuilder
meta.config.toLowerCase match {
case "false" => ()
case "true" => sb append "%config "
case x => sb append ("%config(" + x + ") ")
case "true" => sb append "%config "
case x => sb append ("%config(" + x + ") ")
}
if (meta.docs) sb append "%doc "
if (isDir) sb append "%dir "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,15 +111,15 @@ object WindowsPlugin extends AutoPlugin {
val candleCmd = Seq(wixdir + "\\bin\\candle.exe", wix.getAbsolutePath) ++ candleOptions.value
streams.value.log.debug(candleCmd mkString " ")
sys.process.Process(candleCmd, Some(target.value)) ! streams.value.log match {
case 0 => ()
case 0 => ()
case exitCode => sys.error(s"Unable to run WIX compilation to wixobj. Exited with ${exitCode}")
}
// Now create MSI
val wixobj = target.value / (name.value + ".wixobj")
val lightCmd = Seq(wixdir + "\\bin\\light.exe", wixobj.getAbsolutePath) ++ lightOptions.value
streams.value.log.debug(lightCmd mkString " ")
sys.process.Process(lightCmd, Some(target.value)) ! streams.value.log match {
case 0 => ()
case 0 => ()
case exitCode => sys.error(s"Unable to run build msi. Exited with ${exitCode}")
}
msi
Expand Down
2 changes: 1 addition & 1 deletion src/sbt-test/docker/alias/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ name := "docker-alias-test"

version := "0.1.0"

dockerAlias := DockerAlias(None, None, "docker-alias-test", Some("0.1.0"))
dockerAlias := DockerAlias(None, None, "docker-alias-test", Option("0.1.0"))
11 changes: 11 additions & 0 deletions src/sbt-test/docker/multiple-tags/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
enablePlugins(JavaAppPackaging)

name := "docker-test"

version := "0.1.0"

maintainer := "Gary Coady <[email protected]>"

dockerAliases ++= Seq(dockerAlias.value.withTag(Option("0.1")))

dockerUpdateLatest := true
1 change: 1 addition & 0 deletions src/sbt-test/docker/multiple-tags/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version"))
3 changes: 3 additions & 0 deletions src/sbt-test/docker/multiple-tags/src/main/scala/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
object Main extends App {
println("Hello world")
}
5 changes: 5 additions & 0 deletions src/sbt-test/docker/multiple-tags/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Generate the Docker image locally
> docker:publishLocal
$ exec bash -c 'docker run docker-test:latest | grep -q "Hello world"'
$ exec bash -c 'docker run docker-test:0.1.0 | grep -q "Hello world"'
$ exec bash -c 'docker run docker-test:0.1 | grep -q "Hello world"'
Loading