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

Allow script templates to be overridden #121

Merged
merged 4 commits into from
Jan 6, 2014
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions src/main/scala/com/typesafe/sbt/packager/archetypes/JavaApp.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ package archetypes
import Keys._
import sbt._
import sbt.Project.Initialize
import sbt.Keys.{ mappings, target, name, mainClass, normalizedName }
import sbt.Keys.{ mappings, target, name, mainClass, normalizedName, sourceDirectory }
import linux.LinuxPackageMapping
import SbtNativePackager._

Expand Down Expand Up @@ -43,15 +43,16 @@ object JavaAppPackaging {
} yield JavaAppBashScript.makeDefines(cn, appClasspath = cp, extras = extras)
hasMain getOrElse Nil
},
makeBashScript <<= (bashScriptDefines, target in Universal, normalizedName) map makeUniversalBinScript,
// TODO - Overridable bash template.
makeBashScript <<= (bashScriptDefines, target in Universal, normalizedName, sourceDirectory) map makeUniversalBinScript,
batScriptExtraDefines := Nil,
batScriptReplacements <<= (normalizedName, Keys.mainClass in Compile, scriptClasspath, batScriptExtraDefines) map { (name, mainClass, cp, extras) =>
mainClass map { mc =>
JavaAppBatScript.makeReplacements(name = name, mainClass = mc, appClasspath = cp, extras = extras)
} getOrElse Nil

},
makeBatScript <<= (batScriptReplacements, target in Universal, normalizedName) map makeUniversalBatScript,
makeBatScript <<= (batScriptReplacements, target in Universal, normalizedName, sourceDirectory) map makeUniversalBatScript,
mappings in Universal <++= (makeBashScript, normalizedName) map { (script, name) =>
for {
s <- script.toSeq
Expand All @@ -73,21 +74,27 @@ object JavaAppPackaging {
else "../" + name
}

def makeUniversalBinScript(defines: Seq[String], tmpDir: File, name: String): Option[File] =
def makeUniversalBinScript(defines: Seq[String], tmpDir: File, name: String, sourceDir: File): Option[File] =
if (defines.isEmpty) None
else {
val scriptBits = JavaAppBashScript.generateScript(defines)
val defaultTemplateLocation = sourceDir / "templates" / "bash-template"
val scriptBits =
if(defaultTemplateLocation.exists) JavaAppBashScript.generateScript(defines, defaultTemplateLocation.toURI.toURL)
else JavaAppBashScript.generateScript(defines)
val script = tmpDir / "tmp" / "bin" / name
IO.write(script, scriptBits)
// TODO - Better control over this!
script.setExecutable(true)
Some(script)
}

def makeUniversalBatScript(replacements: Seq[(String, String)], tmpDir: File, name: String): Option[File] =
def makeUniversalBatScript(replacements: Seq[(String, String)], tmpDir: File, name: String, sourceDir: File): Option[File] =
if (replacements.isEmpty) None
else {
val scriptBits = JavaAppBatScript.generateScript(replacements)
val defaultTemplateLocation = sourceDir / "templates" / "bat-template"
val scriptBits =
if(defaultTemplateLocation.exists) JavaAppBatScript.generateScript(replacements, defaultTemplateLocation.toURI.toURL)
else JavaAppBatScript.generateScript(replacements)
val script = tmpDir / "tmp" / "bin" / (name + ".bat")
IO.write(script, scriptBits)
Some(script)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.typesafe.sbt.packager.archetypes

import java.net.URL

/**
* Constructs a bash script for running a java application.
*
Expand Down Expand Up @@ -33,10 +35,10 @@ object JavaAppBashScript {
val fullString = cp map (n => "$lib_dir/"+n) mkString ":"
"declare -r app_classpath=\""+fullString+"\"\n"
}
def generateScript(defines: Seq[String]): String = {
def generateScript(defines: Seq[String], template: URL = bashTemplateSource): String = {
val defineString = defines mkString "\n"
val replacements = Seq("template_declares" -> defineString)
TemplateWriter.generateScript(bashTemplateSource, replacements)
TemplateWriter.generateScript(template, replacements)
}

def configFileDefine(configFile: String) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ object JavaAppBatScript {
}

def generateScript(
replacements: Seq[(String,String)]): String =
TemplateWriter.generateScript(bashTemplateSource, replacements, "\r\n", TemplateWriter.batFriendlyKeySurround)
replacements: Seq[(String,String)], template: java.net.URL = bashTemplateSource): String =
TemplateWriter.generateScript(template, replacements, "\r\n", TemplateWriter.batFriendlyKeySurround)

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.typesafe.sbt.packager.archetypes

import java.io.File
import java.net.URL

/**
* Constructs an start script for running a java application.
*
Expand All @@ -8,31 +11,32 @@ object JavaAppStartScript {

import ServerLoader._

protected def upstartTemplateSource: java.net.URL = getClass.getResource("upstart-template")
protected def sysvinitTemplateSource: java.net.URL = getClass.getResource("sysvinit-template")
protected def postinstTemplateSource: java.net.URL = getClass.getResource("postinst-template")
protected def postinstSysvinitTemplateSource: java.net.URL = getClass.getResource("postinst-sysvinit-template")
protected def preremTemplateSource: java.net.URL = getClass.getResource("prerem-template")


def generateScript(replacements: Seq[(String, String)], loader: ServerLoader): String =
loader match {
case Upstart =>
TemplateWriter.generateScript(upstartTemplateSource, replacements)
case SystemV =>
TemplateWriter.generateScript(sysvinitTemplateSource, replacements)
protected def upstartTemplateSource: URL = getClass.getResource("upstart-template")
protected def sysvinitTemplateSource: URL = getClass.getResource("sysvinit-template")
protected def postinstTemplateSource: URL = getClass.getResource("postinst-template")
protected def postinstSysvinitTemplateSource: URL = getClass.getResource("postinst-sysvinit-template")
protected def preremTemplateSource: URL = getClass.getResource("prerem-template")


def defaultStartScriptTemplate(loader: ServerLoader, defaultLocation: File): URL =
if(defaultLocation.exists) defaultLocation.toURI.toURL
else loader match {
case Upstart => upstartTemplateSource
case SystemV => sysvinitTemplateSource
}


def generatePrerm(appName: String): String =
TemplateWriter.generateScript(preremTemplateSource, Seq("app_name" -> appName))
def generatePrerm(appName: String, template: java.net.URL = preremTemplateSource): String =
TemplateWriter.generateScript(template, Seq("app_name" -> appName))


def generatePostinst(appName: String, loader: ServerLoader): String =
loader match {
case Upstart =>
def generatePostinst(appName: String, loader: ServerLoader, template: Option[java.net.URL] = None): String =
(template, loader) match {
// User has overriden the default.
case (Some(template), _) => TemplateWriter.generateScript(template, Seq("app_name" -> appName))
case (_, Upstart) =>
TemplateWriter.generateScript(postinstTemplateSource, Seq("app_name" -> appName))
case SystemV =>
case (_, SystemV) =>
TemplateWriter.generateScript(postinstSysvinitTemplateSource, Seq("app_name" -> appName))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package archetypes

import Keys._
import sbt._
import sbt.Keys.{ target, mainClass, normalizedName }
import sbt.Keys.{ target, mainClass, normalizedName, sourceDirectory }
import SbtNativePackager._
import com.typesafe.sbt.packager.linux.{ LinuxFileMetaData, LinuxPackageMapping, LinuxSymlink, LinuxPlugin }

Expand All @@ -28,6 +28,7 @@ object JavaServerAppPackaging {
Seq(
serverLoading := Upstart,
daemonUser := Users.Root,
// This one is begging for sbt 0.13 syntax...
debianStartScriptReplacements <<= (
maintainer in Debian, packageSummary in Debian, serverLoading in Debian, daemonUser in Debian, normalizedName,
sbt.Keys.version, defaultLinuxInstallLocation, mainClass in Compile, scriptClasspath)
Expand All @@ -45,9 +46,18 @@ object JavaServerAppPackaging {
appMainClass = mainClass.get,
daemonUser = daemonUser)
},
debianMakeStartScript <<= (debianStartScriptReplacements, normalizedName, target in Universal, serverLoading in Debian)
// TODO - Default locations shouldn't be so hacky.
linuxStartScriptTemplate in Debian <<= (serverLoading in Debian, sourceDirectory) map { (loader, dir) =>
JavaAppStartScript.defaultStartScriptTemplate(loader, dir / "templates" / "start")
},
debianMakeStartScript <<= (debianStartScriptReplacements, normalizedName, target in Universal, linuxStartScriptTemplate in Debian)
map makeDebianStartScript,
debianMakeEtcDefault <<= (normalizedName, target in Universal, serverLoading in Debian)
linuxEtcDefaultTemplate in Debian <<= sourceDirectory map { dir =>
val overrideScript = dir / "templates" / "etc-default"
if(overrideScript.exists) overrideScript.toURI.toURL
else etcDefaultTemplateSource
},
debianMakeEtcDefault <<= (normalizedName, target in Universal, serverLoading in Debian, linuxEtcDefaultTemplate in Debian)
map makeEtcDefaultScript,
linuxPackageMappings in Debian <++= (debianMakeEtcDefault, normalizedName) map { (conf, name) =>
conf.map(c => LinuxPackageMapping(Seq(c -> ("/etc/default/" + name))).withConfig()).toSeq
Expand All @@ -58,7 +68,6 @@ object JavaServerAppPackaging {
case Upstart => ("/etc/init/" + name + ".conf", "0644")
case SystemV => ("/etc/init.d/" + name, "0755")
}

for {
s <- script.toSeq
} yield LinuxPackageMapping(Seq(s -> path)).withPerms(permissions)
Expand All @@ -79,10 +88,10 @@ object JavaServerAppPackaging {
debianMakePostinstScript <<= (normalizedName, target in Universal, serverLoading in Debian) map makeDebianPostinstScript)

private def makeDebianStartScript(
replacements: Seq[(String, String)], name: String, tmpDir: File, loader: ServerLoader): Option[File] =
replacements: Seq[(String, String)], name: String, tmpDir: File, template: URL): Option[File] =
if (replacements.isEmpty) None
else {
val scriptBits = JavaAppStartScript.generateScript(replacements, loader)
val scriptBits = TemplateWriter.generateScript(template, replacements)
val script = tmpDir / "tmp" / "init" / name
IO.write(script, scriptBits)
Some(script)
Expand All @@ -102,11 +111,11 @@ object JavaServerAppPackaging {
Some(script)
}

protected def makeEtcDefaultScript(name: String, tmpDir: File, loader: ServerLoader): Option[File] = {
protected def makeEtcDefaultScript(name: String, tmpDir: File, loader: ServerLoader, source: java.net.URL): Option[File] = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove loader: ServerLoader as the /etc/default/<name> should be generated with Upstart and SystemV. The we can close #98 as this fixes everything! Just place etc-default in the src/templates folder. And I would add the debianScriptReplacements as gives us the ability to generate for example pids based on the applications name, etc.

Copy link
Member Author

Choose a reason for hiding this comment

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

As I mention below, not quite ready for this. We need to unify the two scripts a bit more before I'm comfortable doing so. The config formats are slightly different (ENV variables vs. command line arguments), and the location would be the same...

loader match {
case Upstart => None
case SystemV => {
val scriptBits = TemplateWriter.generateScript(etcDefaultTemplateSource, Seq.empty)
val scriptBits = TemplateWriter.generateScript(source, Seq.empty)
val script = tmpDir / "tmp" / "etc" / "default" / name
IO.write(script, scriptBits)
Some(script)
Expand Down
3 changes: 3 additions & 0 deletions src/main/scala/com/typesafe/sbt/packager/linux/Keys.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ trait Keys {
val linuxPackageMappings = TaskKey[Seq[LinuxPackageMapping]]("linux-package-mappings", "File to install location mappings including owner and privileges.")
val linuxPackageSymlinks = TaskKey[Seq[LinuxSymlink]]("linux-package-symlinks", "Symlinks we should produce in the underlying package.")
val generateManPages = TaskKey[Unit]("generate-man-pages", "Shows all the man files in the current project")

val linuxStartScriptTemplate = TaskKey[URL]("linuxStartScriptTemplate", "The location of the template start script file we use for debian (upstart or init.d")
val linuxEtcDefaultTemplate = TaskKey[URL]("linuxEtcDefaultTemplate", "The location of the /etc/default/<pkg> template script.")
}

object Keys extends Keys {
Expand Down
121 changes: 120 additions & 1 deletion src/sphinx/archetypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ still leveraging the default configuration for other platforms.

Curently, in the nativepackager these archetypes are available:

* Java Command Line Application (Experimental)
* Java Command Line Application
* Java Server Application (Experimental - Debian Only)


Java Command Line Application
Expand All @@ -24,6 +25,7 @@ A Java Command Line application is a Java application that consists of a set of
custom start scripts, or services. It is just a bash/bat script that starts up a Java project. To use
this archetype in your build, do the following in your ``build.sbt``:

.. code-block:: scala

packageArchetype.java_application

Expand All @@ -47,6 +49,7 @@ this archetype in your build, do the following in your ``build.sbt``:
This archetype will use the ``mainClass`` setting of sbt (automatically discovers your main class) to generate ``bat`` and ``bin`` scripts for your project. It
produces a universal layout that looks like the following:

.. code-block:: none

bin/
<app_name> <- BASH script
Expand All @@ -56,3 +59,119 @@ produces a universal layout that looks like the following:


You can add additional files to the project by placing things in ``src/windows``, ``src/universal`` or ``src/linux`` as needed.


Java Server
-----------

This archetype is designed for Java applications that are intended to run as
servers or services. This archetype includes wiring such that the application
is started immediately upon startup.

Currently supported operating systems:

* Ubuntu 12.04 LTS - Upstart
* Ubuntu 12.04 LTS - init.d


The Java Server archetype has a similar installation layout as the java
application. The primary differneces are:
Copy link
Contributor

Choose a reason for hiding this comment

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

just a type

Copy link
Member Author

Choose a reason for hiding this comment

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

?


* Linux

* ``/var/log/<pkg>`` is symlinked from ``<install>/log``

* Creates a start script in ``/etc/init.d`` or ``/etc/init/``

* Creates a startup config file in ``/etc/default/<pkg>``


For Debian servers, you can select to either use SystemV or Upstart for your servers. By default, Upstart (the current Ubuntu LTS default), is used. To switch to SystemV, add the following:

.. code-block:: scala

import NativePackagerKeys._
import com.typesafe.sbt.packager.archetypes.ServerLoader

serverLoading in Debian := ServerLoader.SystemV

By default, the native packager will install and run services using the ``root`` user and group. This is not a good default for services, which should not be exposed to root access. You can change the installation and usage user via the ``daemonUser`` key:

.. code-block:: scala

daemonUser in Debian := "my_app_user"

The archetype will automatically append/prepend the creation/deletion of the user
to your packaging for Debian.
Copy link
Contributor

Choose a reason for hiding this comment

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

We should point out here that on apt-get purge <dpkg> all specified users are deleted.
Color it red, with exclamation mark 🔴

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.



Copy link
Contributor

Choose a reason for hiding this comment

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

I would recommend to write something about #98 (Java_Opts) and #120 (Better upstart script) for handling running instances in Debian.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good point.

Overriding Templates
--------------------

You can override the default template used to generate any of the scripts in
any archetype. Listed below are the overridable files and variables that
you can use when generating scripts.

``src/templates/bat-template``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Creating a file here will override the default template used to
generate the ``.bat`` script for windows distributions.

**Syntax**

``@@APP_ENV_NAME@@`` - will be replaced with the script friendly name of your package.

``@@APP_NAME@@`` - will be replaced with user friendly name of your package.

``@APP_DEIFNES@@`` - will be replaced with a set of variable definitions, like
Copy link
Contributor

Choose a reason for hiding this comment

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

Not @APP_DEIFNES@@ but @@APP_DEFINES@@

``APP_MAIN_CLASS``, ``APP_MAIN_CLASS``.

You can define addiitonal variable deifnitions using ``batScriptExtraDefines``.
Copy link
Contributor

Choose a reason for hiding this comment

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

deifnitions => definitions


``src/templates/bash-template``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Creating a file here will override the default template used to
generate the BASH start script found in ``bin/<application>`` in the
universal distribution

**Syntax**

``${{template_declares}}`` - Will be replaced with a series of ``declare <var>``
lines based on the ``bashScriptDefines`` key. You can add more defines to
the ``bashScriptExtraDefines`` that will be used in addition to the default set:

* ``app_mainclass`` - The main class entry point for the application.
* ``app_classpath`` - The complete classpath for the application (in order).



``src/templates/start``
~~~~~~~~~~~~~~~~~~~~~~~

Creating a file here will override either the init.d startup script or
the upstart start script. It will either be located at
``/etc/init/<application>`` or ``/etc/init.d/<application>`` depending on which
serverLoader is being used.

**Syntax**

You can use ``${{variable_name}}`` to reference variables when writing your scirpt. The default set of variables is:

* ``descr`` - The description of the server.
* ``author`` - The configured author name.
* ``exec`` - The script/binary to execute when starting the server
* ``chdir`` - The working directory for the server.
* ``retries`` - The number of times to retry starting the server.
* ``retryTimeout`` - The amount of time to wait before trying to run the server.
* ``app_name`` - The name of the application (linux friendly)
* ``app_main_class`` - The main class / entry point of the application.
* ``app_classpath`` - The (ordered) classpath of the application.
* ``daemon_user`` - The user that the server should run as.

``src/templates/etc-default``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Creating a file here will override the ``/etc/default/<application>`` template
used when SystemV is the server loader.
Copy link
Contributor

Choose a reason for hiding this comment

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

This feature is too cool to be used only with systemV, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

If you figure out how to wire it up to upstart, we can merge it :). I was mostly documenting the state of thing as they are. I agree it's a very very nice feature for servers.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I need to check the SystemV support. Could be we just use the config feature in the batch file directly and throw that in /etc/default.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, so it looks like the two systems are actually using two different mechanisms of starting.

Right now the SystemV script is a complete script rather than just a wrapper to the BASH script we generate, like upstart. Because of this, currently, they can't share the same /etc/default file. If someone who uses SystemV could detangle that, we can make the default location for config /etc/default/<pkg> and then both scripts will use it.

unfortunately, things like USER= wouldn't work for SystemV.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure if both startup system have to work with the same /etc/default. It would be nice, however IMHO there is no case where you have the same application installed with the same config and two different startsystems.

So in the end your config file looks different for different start systems, which I think is fine. Still the SystemV script has a lot of hardcoded stuff

Upstart

# 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") "$@"

Doesn't rely on environment variables.

SystemV

test -e /etc/default/${{app_name}} && source /etc/default/${{app_name}}

Relies on environment variables and isn't able to set Java_Opts

Loading