Service packaging
- Prerequisites
- Project initialization
- Project directory structure
- Project configuration
- Properties interpolation
Prerequisites
Packaging a new service/tool requires Maven to be correctly installed and configured, as mentioned in the development environment page.
A practical experience of Maven is recommended; the Maven in 5 minutes guide is a good starting point for beginners.
Project initialization
A new Project Factory package can be easily created from a template, using a Maven archetype.
To do so, execute one of the following commands in the directory under which the new package should be created:
-
for a generic system, admin or service package:
mvn archetype:generate -DarchetypeGroupId=fr.project-factory.core.archetypes -DarchetypeArtifactId=package -DarchetypeVersion=3.4.0-SNAPSHOT -DarchetypeRepository=https://services.crespel.me/nexus/repository/project-factory/
-
for a webapp service package (with embedded Tomcat):
mvn archetype:generate -DarchetypeGroupId=fr.project-factory.core.archetypes -DarchetypeArtifactId=webapp -DarchetypeVersion=3.4.0-SNAPSHOT -DarchetypeRepository=https://services.crespel.me/nexus/repository/project-factory/
The interactive mode of the archetype plugin will prompt you to enter the following details:
- groupId: as you wish (e.g.
fr.project-factory.packages.services
for official service packages). - artifactId: name of the packaged service/tool, in lowercase (e.g.
jenkins
). - version: version of the packaged service/tool, must contain a release number (e.g.
1.505-1-SNAPSHOT
for Jenkins 1.505, package release 1, SNAPSHOT). - package: ignored, leave the suggested value.
- type:
system
,admin
orservice
.
After this intialization, a pom.xml
will be generated as well as the base directory structure.
Project directory structure
Any Project Factory package must conform to a specific directory structure, based on the directories generated by the archetype.
For example, for a “foobar” service package:
+---pom.xml Maven project
\---src
\---main
+---product Root of the packaged product
| +---app
| | \---services
| | \---foobar Application binaries, static configuration
| +---backup
| | \---services
| | \---foobar Data backups
| +---bin
| | +---backup.d
| | | +---backup-foobar.sh Backup script
| | | \---restore-foobar.sh Restore script
| | +---cron.5mins
| | | \---foobar-ldap-sync.sh Script executed every 5 minutes
| | +---cron.hourly
| | | \---foobar-indexer.sh Script executed every hour
| | +---cron.daily
| | \---foobar-mail-notifications.sh Script executed every day
| +---data
| | \---services
| | \---foobar Application data (uploads, builds, dynamic config)
| \---log
| \---services
| \---foobar Application logs
\---scripts
+---build.xml Ant script executed during package build
+---preinstall.sh Shell script executed before installation on the target system
+---postinstall.sh Shell script executed after installation on the target system
+---preremove.sh Shell script executed before uninstallation on the target system
\---postremove.sh Shell script executed after uninstallation on the target system
For system and admin package types, replace the services
directory with system
and admin
.
A service package may also install files in a system
or admin
directory if it depends on it (e.g. Apache or Nagios configuration).
Project configuration
Package dependencies
System or Project Factory package dependencies may be specified in the project pom.xml
. For example:
<properties>
<package.dependencies>
${package.prefix}-system-httpd,
${package.prefix}-system-tomcat,
${package.prefix}-service-portal,
${system.package.subversion},
${system.package.git}
</package.dependencies>
</properties>
IMPORTANT: any dependency to a system package must reference a property in the system properties file of the default product (e.g. system-el7-x86_64.properties
).
NOTICE: all packages include a dependency to ${package.prefix}-core
by default, it is not necessary to add it explicitly.
Downloading external sources
A third-party zip/tar.gz/tar.bz2 archive may be downloaded automatically by adding the following snippet to the pom.xml
:
<properties>
<!-- URL of the directory containing the file to download -->
<package.src.url>http://example.org/foobar/downloads</package.src.url>
<!-- Name of the file to download -->
<package.src.file>foobar-${package.version}.zip</package.src.file>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>wagon-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
The local path of the downloaded file is ${project.downloaded.directory}/${package.src.file}
.
The content of the downloaded archive may then be extracted using the following snippet:
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<executions>
<execution>
<id>process-resources-extract</id>
<phase>process-resources</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<skip>${package.disabled}</skip>
<target>
<!-- Extract a tar.gz archive, ignoring its root directory -->
<untar compression="gzip"
src="${project.downloaded.directory}/${package.src.file}"
dest="${project.app.directory}">
<cutdirsmapper dirs="1" />
</untar>
<!-- Extract a zip archive, ignoring its root directory -->
<unzip src="${project.downloaded.directory}/${package.src.file}"
dest="${project.app.directory}">
<cutdirsmapper dirs="1" />
</unzip>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
Refer to the Ant manual (and particularly the Unzip/Untar tasks) for more information.
Tomcat server embedding
Java webapps may easily embed a dedicated Tomcat server by following these steps:
- Use the webapp archetype to initialize the project.
-
Define unused shutdown/ajp/http ports in the
product-default.properties
file (and custom products if necessary), for example:foobar.port.shutdown=18505 foobar.port.ajp=18509 foobar.port.http=18580
- Install the webapp in the
${project.app.directory}/webapps
directory. For this you may use external source download, dependency copy or the “WAR overlay” method.
You may refer to existing packages for sample implementations.
Properties interpolation
Property definition
It is recommended to define Maven properties to make the package configurable and easier to maintain (e.g. directories, database name, default language, etc.)
These properties may be defined in two locations:
- Private properties: for properties only used by the current package, use the
<properties>
section of thepom.xml
file:
<properties>
<package.disabled>${foobar.disabled}</package.disabled>
<foobar.src.url>http://example.org/foobar/downloads</foobar.src.url>
<foobar.src.file>foobar-${project.version.trimmed}.zip</foobar.src.file>
<foobar.db.name>${product.id}_foobar</foobar.db.name>
<foobar.db.user>${product.id}_foobar</foobar.db.user>
</properties>
-
Shared properties: for properties that may be used by other packages (e.g. port numbers), use the
product-default.properties
file in theproducts/default/src/main/resources
directory ofproject-factory-core
:# Foobar properties foobar.host=127.0.0.1 foobar.port=3615
Build-time interpolation
Files in the src/product
and src/rpm/scripts
directories may reference Maven properties, that will be automatically replaced by their value during the build.
Two kinds of interpolation are available:
- Simple interpolation: use
@{prop.value}
placeholders, whereprop.value
is a Maven property. For example:
<config>
<version>@{package.version}</version>
<authSource>@{foobar.authsource}</authSource>
</config>
- Advanced interpolation: add
.vm
to the file extension to make it a Velocity template. For example:
#set( $PACKAGE_VERSION = ${project.properties.getProperty('package.version')} )
#set( $CAS_ENABLED = ${project.properties.getProperty('cas.enabled')} )
<config>
<version>${PACKAGE_VERSION}</version>
#if( ${CAS_ENABLED} == "1" || ${CAS_ENABLED} == "true" )
<authSource>cas</authSource>
#else
<authSource>ldap</authSource>
#end
</config>
Refer to the Velocity user guide for more information about the Velocity template syntax.
Install-time interpolation
Certain properties, like auto-generated passwords, can only be replaced when the package is installed on the target system.
- In the concerned files, use
%{VARIABLE_NAME}
placeholders. For example:
<config>
<dbType>mysql</dbType>
<dbUser>@{foobar.db.user}</dbUser>
<dbPassword>%{FOOBAR_DB_PASSWORD}@</dbPassword>
</config>
-
In the
src/rpm/scripts
directory, add the following snippet to thepostinstall.sh
file:# Generate and store a random password ensurepassword FOOBAR_DB_PASSWORD # Interpolate variables in template files interpolatetemplate_inplace "@{foobar.app}/conf/database.properties" interpolatetemplate_inplace "@{foobar.app}/conf/config.xml"
NOTICE: as shown above, build-time interpolated properties may be used in scripts.