diff --git a/src/main/scala/com/typesafe/sbt/packager/docker/DockerPlugin.scala b/src/main/scala/com/typesafe/sbt/packager/docker/DockerPlugin.scala index 3ac806b00..44af0d6f4 100644 --- a/src/main/scala/com/typesafe/sbt/packager/docker/DockerPlugin.scala +++ b/src/main/scala/com/typesafe/sbt/packager/docker/DockerPlugin.scala @@ -8,7 +8,7 @@ import sbt._ trait DockerPlugin extends Plugin with UniversalPlugin { val Docker = config("docker") extend Universal - private[this] final def makeDockerContent(dockerBaseImage: String, dockerBaseDirectory: String, maintainer: String, daemonUser: String, name: String, exposedPorts: Seq[Int]) = { + private[this] final def makeDockerContent(dockerBaseImage: String, dockerBaseDirectory: String, maintainer: String, daemonUser: String, name: String, exposedPorts: Seq[Int], exposedVolumes: Seq[String]) = { val dockerCommands = Seq( Cmd("FROM", dockerBaseImage), Cmd("MAINTAINER", maintainer), @@ -27,12 +27,27 @@ trait DockerPlugin extends Plugin with UniversalPlugin { Some(Cmd("EXPOSE", exposedPorts.mkString(" "))) } - Dockerfile(dockerCommands ++ exposeCommand: _*).makeContent + // If the exposed volume does not exist, the volume is made available + // with root ownership. This may be too strict for some directories, + // and we lose the feature that all directories below the install path + // can be written to by the binary. Therefore the directories are + // created before the ownership is changed. + val volumeCommands: Seq[CmdLike] = { + if (exposedVolumes.isEmpty) + Seq() + else + Seq( + ExecCmd("RUN", Seq("mkdir", "-p") ++ exposedVolumes: _*), + ExecCmd("VOLUME", exposedVolumes: _*) + ) + } + + Dockerfile(volumeCommands ++ exposeCommand ++ dockerCommands: _*).makeContent } private[this] final def generateDockerConfig( - dockerBaseImage: String, dockerBaseDirectory: String, maintainer: String, daemonUser: String, normalizedName: String, exposedPorts: Seq[Int], target: File) = { - val dockerContent = makeDockerContent(dockerBaseImage, dockerBaseDirectory, maintainer, daemonUser, normalizedName, exposedPorts) + dockerBaseImage: String, dockerBaseDirectory: String, maintainer: String, daemonUser: String, normalizedName: String, exposedPorts: Seq[Int], exposedVolumes: Seq[String], target: File) = { + val dockerContent = makeDockerContent(dockerBaseImage, dockerBaseDirectory, maintainer, daemonUser, normalizedName, exposedPorts, exposedVolumes) val f = target / "Dockerfile" IO.write(f, dockerContent) @@ -63,6 +78,7 @@ trait DockerPlugin extends Plugin with UniversalPlugin { publishArtifact := false, defaultLinuxInstallLocation := "/opt/docker", dockerExposedPorts := Seq(), + dockerExposedVolumes := Seq(), dockerPackageMappings <<= (sourceDirectory) map { dir => MappingsHelper contentOf dir }, @@ -75,9 +91,9 @@ trait DockerPlugin extends Plugin with UniversalPlugin { contextDir }, dockerGenerateConfig <<= - (dockerBaseImage, defaultLinuxInstallLocation, maintainer, daemonUser, normalizedName, dockerExposedPorts, target) map { - case (dockerBaseImage, baseDirectory, maintainer, daemonUser, normalizedName, exposedPorts, target) => - generateDockerConfig(dockerBaseImage, baseDirectory, maintainer, daemonUser, normalizedName, exposedPorts, target) + (dockerBaseImage, defaultLinuxInstallLocation, maintainer, daemonUser, normalizedName, dockerExposedPorts, dockerExposedVolumes, target) map { + case (dockerBaseImage, baseDirectory, maintainer, daemonUser, normalizedName, exposedPorts, exposedVolumes, target) => + generateDockerConfig(dockerBaseImage, baseDirectory, maintainer, daemonUser, normalizedName, exposedPorts, exposedVolumes, target) } )) } diff --git a/src/main/scala/com/typesafe/sbt/packager/docker/Keys.scala b/src/main/scala/com/typesafe/sbt/packager/docker/Keys.scala index ea6eb838f..c83d6fae6 100644 --- a/src/main/scala/com/typesafe/sbt/packager/docker/Keys.scala +++ b/src/main/scala/com/typesafe/sbt/packager/docker/Keys.scala @@ -11,6 +11,7 @@ trait DockerKeys { val dockerBaseImage = SettingKey[String]("dockerBaseImage", "Base image for Dockerfile.") val dockerExposedPorts = SettingKey[Seq[Int]]("dockerExposedPorts", "Ports exposed by Docker image") + val dockerExposedVolumes = SettingKey[Seq[String]]("dockerExposedVolumes", "Volumes exposed by Docker image") } object Keys extends DockerKeys { diff --git a/src/sbt-test/docker/volumes/build.sbt b/src/sbt-test/docker/volumes/build.sbt new file mode 100644 index 000000000..e04a596cd --- /dev/null +++ b/src/sbt-test/docker/volumes/build.sbt @@ -0,0 +1,9 @@ +import NativePackagerKeys._ + +packagerSettings + +name := "simple-test" + +version := "0.1.0" + +dockerExposedVolumes in Docker := Seq("/opt/docker/logs", "/opt/docker/config") diff --git a/src/sbt-test/docker/volumes/project/plugins.sbt b/src/sbt-test/docker/volumes/project/plugins.sbt new file mode 100644 index 000000000..b53de154c --- /dev/null +++ b/src/sbt-test/docker/volumes/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % sys.props("project.version")) diff --git a/src/sbt-test/docker/volumes/test b/src/sbt-test/docker/volumes/test new file mode 100644 index 000000000..108c02a86 --- /dev/null +++ b/src/sbt-test/docker/volumes/test @@ -0,0 +1,4 @@ +# Stage the distribution and ensure files show up. +> docker:stage +$ exec grep -q -F 'VOLUME ["/opt/docker/logs", "/opt/docker/config"]' target/docker/Dockerfile +$ exec grep -q -F 'RUN ["mkdir", "-p", "/opt/docker/logs", "/opt/docker/config"]' target/docker/Dockerfile