Skip to content
Snippets Groups Projects
README.md 17 KiB
Newer Older
Pierre Smeyers's avatar
Pierre Smeyers committed
# GitLab CI template for Maven

This project implements a generic GitLab CI template for [Maven](https://maven.apache.org/).

It provides several features, usable in different modes (by configuration).

## Usage

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

```yaml
include:
Pierre Smeyers's avatar
Pierre Smeyers committed
  - project: 'to-be-continuous/maven'
    ref: '3.1.2'
Pierre Smeyers's avatar
Pierre Smeyers committed
    file: '/templates/gitlab-ci-maven.yml'
```

## Global configuration

The Maven template uses some global configuration throughout all jobs.
Pierre Smeyers's avatar
Pierre Smeyers committed

| Name                  | description                            | default value     |
| --------------------- | -------------------------------------- | ----------------- |
| `MAVEN_IMAGE`         | The Docker image used to run Maven <br/>:warning: **set the version required by your project** | `maven:latest` |
| `MAVEN_PROJECT_DIR`   | Maven projet root directory | `.` |
Pierre Smeyers's avatar
Pierre Smeyers committed
| `MAVEN_CFG_DIR`       | The Maven configuration directory      | `.m2`             |
| `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 for two (2) purposes:

* in case a Maven settings file (`settings.xml`) is found, the template automatically uses it (using the `-s` option on command line),
* the cache policy declares the `${MAVEN_CFG_DIR}/repository` directory as cached (not to download Maven dependencies over and over again).
Pierre Smeyers's avatar
Pierre Smeyers committed

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.

## 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](https://docs.gitlab.com/ee/user/project/pipelines/settings.html#test-coverage-parsing)
to integrate code coverage stats into your GitLab project: [report badge](https://docs.gitlab.com/ee/user/project/pipelines/settings.html#test-coverage-report-badge)
and viewable 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
Pierre Smeyers's avatar
Pierre Smeyers committed
  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.
Pierre Smeyers's avatar
Pierre Smeyers committed
* 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)

### SonarQube analysis job

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

It 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_ |
Pierre Smeyers's avatar
Pierre Smeyers committed
| :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).
Pierre Smeyers's avatar
Pierre Smeyers committed

### `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_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
Pierre Smeyers's avatar
Pierre Smeyers committed
* 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-forbid-snapshot-dependencies` 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_ |
Pierre Smeyers's avatar
Pierre Smeyers committed
### `mvn-snapshot` &amp; `mvn-release` jobs

These jobs are **disabled by default** and perform, respectively, the following:
* a [Maven deploy](https://maven.apache.org/plugins/maven-deploy-plugin/) of your Java packages (jar, war, etc.),
Pierre Smeyers's avatar
Pierre Smeyers committed
* a [Maven release](http://maven.apache.org/maven-release/maven-release-plugin/index.html) of your current branch.

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

| Name                                | description                                                  | default value     |
| ----------------------------------- | ------------------------------------------------------------ | ----------------- |
| `MAVEN_DEPLOY_ENABLED`              | Set to `true` to enable a 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 Snapshot job                         | `deploy -Dmaven.test.skip=true` |
| `MAVEN_RELEASE_ARGS`                | Maven arguments for the Release job                          | `release:prepare release:perform -Darguments=-Dmaven.test.skip=true` |
Pierre Smeyers's avatar
Pierre Smeyers committed
| `MAVEN_RELEASE_SCM_COMMENT_PREFIX`  | Maven release plugin [scmCommentPrefix](https://maven.apache.org/maven-release/maven-release-plugin/prepare-mojo.html#scmCommentPrefix) parameter  | `[ci skip][maven-release-plugin]` |
Pierre Smeyers's avatar
Pierre Smeyers committed
| `MVN_SEMREL_RELEASE_DISABLED`  | Set to `true` to disable [semantic-release integration](#semantic-release-integration)   | _none_ (disabled) |
Pierre Smeyers's avatar
Pierre Smeyers committed

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

Pierre Smeyers's avatar
Pierre Smeyers committed
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}`
Pierre Smeyers's avatar
Pierre Smeyers committed
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.

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

Or you can [override the maven release plugin tag format](http://maven.apache.org/maven-release/maven-release-plugin/examples/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"
```

Pierre Smeyers's avatar
Pierre Smeyers committed
Finally, the semantic-release integration can be disabled with the `MVN_SEMREL_RELEASE_DISABLED` variable.
Pierre Smeyers's avatar
Pierre Smeyers committed
#### Maven repository authentication

Your Maven repository may require authentication credentials to publish artifacts.

You may handle them in the following ways:
Pierre Smeyers's avatar
Pierre Smeyers committed

1. define all required credentials as :lock: [project variables](https://docs.gitlab.com/ee/ci/variables/#create-a-custom-variable-in-the-ui),
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/)
Pierre Smeyers's avatar
Pierre Smeyers committed

`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_CFG_DIR}/settings.xml`:

```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 -->
Pierre Smeyers's avatar
Pierre Smeyers committed
    <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
Pierre Smeyers's avatar
Pierre Smeyers committed

`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
Pierre Smeyers's avatar
Pierre Smeyers committed

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
Pierre Smeyers's avatar
Pierre Smeyers committed
-----BEGIN 0PENSSH PRIVATE KEY-----
Pierre Smeyers's avatar
Pierre Smeyers committed
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
Pierre Smeyers's avatar
Pierre Smeyers committed

```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 
Pierre Smeyers's avatar
Pierre Smeyers committed
evaluated and appended to the Maven release arguments.

Note that the password should be an access token with `read_repository` and `write_repository` scopes.

:warning: The `scm` connections in your `pom.xml` should use the `https` protocol
Pierre Smeyers's avatar
Pierre Smeyers committed

```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>
```