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 or service.

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 the pom.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 the products/default/src/main/resources directory of project-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, where prop.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 the postinstall.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.