# GitLab CI template for Maven

This project implements a GitLab CI/CD template to build, test and analyse your [Maven](https://maven.apache.org/)-based projects.

## Usage

In order to include this template in your project, add the following to your `gitlab-ci.yml`:

```yaml
include:
  - project: 'to-be-continuous/maven'
    ref: '3.5.0'
    file: '/templates/gitlab-ci-maven.yml'
```

## Global configuration

The Maven template uses some global configuration throughout all jobs.

| Name                  | description                                                                                                        | default value                                                                                                                                  |
| --------------------- |--------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------|
| `MAVEN_IMAGE`         | The Docker image used to run Maven <br/>:warning: **set the version required by your project**                     | `registry.hub.docker.com/library/maven:latest`                                                                                                 |
| `MAVEN_PROJECT_DIR`   | Maven projet root directory                                                                                        | `.`                                                                                                                                            |
| `MAVEN_CFG_DIR`       | The Maven configuration directory                                                                                  | `.m2`                                                                                                                                          |
| `MAVEN_SETTINGS_FILE`       | The Maven `settings.xml` file path                                                                        | `${MAVEN_CFG_DIR}/settings.xml`                                                                                                                |
| `MAVEN_OPTS`          | [Global Maven options](http://maven.apache.org/configure.html#maven_opts-environment-variable)                     | `-Dhttps.protocols=TLSv1.2 -Dmaven.repo.local=${MAVEN_CFG_DIR}/repository -Dorg.slf4j.simpleLogger.showDateTime=true -Djava.awt.headless=true` |
| `MAVEN_CLI_OPTS`      | Additional [Maven options](https://maven.apache.org/ref/3-LATEST/maven-embedder/cli.html) used on the command line | `--no-transfer-progress --batch-mode --errors --fail-at-end --show-version -DinstallAtEnd=true -DdeployAtEnd=true`                             |

### About `$MAVEN_CFG_DIR`

This variable is used to define the Maven configuration directory. It is used to declare the cache policy and marked the `${MAVEN_CFG_DIR}/repository` directory as cached (not to download Maven dependencies over and over again).

If you have a good reason to do differently, you'll have to override the `MAVEN_CLI_OPTS` variable as well as the [`cache`](https://docs.gitlab.com/ee/ci/yaml/README.html#cache) policy.

### About `$MAVEN_SETTINGS_FILE`

If a file is found at the `$MAVEN_SETTINGS_FILE` location, the template automatically uses it as a `settings.xml` (using the [`--settings` option](https://maven.apache.org/ref/current/maven-embedder/cli.html) on command line).

Note that with this design you are free to either:

1. inline the `settings.xml` file into your repository source (:warning: make sure not to inline secrets but use the `${env.MY_PASSWORD}` replacement pattern instead and define the `MY_PASSWORD` variable as secret project variable),
2. or define the `settings.xml` content as a [file type project variable](https://docs.gitlab.com/ee/ci/variables/#use-file-type-cicd-variables).

## Jobs

### `mvn-build` job

The Maven template features a job `mvn-build` that performs **build and tests** at once.
This stage is performed in a single job for **optimization** purpose (it saves time) and also
for test jobs dependency reasons (some test jobs such as SONAR analysis have a dependency on test results).

It uses the following variable:

| Name                  | description                              | default value     |
| --------------------- | ---------------------------------------- | ----------------- |
| `MAVEN_BUILD_ARGS`    | Maven arguments for the build & test job | `org.jacoco:jacoco-maven-plugin:prepare-agent verify org.jacoco:jacoco-maven-plugin:report` |

#### About Code Coverage

With its default arguments, the GitLab CI template for Maven forces the use of [JaCoCo Maven Plugin](https://www.eclemma.org/jacoco/trunk/doc/maven.html)
to compute code coverage during unit tests execution.

In addition it makes the necessary to [integrate code coverage stats into your GitLab project](https://docs.gitlab.com/ee/ci/pipelines/settings.html#merge-request-test-coverage-results) (report badge and viewable coverage in merge requests).

If yo want to fix the JaCoCo plugin version or tweak the default configuration, you may have to configure the
[JaCoCo Maven Plugin](https://www.eclemma.org/jacoco/trunk/doc/maven.html) in your `pom.xml`, but be aware of the
following:

* do not declare JaCoCo executions for `prepare-agent` and `report` goals as each would run twice during
  unit tests (not necessarily with the expected configuration). If you really need to do so anyway, you'll have to
  override the `$MAVEN_BUILD_ARGS` variable to remove the explicit invocation to JaCoCo goals.
* make sure the `report` goal computes a CSV report, that is used by the Maven template to compute the global coverage stat.

More info:

* [Maven Surefire Plugin](https://maven.apache.org/surefire/maven-surefire-plugin)
* [`surefire:test` parameters](https://maven.apache.org/surefire/maven-surefire-plugin/test-mojo.html)

### `mvn-sonar` job — SonarQube analysis

This job is **disabled by default** and performs a SonarQube analysis of your code.

The job is bound to the `test` stage and uses the following variables:

| Name                     | description                            | default value     |
| ------------------------ | -------------------------------------- | ----------------- |
| `SONAR_HOST_URL`         | SonarQube server url                   | _none_ (disabled) |
| :lock: `SONAR_TOKEN`     | SonarQube authentication [token](https://docs.sonarqube.org/latest/user-guide/user-token/) (depends on your authentication method) | _none_ |
| :lock: `SONAR_LOGIN`     | SonarQube login (depends on your authentication method)                | _none_ |
| :lock: `SONAR_PASSWORD`  | SonarQube password (depends on your authentication method)             | _none_ |
| `SONAR_BASE_ARGS`        | SonarQube [analysis arguments](https://docs.sonarqube.org/latest/analysis/analysis-parameters/) | `sonar:sonar -Dsonar.links.homepage=${CI_PROJECT_URL} -Dsonar.links.ci=${CI_PROJECT_URL}/-/pipelines -Dsonar.links.issue=${CI_PROJECT_URL}/-/issues` |
| `SONAR_QUALITY_GATE_ENABLED` | Set to `true` to enable SonarQube [Quality Gate](https://docs.sonarqube.org/latest/user-guide/quality-gates/) verification.<br/>_Uses `sonar.qualitygate.wait` parameter ([see doc](https://docs.sonarqube.org/latest/analysis/ci-integration-overview/#header-1))._ | _none_ (disabled) |

#### Automatic Branch Analysis & Merge Request Analysis

This template relies on SonarScanner's [GitLab integration](https://docs.sonarqube.org/latest/analysis/gitlab-integration), that is able to auto-detect whether to launch Branch Analysis or Merge Request Analysis
from GitLab's environment variables.

:warning: This feature also depends on your SonarQube server version and license.
If using Community Edition, you'll have to install the [sonarqube-community-branch-plugin](https://github.com/mc1arke/sonarqube-community-branch-plugin) to enable automatic Branch & Merge Request analysis (only works from SonarQube version 8).

:warning: Merge Request Analysis only works if you're running [Merge Request pipeline](https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines) strategy (default).

#### Disable the job

> :information_source: See [Usage](https://to-be-continuous.gitlab.io/doc/usage/#example-3-disable-go-mod-outdated-job) 
> for more information about disabling any job that MAY not be required in a project or group.

### `mvn-dependency-check` job

This job enables a manual [Dependency-Check](https://jeremylong.github.io/DependencyCheck/dependency-check-maven/configuration.html)
analysis.

It is bound to the `test` stage, and uses the following variables:

| Name                  | description                            | default value     |
| --------------------- | -------------------------------------- | ----------------- |
| `MAVEN_DEPENDENCY_CHECK_DISABLED` | Set to `true` to disable this job | _none_ |
| `MAVEN_DEPENDENCY_CHECK_ARGS` | Maven arguments for Dependency Check job | `org.owasp:dependency-check-maven:check -DretireJsAnalyzerEnabled=false -DassemblyAnalyzerEnabled=false` |


A Dependency Check is a quite long operation and therefore the job is configured to be ran __manually__ by default.

However, if you want to enable an automatic Dependency-Check scan, you will have to override the `rules` keyword for the `mvn-dependency-check` job.

Furthermore, if you want to upload Dependency-Check reports to SonarQube, you have to:

* Move `mvn-dependency-check` to the `build` stage
* Add `-Dformats=html,json,xml` to `MAVEN_DEPENDENCY_CHECK_ARGS` to output reports
    * HTML report to read the report on SonarQube UI
    * JSON report to create SonarQube issues from the report
    * XML report to import into DefectDojo security dashboard
* Add `-Dsonar.dependencyCheck.htmlReportPath` and `-Dsonar.dependencyCheck.jsonReportPath` with the paths of the generated html and json reports to SonarQube arguments.

More info:

* [Maven Dependency-Check Plugin](https://jeremylong.github.io/DependencyCheck/dependency-check-maven/configuration.html)

### `mvn-no-snapshot-deps` job

This job checks if the project has release-only dependencies, i.e., no `_*-SNAPSHOT_` versions, using the [Maven Enforcer](https://maven.apache.org/enforcer/enforcer-rules/requireReleaseDeps.html) plugin.

Failure is allowed in feature branches.

It is bound to the `test` stage, and uses the following variables:

| Name                  | description                            | default value     |
| --------------------- | -------------------------------------- | ----------------- |
| `MVN_FORBID_SNAPSHOT_DEPENDENCIES_DISABLED` | Set to `true` to disable this job | _none_ |

### `mvn-sbom` job

This job generates a [SBOM](https://cyclonedx.org/) file listing all dependencies using [cyclonedx-maven-plugin](https://github.com/CycloneDX/cyclonedx-maven-plugin).

It is bound to the `test` stage, and uses the following variables:

| Name                  | description                            | default value     |
| --------------------- | -------------------------------------- | ----------------- |
| `MAVEN_SBOM_DISABLED` | Set to `true` to disable this job | _none_ |
| `MAVEN_SBOM_GEN_ARGS` | Maven command used for SBOM analysis | `org.cyclonedx:cyclonedx-maven-plugin:makeAggregateBom` |

### `mvn-release` &amp; `mvn-deploy-*` jobs

These jobs are **disabled by default** and - when enabled - respectively perform the following:

1. a [Maven release:prepare](https://maven.apache.org/maven-release/maven-release-plugin/usage/prepare-release.html) of your current branch
    * only triggers the first part of the release (version changes in `pom.xml`, Git commits and version tag)
    * provides a default integration with `semantic-release` ([see below](#semantic-release-integration))
    * the second part of the release (package and publish to a Maven registry) is performed by the `mvn-deploy` job below
2. a [Maven deploy](https://maven.apache.org/plugins/maven-deploy-plugin/) of your Java packages (jar, war, etc.) to any Maven-compliant registry
    * `mvn-deploy-release` publishes the **stable** version on the tag pipeline triggered by a `mvn-release`,
    * `mvn-deploy-snapshot` publishes the **snapshot** version on other branches.

They are bound to the `publish` stage, and use the following variables:

| Name                                | description                                                  | default value     |
| ----------------------------------- | ------------------------------------------------------------ | ----------------- |
| `MAVEN_DEPLOY_ENABLED`              | Set to `true` to enable release and publish jobs             | _none_ (disabled) |
| `MAVEN_DEPLOY_FROM_UNPROTECTED_DISABLED`| Set to `true` to limit snapshot publication to protected branches | _none_ (disabled) |
| `MAVEN_DEPLOY_ARGS`                 | Maven arguments for the `mvn-deploy` job                     | `deploy -Dmaven.test.skip=true` |
| `MAVEN_RELEASE_ARGS`                | Maven arguments for the `mvn-release` job                    | `release:prepare -DtagNameFormat=@{project.version} -Darguments=-Dmaven.test.skip=true` |
| `MAVEN_RELEASE_VERSION`             | Explicit version to use when triggering a release | _none_ (uses the current snapshot version from `pom.xml`) |
| `MAVEN_RELEASE_SCM_COMMENT_PREFIX`  | Maven release plugin [scmCommentPrefix](https://maven.apache.org/maven-release/maven-release-plugin/prepare-mojo.html#scmCommentPrefix) parameter | `chore(maven-release): ` |
| `MAVEN_RELEASE_SCM_RELEASE_COMMENT` | Maven release plugin [scmReleaseCommitComment](https://maven.apache.org/maven-release/maven-release-plugin/prepare-mojo.html#scmReleaseCommitComment) parameter (since Maven `3.0.0-M1`) | _none_ (Maven default) |
| `MAVEN_RELEASE_SCM_DEV_COMMENT`     | Maven release plugin [scmDevelopmentCommitComment](https://maven.apache.org/maven-release/maven-release-plugin/prepare-mojo.html#scmDevelopmentCommitComment) parameter (since Maven `3.0.0-M1`) | _none_ (Maven default) |
| `MVN_SEMREL_RELEASE_DISABLED`       | Set to `true` to disable [semantic-release integration](#semantic-release-integration)   | _none_ (disabled) |

More info:

* [Maven Deploy Plugin](https://maven.apache.org/plugins/maven-deploy-plugin/)
* [Maven Release Plugin](http://maven.apache.org/maven-release/maven-release-plugin/index.html)

#### `semantic-release` integration

If you activate the [`semantic-release-info` job from the `semantic-release` template](https://gitlab.com/to-be-continuous/semantic-release/#semantic-release-info-job), the `mvn-release` job will rely on the generated next version info.

* the release will only be performed if a semantic release is present
* the version is passed to the Maven Release plugin as the release version argument adding `-DreleaseVersion=${SEMREL_INFO_NEXT_VERSION}` to the `MAVEN_RELEASE_ARGS` value

:warning: Both the Maven Release plugin and semantic-release template use a dedicated tag format that need to be set accordingly.
By default, the Maven Release plugin uses `${artifactId}-${version}` and semantic-release uses `${version}`
For exemple you can modify the semantic-release tag format with the `SEMREL_TAG_FORMAT` variable (see [semantic-release template variables](https://gitlab.com/to-be-continuous/semantic-release/#variables)).

:information_source: semantic-release determines the next release version from the existing tags in the Git repository. As the default semantic-release tag format (`${version}`) is _not_ the Maven default, if leaving defaults, semantic-release will always determine the next version to release as `1.0.0`, trying to keep overwriting the same version.
In addition to the functional problem, this might result in a release failure as soon as trying to release version `1.0.0` for the second time (as Maven repos configured as "release" repos will not permit overwriting).
Simply set `SEMREL_TAG_FORMAT` as shown below to have the semantic-release tag format match the maven release plugin one.

```yml
variables:
  # double dollar to prevent evaluation (escape char)
  SEMREL_TAG_FORMAT: "myArtifactId-$${version}"
```

Or you can [override the maven release plugin tag format](https://maven.apache.org/maven-release/maven-release-plugin/usage/prepare-release.html#overriding-the-default-tag-name-format).

Note: It is the `mvn-release` job that will perform the release and so you only need the `semantic-release-info` job. Set the `SEMREL_RELEASE_DISABLED` variable as shown below.

```yml
variables:
  SEMREL_RELEASE_DISABLED: "true"
```

Finally, the semantic-release integration can be disabled with the `MVN_SEMREL_RELEASE_DISABLED` variable.

#### Maven repository authentication

Your Maven repository may require authentication credentials to publish artifacts.

You may handle them in the following ways:

1. define all required credentials as :lock: [project variables](https://docs.gitlab.com/ee/ci/variables/#add-a-cicd-variable-to-a-project),
2. make sure your `pom.xml` (or ancestor) [declares your `<repository>` and `<snapshotRepository>` with server **id**s in a `<distributionManagement>` section](https://maven.apache.org/pom.html#repository),
3. in your `${MAVEN_CFG_DIR}/settings.xml` file, [define the repository servers credentials in the `<servers>` section](https://maven.apache.org/settings.html#Servers) using the `${env.VARIABLE}` pattern—will be automatically evaluated and replaced by Maven.

**Example 1** — using the [GitLab Maven Repository](https://docs.gitlab.com/ee/user/packages/maven_repository/)

`pom.xml`:

```xml
<!-- ... -->
<distributionManagement>
  <snapshotRepository>
      <id>gitlab-maven</id>
      <url>${env.CI_API_V4_URL}/projects/${env.CI_PROJECT_ID}/packages/maven</url>
  </snapshotRepository>
  <repository>
      <id>gitlab-maven</id>
      <url>${env.CI_API_V4_URL}/projects/${env.CI_PROJECT_ID}/packages/maven</url>
  </repository>
</distributionManagement>
<!-- ... -->
```

`${MAVEN_SETTINGS_FILE}`:

```xml
<settings>
  <servers>
    <!-- required when using GitLab's package registry to deploy -->
    <!-- see: https://docs.gitlab.com/ee/user/packages/maven_repository/index.html#use-the-gitlab-endpoint-for-maven-packages -->
    <server>
        <id>gitlab-maven</id>
        <configuration>
            <httpHeaders>
                <property>
                    <name>Job-Token</name>
                    <value>${env.CI_JOB_TOKEN}</value>
                </property>
            </httpHeaders>
        </configuration>
    </server>
  </servers>
</settings>
```

**Example 2** — using an Artifactory repository with same credentials for snapshot &amp; release

`pom.xml`:

```xml
<!--... -->
<distributionManagement>
  <snapshotRepository>
    <id>artifactory</id>
    <url>https://artifactory.acme.host/artifactory/maven-snapshot-repo</url>
  </snapshotRepository>
  <repository>
    <id>artifactory</id>
    <url>https://artifactory.acme.host/artifactory/maven-release-repo</url>
  </repository>
</distributionManagement>
<!--...-->
```

`${MAVEN_CFG_DIR}/settings.xml`:

```xml
<settings>
  <servers>
    <server>
      <id>artifactory</id>
      <username>${env.ARTIFACTORY_USER}</username>
      <password>${env.ARTIFACTORY_PASSWORD}</password>
    </server>
  </servers>
  <mirrors>
    <mirror>
      <id>artifactory.mirror</id>
      <mirrorOf>central</mirrorOf>
      <name>Artifactory Maven 2 central repository mirror</name>
      <url>https://artifactory.acme.host/artifactory/maven-virtual-repo/</url>
    </mirror>
  </mirrors>
</settings>
```

#### SCM authentication

A Maven release involves some Git push operations.

You can either use an `ssh` key or an authenticated and authorized Git user.

##### Using an SSH key

We recommend you to use a [project deploy key](https://docs.gitlab.com/ee/user/project/deploy_keys/#project-deploy-keys) with write access to your project.

The key should not have a passphrase (see [how to generate a new SSH key pair](https://docs.gitlab.com/ce/ssh/README.html#generating-a-new-ssh-key-pair)).

Specify :lock: `$GIT_PRIVATE_KEY` as protected project variable with the private part of the deploy key.

```PEM
-----BEGIN 0PENSSH PRIVATE KEY-----
blablabla
-----END OPENSSH PRIVATE KEY-----
```

The template handles both classic variable and file variable.

:warning: The `scm` connections in your `pom.xml` should use the `ssh` protocol

```xml
  <scm>
    <connection>scm:git:git@gitlab-host/path/to/my/project.git</connection>
    <developerConnection>scm:git:git@gitlab-host/path/to/my/project.git</developerConnection>
    ...
  </scm>
```

##### Using Git user authentication

Simply specify :lock: `$GIT_USERNAME` and :lock: `$GIT_PASSWORD` as protected project variables and they will be dynamically 
evaluated and appended to the Maven release arguments.

Note that the password should be an access token with `write_repository` scope and `Maintainer` role.

:warning: The `scm` connections in your `pom.xml` should use the `https` protocol

```xml
  <scm>
    <connection>scm:git:https://gitlab-host/path/to/my/project.git</connection>
    <developerConnection>scm:git:https://gitlab-host/path/to/my/project.git</developerConnection>
    ...
  </scm>
```