Generate wars for multiple environments with maven?

1k Views Asked by At

How do I generate wars for multiple environments with maven ?

  • I have about 8 environments. dev, dev_local, ci, ci_local, qa, qa_local, prod, prod_local.
  • I don't want to generate war files for *_local environments.
  • I have configuration files which are common to all environments. I would like to avoid maintaining duplicate copies of those files for various environments.
  • The common files may have few properties which need to be customized for various environments.
  • The manifest files in the wars should have environment specific information.
  • The resource files should be placed inside WEB-INF/classes directory.
1

There are 1 best solutions below

4
On BEST ANSWER

Environments-maven-plugin provides all the things listed above.

Environments-maven-plugin is a fork of multienv-maven-plugin. The core philosophy of multienv-maven-plugin is that each directory in src/main/environments directory is an environment and war will be generated for each directory. This philosophy is too restrictive. It provides simplicity at the expense of functionality. Environments maven plugin continues to use the directories in src/main/environments as the names of the various environments. But it extends the plugin further. Listed below are some of those details.

Exclusion of environments.

Most projects have resource files both for various environments and for running the code on the developer's machine but pointing to various environments. For example:dev, dev_local, ci, ci_local, qa, qa_local, etc. — these QA resources files are used to run the application in the QA environment. qa_local resource files are used to run the application on the developer machine pointing to the QA environment. When a release build is run, war files for dev_local, ci_local, and qa_local should not be generated. Otherwise, it slows down the build and clutters bamboo build pages, potentially causing confusion to the party performing the actual deployment.

environments-maven-plugin allows for the exclusion of environments for which generation of war files is undesirable. This can be done using the excludeEnvironments tag in the plugin configuration.

Example configuration

<plugin>
   <groupId>net.sf.environments-maven-plugin</groupId>
   <artifactId>environments-maven-plugin</artifactId>
   <version>1.1.0-SNAPSHOT</version>
   <configuration>
      <excludeEnvironments>dev01, qa01 ,qa02,</excludeEnvironments>
      <parallel>true</parallel>
   </configuration>
   <executions>
      <execution>
         <goals>
            <goal>configuration</goal>
         </goals>
      </execution>
   </executions>
</plugin>

Doesn't Allow Manifest Customization

When war files are generated for different environments, there may be cases where it is necessary to add environment-specific manifest entries. One can do this easily with environments-maven-plugin using the environmentArchiveConfiguration tag in the plugin configuration.

You can have one configuration for all environments or have a configuration for each of the individual environments, as shown below.

For ease, the environment is already added to each war file. The manifest entry looks like this.

<plugin>
         <groupId>net.sf.environments-maven-plugin</groupId>
         <artifactId>environments-maven-plugin</artifactId>
         <version>1.1.0-SNAPSHOT</version>
         <configuration>
            <excludeEnvironments>dev01, qa01</excludeEnvironments>
            <parallel>true</parallel>
            <archives>
               <environmentArchiveConfiguration>
                  <environment>dev02</environment>
                  <archive>
                     <manifestEntries>
                        <mode>development</mode>
                        <key>value</key>
                     </manifestEntries>
                  </archive>
               </environmentArchiveConfiguration>
               <environmentArchiveConfiguration>
                  <environment>prod01, prod02</environment>
                  <archive>
                     <manifestEntries>
                        <mode>development1</mode>
                        <key>value1</key>
                     </manifestEntries>
                  </archive>
               </environmentArchiveConfiguration>
            </archives>
         </configuration>
         <executions>
            <execution>
               <goals>
                  <goal>configuration</goal>
               </goals>
            </execution>
         </executions>
      </plugin>

Environment-Specific Filtering

environments-maven-plugin introduces the commonDir and filters tags. commonDir tag takes the path of a directory inside src/main/environnments. The common directory contains templates that are common to all environments. These templates can have variables that are enclosed by @ (@variable@).

The filters tag can have multiple filter tags. Each filter tag takes the name of a file.

When the wars are built, the plugin does the following.

  • Takes the template files from the common directory.
  • Retrieves the files with the names declared in filter tags from the environment directory. For example, if dev war is currently being built, the plugin tries to find the files from filter tags in the src/main/environments/dev directory.
  • Merges all the key-value pairs from all the files in filter tags into a HashMap.
  • Uses those key-value pairs from the HashMap to substitute variables in the template.
  • Packages the files from the previous step in the dev war file.
  • Also packages all the files from src/main/environments/${env} directory.

Example Configuration

<plugin>
   <groupId>net.sf.environments-maven-plugin</groupId>
   <artifactId>environments-maven-plugin</artifactId>
   <version>1.3.0-SNAPSHOT</version>
   <configuration>
      <commonDir>common</commonDir>
      <filters>
         <filter>ec.properties</filter>
      </filters>
   </configuration>
   <executions>
      <execution>
         <goals>
            <goal>environment</goal>
         </goals>
      </execution>
   </executions>
</plugin>

The image below illustrates the file/directory naming/structure.

enter image description here

NOTE: For filtering to work, both commonDir and filters tags should be declared.

Facility to Place the Resource Files in a Specific Directory Inside the War File

Resource files usually belong in WEB-INF/classes directory inside the war file. To achieve this in multimaven-maven-plugin, one would have to create WEB-INF/classes directory in each of the environments inside src/main/environments and then place the resource files inside them. As shown below this is redundant and ugly.

  • src
    • main
      • environments
        • dev
          • WEB-INF
            • classes
              • files
        • dev_local
          • WEB-INF
            • classes
              • files
        • ci
          • WEB-INF
            • classes
              • files
        • ci_local
          • WEB-INF
            • classes
              • files

To solve this, environments-maven-plugin introduces a new tag called targetPath. If this tag is declared with a directory path for value, the resources files packaged in the war are placed inside the directory. So. it looks like this.

  • app_dev.jar
    • WEB-INF
      • classes
        • files
  • app_dev_local.jar
    • WEB-INF
      • classes
        • files
  • app_ci.jar
    • WEB-INF
      • classes
        • files
  • app_ci_local.jar
    • WEB-INF
      • classes
        • files

So the directory structure looks like this in that case.

  • src
    • main
    • environments
      • dev
        • files
      • dev_local
        • files
      • ci
        • files
      • ci_local
        • files

Example Configuration

<plugin>
   <groupId>net.sf.environments-maven-plugin</groupId>
   <artifactId>environments-maven-plugin</artifactId>
   <version>1.1.0-SNAPSHOT</version>
   <configuration>
      <targetPath>WEB-INF/classes</targetPath>
   </configuration>
   <executions>
      <execution>
         <goals>
            <goal>environment</goal>
         </goals>
      </execution>
   </executions>
</plugin>

Building a Single Environment

multienv-maven-plugin doesn't provide the facility to build a single environment. The result is that one will have to maintain an entirely separate set-up just for individual environments. environments-maven-plugin solves this problem with a command line option -Dem.env. The value of the option is the directory name of the environment of interest in src/main/environment.

mvn clean install -Dem.env=dev_local

Resources

Project Page: https://sourceforge.net/projects/environments-maven-plugin/

Source Code: https://sourceforge.net/p/environments-maven-plugin/code/ci/master/tree/

Overview page: https://environments-maven-plugin.sourceforge.io/index.html

Edit

This is what https://12factor.net/config says about config.

Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not.

environments-maven-plugin enables separating configuration from code.

Another aspect of config management is grouping. Sometimes apps batch config into named groups (often called “environments”) named after specific deploys, such as the development, test, and production environments in Rails. This method does not scale cleanly: as more deploys of the app are created, new environment names are necessary, such as staging or qa. As the project grows further, developers may add their own special environments like joes-staging, resulting in a combinatorial explosion of config which makes managing deploys of the app very brittle.

Notice that it suggests that maintaining separate properties files for different environments is brittle, but doesn't suggest any alternatives. While somebody more knowledgeable can argue on the merits and demerits of such an idea, in the real world property files are grouped into various environments. And when that is the case, there is a need to package those groups properly in war files or deliver those groups to respective environment directly and hope that the person doing the deployment doesn't have ignorance to plead and politics to play. environments-maven-plugin takes the approach of packaging the resource files into wars and generating multiple wars. Once you have a war, CI server can take over the responsibility of doing the deployment. No need to worry about placing the files in proper location on the server or creating the environment variables. If there is a canonical solution for this problem, it would be nice to know.

Also notice that it doesn't say anything about creating multiple wars.


Note that there was no canonical way prescribed to invalidate the suggested solution.

1.) You should not store passwords in text files. Accepted practice is to store them in environment variables on the servers. But may be there are more secure ways.

2.) Building multiple wars is no more unreliable than building a single war assuming the same versions of java and maven. When we use a particular version of java and maven, the unspoken assumption is that given a chunk of code, multiple iterations of building will produce the same exact war.

3.) One should not rely on the war deployed to production as the sole source of truth. That is a single point of failure. Instead, using release branches to manage the code being released will ensure that you have a war which can be consistently regenerated. This saves you trouble if the war file were to get corrupted, accidentally deleted etc..

4.) The usual practice to reproduce the production defects is to run the release branch on local machine. Not get a copy of the war deployed on production.

5.) Space can be an issue when one is saving these files indefinitely.

6.) Building all the wars using this plugin is definitely slow. But that should be in its own maven profile. That profile should be executed on the CI server. One should only build war file for a single environment on local machine. Plugin enables this.

If Tn= the time taken to build n wars is much less than T1= the the time taken to build one war then with this plugin (Tn) is quite a bit less than (n * T1)

7.) Unless one is working in somebody's garage or basement, it is highly unlikely that one would be allowed to change anything in production in the java world. But if you think you like the flexibility of changing the configuration files at will in production, this plugin doesn't allow that. Just know that this introduces arbitrariness. i.e. there is no guarantee that the text files you shipped to production are the same text files currently in production.

So to conclude, considering there wasn't a clear invalidation or even undermining of the approach taken in the past three months, it is safe to assume that building multiple wars is as acceptable as shipping configuration files separately. It just depends on what one prefers, be it a religious adherence to accepted practices or a simpler approach which removes the need for intermediaries in the form of support or otherwise.