Skip to content

Commit

Permalink
Merge branch 'master' into feature/javapackager-format
Browse files Browse the repository at this point in the history
* master:
  Update README.md
  Replace chmod call
  More comprehensive tests
  Adding documentation
  FIX sbt#276 creating directories as necessary and specify top level dir and sadly realizing that apache commons compress is still the best bet
  Adding documentation, examples and tests.
  Initial refactoring on sbt#453
  Revert "[fix sbt#472] /etc/default/<package-name> should be shell script setting envars"
  FIX sbt#489: Small fix in documentation
  Upgrading to java 7 and using posix nio API

Conflicts:
	src/main/scala/com/typesafe/sbt/packager/jdkpackager/JDKPackagerHelper.scala
  • Loading branch information
Simeon H.K. Fitch committed Feb 15, 2015
2 parents c51cb77 + 5d03d9f commit c52f906
Show file tree
Hide file tree
Showing 35 changed files with 758 additions and 138 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ language: scala
os:
- linux
script:
- sbt ++$TRAVIS_SCALA_VERSION "test"
- sbt ++$TRAVIS_SCALA_VERSION "scripted rpm/* debian/* universal/*"
scala:
- 2.10.3
jdk:
- openjdk6
- openjdk7
- oraclejdk8
notifications:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.7.7-RC1")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "0.8.0")

// for autoplugins
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0-M4")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.0.0-M5")
```

For the native packager keys add this to your `build.sbt`
Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ scalacOptions in Compile ++= Seq("-deprecation", "-target:jvm-1.6")

libraryDependencies ++= Seq(
"org.apache.commons" % "commons-compress" % "1.4.1",
"org.vafer" % "jdeb" % "1.3" artifacts (Artifact("jdeb", "jar", "jar"))
"org.vafer" % "jdeb" % "1.3" artifacts (Artifact("jdeb", "jar", "jar")),
"org.scalatest" %% "scalatest" % "2.2.4" % "test"
)

site.settings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,11 @@ run() {
exit $exit_code
}

# Loads a configuration file full of default command line options for this script.
loadConfigFile() {
cat "$1" | sed '/^\#/d'
}

# Now check to see if it's a good enough version
# TODO - Check to see if we have a configured default java version, otherwise use 1.6
java_version_check() {
Expand Down Expand Up @@ -361,8 +366,7 @@ ${{template_declares}}
# java_cmd is overrode in process_args when -java-home is used
declare java_cmd=$(get_java_cmd)


# use JAVA_OPTS to prepend its contents to $@
set -- "$JAVA_OPTS" "$@"
# if configuration files exist, prepend their contents to $@ so it can be processed by this runner
[[ -f "$script_conf_file" ]] && set -- $(loadConfigFile "$script_conf_file") "$@"

run "$@"
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
# ${{daemon_user}} daemon user
# -------------------------------------------------

# Using $JAVA_OPTS envars
# Setting -Xmx and -Xms in Megabyte
# -mem 1024

Expand All @@ -32,8 +31,4 @@
# -jvm-debug <port>

# Don't run the java version check
# -no-version-check

# Example
# JAVA_OPTS=" -Dpidfile.path=/var/run/${{app_name}}/play.pid $JAVA_OPTS"
# JAVA_OPTS=" -mem 1024 -Dkey=val -jvm-debug $JAVA_OPTS"
# -no-version-check
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@
source /lib/init/vars.sh
source /lib/lsb/init-functions

# if configuration files exist, source it and use JAVA_OPTS to prepend their contents to $@
[[ -f /etc/default/${{app_name}} ]] && . /etc/default/${{app_name}}
# $JAVA_OPTS used in $RUN_CMD wrapper
export JAVA_OPTS

PIDFILE=/var/run/${{app_name}}/running.pid

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@
# Source function library.
. /etc/rc.d/init.d/functions

# if configuration files exist, source it and use JAVA_OPTS to prepend their contents to $@
[[ -f /etc/default/${{app_name}} ]] && . /etc/default/${{app_name}}
# $JAVA_OPTS used in $RUN_CMD wrapper
export JAVA_OPTS

prog="${{exec}}"

# FIXME The pid file should be handled by the executed script
Expand Down
63 changes: 53 additions & 10 deletions src/main/scala/com/typesafe/sbt/packager/FileUtil.scala
Original file line number Diff line number Diff line change
@@ -1,18 +1,61 @@
package com.typesafe.sbt
package packager

import java.io.File
import sbt.Process
import java.io.{ File, IOException }
import java.nio.file.{ Paths, Files }
import java.nio.file.attribute.{ PosixFilePermission, PosixFilePermissions }

/**
* Setting the file permissions
*/
object chmod {

/**
* Using java 7 nio API to set the permissions.
*
* @param file
* @param perms in octal format
*/
def apply(file: File, perms: String): Unit =
Process(Seq("chmod", perms, file.getAbsolutePath)).! match {
case 0 => ()
case n => sys.error("Error running chmod " + perms + " " + file)
}
def safe(file: File, perms: String): Unit =
try apply(file, perms)
catch {
case e: RuntimeException => ()
try {
Files.setPosixFilePermissions(file.toPath, permissions(perms))
} catch {
case e: IOException => sys.error("Error setting permissions " + perms + " on " + file.getAbsolutePath + ": " + e.getMessage)
}

}

/**
* Converts a octal unix permission representation into
* a java `PosiFilePermissions` compatible string.
*/
object permissions {

/**
* @param perms in octal format
* @return java 7 posix file permissions
*/
def apply(perms: String): java.util.Set[PosixFilePermission] = PosixFilePermissions fromString convert(perms)

def convert(perms: String): String = {
require(perms.length == 4 || perms.length == 3, s"Permissions must have 3 or 4 digits, got [$perms]")
// ignore setuid/setguid/sticky bit
val i = if (perms.length == 3) 0 else 1
val user = Character getNumericValue (perms charAt i)
val group = Character getNumericValue (perms charAt i + 1)
val other = Character getNumericValue (perms charAt i + 2)

asString(user) + asString(group) + asString(other)
}

private def asString(perm: Int): String = perm match {
case 0 => "---"
case 1 => "--x"
case 2 => "-w-"
case 3 => "-wx"
case 4 => "r--"
case 5 => "r-x"
case 6 => "rw-"
case 7 => "rwx"
}
}
181 changes: 126 additions & 55 deletions src/main/scala/com/typesafe/sbt/packager/docker/DockerPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import sbt.Keys.{
import packager.Keys._
import linux.LinuxPlugin.autoImport.{ daemonUser, defaultLinuxInstallLocation }
import universal.UniversalPlugin.autoImport.stage
import SbtNativePackager.Universal
import SbtNativePackager.{ Universal, Linux }

/**
* == Docker Plugin ==
Expand Down Expand Up @@ -65,7 +65,29 @@ object DockerPlugin extends AutoPlugin {
dockerExposedVolumes := Seq(),
dockerRepository := None,
dockerUpdateLatest := false,
dockerEntrypoint := Seq("bin/%s" format executableScriptName.value)
dockerEntrypoint := Seq("bin/%s" format executableScriptName.value),
dockerCmd := Seq(),
dockerCommands := {
val dockerBaseDirectory = (defaultLinuxInstallLocation in Docker).value
val user = (daemonUser in Docker).value
val group = (daemonGroup in Docker).value

val generalCommands = makeFrom(dockerBaseImage.value) +: makeMaintainer((maintainer in Docker).value).toSeq

generalCommands ++ Seq(
makeWorkdir(dockerBaseDirectory),
makeAdd(dockerBaseDirectory),
makeChown(user, group, "." :: Nil)
) ++
makeExposePorts(dockerExposedPorts.value) ++
makeVolumes(dockerExposedVolumes.value, user, group) ++
Seq(
makeUser(user),
makeEntrypoint(dockerEntrypoint.value),
makeCmd(dockerCmd.value)
)

}

) ++ mapGenericFilesToDocker ++ inConfig(Docker)(Seq(
executableScriptName := executableScriptName.value,
Expand All @@ -91,79 +113,128 @@ object DockerPlugin extends AutoPlugin {
target := target.value / "docker",

daemonUser := "daemon",
daemonGroup := daemonUser.value,
defaultLinuxInstallLocation := "/opt/docker",

dockerPackageMappings <<= sourceDirectory map { dir =>
MappingsHelper contentOf dir
},
dockerGenerateConfig <<= (dockerBaseImage, defaultLinuxInstallLocation,
maintainer, daemonUser, executableScriptName,
dockerExposedPorts, dockerExposedVolumes, target, dockerEntrypoint) map generateDockerConfig,
dockerGenerateConfig <<= (dockerCommands, target) map generateDockerConfig,
dockerTarget <<= (dockerRepository, packageName, version) map {
(repo, name, version) =>
repo.map(_ + "/").getOrElse("") + name + ":" + version
(repo, name, version) => repo.map(_ + "/").getOrElse("") + name + ":" + version
}
))

private[this] final def makeDockerContent(dockerBaseImage: String, dockerBaseDirectory: String, maintainer: String, daemonUser: String, execScript: String, exposedPorts: Seq[Int], exposedVolumes: Seq[String], entrypoint: Seq[String]) = {
val fromCommand = Cmd("FROM", dockerBaseImage)

val maintainerCommand: Option[Cmd] = {
if (maintainer.isEmpty)
None
else
Some(Cmd("MAINTAINER", maintainer))
}

/**
* @param maintainer (optional)
* @return MAINTAINER if defined
*/
private final def makeMaintainer(maintainer: String): Option[CmdLike] =
if (maintainer.isEmpty) None else Some(Cmd("MAINTAINER", maintainer))

/**
* @param dockerBaseImage
* @return FROM command
*/
private final def makeFrom(dockerBaseImage: String): CmdLike = Cmd("FROM", dockerBaseImage)

/**
* @param dockerBaseDirectory, the installation directory
* @param WORKDIR command, setting dockerBaseDirectory as cwd
*/
private final def makeWorkdir(dockerBaseDirectory: String): CmdLike = Cmd("WORKDIR", dockerBaseDirectory)

/**
* @param dockerBaseDirectory, the installation directory
* @return ADD command adding all files inside the installation directory
*/
private final def makeAdd(dockerBaseDirectory: String): CmdLike = {
val files = dockerBaseDirectory.split(java.io.File.separator)(1)
Cmd("ADD", s"$files /$files")
}

val dockerCommands = Seq(
Cmd("ADD", s"$files /$files"),
Cmd("WORKDIR", "%s" format dockerBaseDirectory),
ExecCmd("RUN", "chown", "-R", daemonUser, "."),
Cmd("USER", daemonUser),
ExecCmd("ENTRYPOINT", entrypoint: _*),
ExecCmd("CMD")
)

val exposeCommand: Option[CmdLike] = {
if (exposedPorts.isEmpty)
None
else
Some(Cmd("EXPOSE", exposedPorts.mkString(" ")))
}

// 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: _*)
)
}

val commands =
Seq(fromCommand) ++ maintainerCommand ++ volumeCommands ++ exposeCommand ++ dockerCommands
/**
* @param daemonUser
* @param daemonGroup
* @param directory to chown recursively
* @return chown command, owning the installation directory with the daemonuser
*/
private final def makeChown(daemonUser: String, daemonGroup: String, directories: Seq[String]): CmdLike =
ExecCmd("RUN", Seq("chown", "-R", s"$daemonUser:$daemonGroup") ++ directories: _*)

/**
* @param daemonUser
* @return USER docker command
*/
private final def makeUser(daemonUser: String): CmdLike = Cmd("USER", daemonUser)

/**
* @param entrypoint
* @return ENTRYPOINT command
*/
private final def makeEntrypoint(entrypoint: Seq[String]): CmdLike = ExecCmd("ENTRYPOINT", entrypoint: _*)

/**
* Default CMD implementation as default parameters to ENTRYPOINT.
* @param args
* @return CMD with args in exec form
*/
private final def makeCmd(args: Seq[String]): CmdLike = ExecCmd("CMD", args: _*)

/**
* @param exposedPorts
* @return if ports are exposed the EXPOSE command
*/
private final def makeExposePorts(exposedPorts: Seq[Int]): Option[CmdLike] = {
if (exposedPorts.isEmpty) None else Some(Cmd("EXPOSE", exposedPorts mkString " "))
}

Dockerfile(commands: _*).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.
*
* All directories created afterwards are chowned.
*
* @param exposedVolumes
* @return commands to create, chown and declare volumes
* @see http://stackoverflow.com/questions/23544282/what-is-the-best-way-to-manage-permissions-for-docker-shared-volumes
* @see https://docs.docker.com/userguide/dockervolumes/
*/
private final def makeVolumes(exposedVolumes: Seq[String], daemonUser: String, daemonGroup: String): Seq[CmdLike] = {
if (exposedVolumes.isEmpty) Seq.empty
else Seq(
ExecCmd("RUN", Seq("mkdir", "-p") ++ exposedVolumes: _*),
makeChown(daemonUser, daemonGroup, exposedVolumes),
ExecCmd("VOLUME", exposedVolumes: _*)
)
}

private[this] final def generateDockerConfig(
dockerBaseImage: String, dockerBaseDirectory: String, maintainer: String, daemonUser: String, execScript: String, exposedPorts: Seq[Int], exposedVolumes: Seq[String], target: File, entrypoint: Seq[String]
) = {
val dockerContent = makeDockerContent(dockerBaseImage, dockerBaseDirectory, maintainer, daemonUser, execScript, exposedPorts, exposedVolumes, entrypoint)
/**
* @param commands representing the Dockerfile
* @return String representation of the Dockerfile described by commands
*/
private final def makeDockerContent(commands: Seq[CmdLike]): String = Dockerfile(commands: _*).makeContent

/**
* @param commands, docker content
* @param target directory for Dockerfile
* @return Dockerfile
*/
private[this] final def generateDockerConfig(commands: Seq[CmdLike], target: File): File = {
val dockerContent = makeDockerContent(commands)

val f = target / "Dockerfile"
IO.write(f, dockerContent)
f
}

/**
* uses the `mappings in Unversial` to generate the
* `mappings in Docker`.
*/
def mapGenericFilesToDocker: Seq[Setting[_]] = {
def renameDests(from: Seq[(File, String)], dest: String) = {
for {
Expand Down
Loading

0 comments on commit c52f906

Please sign in to comment.