# 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: - project: 'to-be-continuous/maven' ref: '2.3.0' file: '/templates/gitlab-ci-maven.yml' ``` ## Global configuration The Maven template uses some global configuration used 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** | `maven:latest` | | `MAVEN_PROJECT_DIR` | Maven projet root directory | `.` | | `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.log.org.apache.maven.cli.transfer.Slf4jMavenTransferListener=WARN -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 | `--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 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). 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 otherwise then would be ran 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 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) ### 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_URL` | SonarQube server url | _none_ (disabled) | | :lock: `SONAR_AUTH_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.host.url=${SONAR_URL} -Dsonar.links.homepage=${CI_PROJECT_URL} -Dsonar.links.ci=${CI_PROJECT_URL}/-/pipelines -Dsonar.links.issue=${CI_PROJECT_URL}/-/issues` | | :lock: `SONAR_GITLAB_TOKEN` | GitLab [access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html) with `api` scope. When set, activates the [Sonar GitLab plugin](https://github.com/gabrie-allaigre/sonar-gitlab-plugin/#plugins-properties) integration. | _none_ | | `SONAR_BRANCH_ANALYSIS_DISABLED` | Set to `true` to disable automatic [Pull Request Analysis](https://docs.sonarqube.org/latest/analysis/pull-request/) and [Branch Analysis](https://docs.sonarqube.org/latest/branches/overview/) | _none_ (enabled) | | `SONAR_GITLAB_ARGS` | Extra arguments to use with [Sonar GitLab plugin](https://github.com/gabrie-allaigre/sonar-gitlab-plugin/#plugins-properties) | `-Dsonar.gitlab.url=${CI_SERVER_URL} -Dsonar.gitlab.user_token=${SONAR_GITLAB_TOKEN} -Dsonar.gitlab.project_id=${CI_PROJECT_ID} -Dsonar.gitlab.commit_sha=${CI_COMMIT_SHA} -Dsonar.gitlab.ref_name=${CI_COMMIT_REF_NAME}` | | `SONAR_AUTO_ON_DEV_DISABLED` | When set to `true`, SonarQube analysis becomes **manual** on development branches (automatic otherwise) | _none_ | | `SONAR_QUALITY_GATE_ENABLED` | Set to `true` to enables check of SonarQube [Quality Gate](https://docs.sonarqube.org/latest/user-guide/quality-gates/) | _none_ (disabled) | #### Automatic Branch Analysis & Pull Request Analysis By default, this template tries to auto-detect and use [Pull Request Analysis](https://docs.sonarqube.org/latest/analysis/pull-request/) or [Branch Analysis](https://docs.sonarqube.org/latest/branches/overview/) (depending on the context). Those is a great SonarQube features but it assumes one of the following conditions: * you are using a [Developer Edition](https://www.sonarqube.org/developer-edition/) version, * or you are using Community Edition with an opensource plugin emulating those features, such as [sonarqube-community-branch-plugin](https://github.com/mc1arke/sonarqube-community-branch-plugin). If you're not in one of those cases, then you shall disable this feature by setting `SONAR_BRANCH_ANALYSIS_DISABLED`. If you leave the feature enabled, if `SONAR_AUTH_TOKEN` is provided, the template will try to autodetect (using GitLab APIs) an opened merge request matching the current branch: * If one is found, a SonarQube [Pull Request Analysis](https://docs.sonarqube.org/latest/analysis/pull-request/) will be made. * Otherwise, a simple [Branch Analysis](https://docs.sonarqube.org/latest/branches/overview/) is performed on the current branch. #### About Sonar GitLab plugin The [Sonar GitLab plugin](https://github.com/gabrie-allaigre/sonar-gitlab-plugin) uses the GitLab APIs to inline comments into your commits directly in GitLab for each new anomaly. As explained above, this template automatically enables the Sonar GitLab plugin if `SONAR_GITLAB_TOKEN` is set. It will then simply append the `SONAR_GITLAB_ARGS` (overridable) to the SonarQube analysis arguments. Comments added to GitLab will appear as owned by the user associated to the GitLab [access token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html). ### `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 * 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 your project has release-only dependencies (no _snapshot_), 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-snapshot` & `mvn-release` jobs Those jobs are **disabled by default** and perform respectively: * a [Maven deploy](https://maven.apache.org/plugins/maven-deploy-plugin/) of your Java packages (jar, war or else), * 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` | | `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]` | | `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 next semantic release is present * the version is passed to the maven release plugin as release version argument adding `-DreleaseVersion=${SEMREL_INFO_NEXT_VERSION}` to the `MAVEN_RELEASE_ARGS` value :warning: Both maven release plugin and semantic-release use a dedicated tag format that need to be set accordingly. By default maven release plugin uses `${artifactId}-${version}` and semantic-release uses `s${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)). ```yml variables: 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: You can disable the `semantic-release` job (as it's the `mvn-release` job that will perform the release and so we only need the `semantic-release-info` job) with the `SEMREL_RELEASE_DISABLED` variable. ```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 shall handle them in the following way: 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/)): `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 --> <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 a ssh key or an authenticated and authorized Git user. ##### Using a 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 OPENSSH PRIVATE KEY----- blablabla -----END OPENSSH PRIVATE KEY----- ``` The template handle 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 : they will be dynamically 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 ```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> ```