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