Setting up GitLab CI for uploading a Java project to Maven Central

This article is aimed at Java developers who need to quickly publish their products in Sonatype and/or Maven Central repositories using GitLab. In this article, I will discuss the setup of gitlab-runner, gitlab-ci, and maven-plugin to achieve this goal.

Prerequisites:

  • Secure storage of mvn and GPG keys.
  • Secure execution of public CI tasks.
  • Uploading artifacts (release/snapshot) to public repositories.
  • Automatic verification of release versions for publishing to Maven Central.
  • General solution for uploading artifacts to the repository for multiple projects.
  • Simplicity and ease of use.

Content

General information

  • A detailed description of the mechanism for publishing artifacts to Maven Central via the Sonatype OSS Repository Hosting Service has already been provided in this article by the user Googolplex, so I will reference this article in the necessary places.
  • First, register at Sonatype JIRA and create a ticket to open a repository (for more details, see the section Create a ticket on Sonatype JIRA). After opening the repository, the login/password pair from JIRA (thus, the Sonatype account) will be used to upload artifacts to Sonatype Nexus.
  • Then the process of generating a GPG key is described rather dryly. For more details, refer to the section Setting up GnuPG for signing artifacts
  • If you are using the Linux console to generate a GPG key (gnupg/gnupg2), you need to install rng-tools to generate entropy. Otherwise, key generation may take a very long time.
  • Storage services for public GPG keys

To the content

Setting up the deploy project in GitLab

  • First, you need to create and configure a project where the pipeline for deploying artifacts will be stored. I named my project simply and straightforwardly — deploy
  • After creating the repository, it is necessary to restrict access to modify the repository.
    Go to the project -> Settings -> Repository -> Protected Branches. Remove all rules and add a single rule with Wildcard * allowing push and merge only for users with the Maintainers role. This rule will apply to all users in both this project and the group to which this project belongs.
    Setting up GitLab CI for uploading a Java project to Maven Central
  • If there are several maintainers, the best solution would be to restrict access to the project altogether.
    Go to the project -> Settings -> General -> Visibility, project features, permissions and set Project visibility to Private.
    I have my project publicly accessible since I use my own GitLab Runner and only I have access to modify the repository. Besides, it's not in my interest to expose private information in public pipeline logs.
  • Tightening the rules for modifying the repository
    Go to the project -> Settings -> Repository -> Push Rules and enable the Committer restriction flag and Check whether author is a GitLab user. I also recommend configuring commit signatures, and enable the Reject unsigned commits flag.
  • Next, configure a trigger to start tasks
    Go to the project -> Settings -> CI / CD -> Pipeline triggers and create a new trigger-token
    This token can be added to the common configuration of variables for the project group.
    Go to the group -> Settings -> CI / CD -> Variables and add the variable DEPLOY_TOKEN with the trigger-token value.

To the content

GitLab Runner

This section describes the configuration for deploying tasks with the use of a specific (Specific) and a shared (Shared) runner.

Specific Runner

I use my own runners, as this is more convenient, faster, and cheaper.
For the runner, I recommend a Linux VDS with 1 CPU, 2 GB RAM, and 20 GB HDD. The cost is about ~3000₽ per year.

My runner

For the runner, I chose a VDS with 4 CPU, 4 GB RAM, and 50 GB SSD. It cost around ~11000₽ and I have never regretted it.
I have a total of 7 machines. 5 on Aruba and 2 on Ihor.

So, we have the runner. Now we will set it up.
Log into the machine via SSH and install java, git, maven, gnupg2.

To the content

Install GitLab runner

  • Create a new group runner
    sudo groupadd runner
  • Create a directory for the maven cache and assign group permissions runner
    This step can be skipped if you do not plan to run multiple runners on the same machine.
    mkdir -p /usr/cache/.m2/repository
    chown -R :runner /usr/cache
    chmod -R 770 /usr/cache
  • Create a user gitlab-deployer and add it to the group runner
    useradd -m -d /home/gitlab-deployer gitlab-deployer
    usermod -a -G runner gitlab-deployer
  • Add the following line to the file /etc/ssh/sshd_config .
    AllowUsers root@* gitlab-deployer@127.0.0.1
  • Restarting sshd.
    systemctl restart sshd
  • Setting password for user gitlab-deployer (can be simple as there is a restriction for localhost)
    passwd gitlab-deployer
  • Installing GitLab Runner (Linux x86-64)
    sudo wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
    sudo chmod +x /usr/local/bin/gitlab-runner
    ln -s /usr/local/bin/gitlab-runner /etc/alternatives/gitlab-runner
    ln -s /etc/alternatives/gitlab-runner /usr/bin/gitlab-runner
  • Go to gitlab.com -> deploy-project -> Settings -> CI/CD -> Runners -> Specific Runners and copy the registration token

Screenshot

Setting up GitLab CI for uploading a Java project to Maven Central

  • Registering runner
    gitlab-runner register --config /etc/gitlab-runner/gitlab-deployer-config.toml

Process

Runtime platform arch=amd64 os=linux pid=17594 revision=3001a600 version=11.10.0
Running in system-mode.
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/):
https://gitlab.com/
Please enter the gitlab-ci token for this runner:
REGISTRATION_TOKEN
Please enter the gitlab-ci description for this runner:
[ih1174328.vds.myihor.ru]: Deploy Runner
Please enter the gitlab-ci tags for this runner (comma separated):
deploy
Registering runner... succeeded                     runner=ZvKdjJhx
Please enter the executor: docker-ssh, parallels, virtualbox, docker-ssh+machine, kubernetes, docker, ssh, docker+machine, shell:
shell
Runner registered successfully. Feel free to start it, but if it's running already the config should be automatically reloaded!
  • Checking that the runner is registered. Go to gitlab.com -> deploy-project -> Settings -> CI/CD -> Runners -> Specific Runners -> Runners activated for this project

Screenshot

Setting up GitLab CI for uploading a Java project to Maven Central

  • Add separate service /etc/systemd/system/gitlab-deployer.service
    [Unit]
    Description=GitLab Deploy Runner
    After=syslog.target network.target
    ConditionFileIsExecutable=/usr/local/bin/gitlab-runner
    [Service]
    StartLimitInterval=5
    StartLimitBurst=10
    ExecStart=/usr/local/bin/gitlab-runner "run" "--working-directory" "/home/gitlab-deployer" "--config" "/etc/gitlab-runner/gitlab-deployer-config.toml" "--service" "gitlab-deployer" "--syslog" "--user" "gitlab-deployer"
    Restart=always
    RestartSec=120
    [Install]
    WantedBy=multi-user.target
  • Starting the service.
    systemctl enable gitlab-deployer.service
    systemctl start gitlab-deployer.service
    systemctl status gitlab-deployer.service
  • Checking that the runner is started.

Example

Setting up GitLab CI for uploading a Java project to Maven Central

To the content

Generating GPG keys

  • From this same machine, log in via ssh as user gitlab-deployer (this is important for generating the GPG key)
    ssh gitlab-deployer@127.0.0.1
  • Generating the key by answering the questions. I used my own name and email.
    Make sure to specify a password for the key. This key will be used to sign the artifacts.
    gpg --gen-key 
  • Checking
    gpg --list-keys -a
    /home/gitlab-deployer/.gnupg/pubring.gpg
    ----------------------------------------
    pub   4096R/00000000 2019-04-19
    uid                  Petruha Petrov 
    sub   4096R/11111111 2019-04-19
  • Uploading our public key to the key server
    gpg --keyserver keys.gnupg.net --send-key 00000000
    gpg: sending key 00000000 to hkp server keys.gnupg.net

To the content

Setting up Maven

  • Logging in as user gitlab-deployer
    su gitlab-deployer 
  • Creating a maven directory repository and linking with the cache (be careful)
    This step can be skipped if you do not plan to run multiple runners on one machine.
    mkdir -p ~/.m2/repository
    ln -s /usr/cache/.m2/repository /home/gitlab-deployer/.m2/repository
  • Creating master key
    mvn --encrypt-master-password password
    {hnkle5BJ9HUHUMP+CXfGBl8dScfFci/mpsur/73tR2I=}
  • Creating file ~/.m2/settings-security.xml
    {hnkle5BJ9HUHUMP+CXfGBl8dScfFci/mpsur/73tR2I=}
  • Encrypting password for the Sonatype account
    mvn --encrypt-password SONATYPE_PASSWORD
    {98Wv5+u+Tn0HX2z5G/kR4R8Z0WBgcDBgi7d12S/un+SCU7uxzaZGGmJ8Cu9pAZ2J}
  • Creating file ~/.m2/settings.xml
    env
            
                true
            
            
                GPG_SECRET_KEY_PASSPHRASE
            
        
    
    
        
            sonatype
            SONATYPE_USERNAME
            {98Wv5+u+Tn0HX2z5G/kR4R8Z0WBgcDBgi7d12S/un+SCU7uxzaZGGmJ8Cu9pAZ2J}

where,
GPG_SECRET_KEY_PASSPHRASE — password for the GPG key
SONATYPE_USERNAME — login for the Sonatype account

This completes the runner setup, and we can move on to the section GitLab CI

To the content

Shared Runner

Generating GPG keys

  • First, you need to create a GPG key. To do this, install gnupg.
    yum install -y gnupg
  • Generate the key by answering the questions. I used my own name and email. Be sure to specify a password for the key.
    gpg --gen-key 
  • Display information about the key
    gpg --list-keys -a
    pub   rsa3072 2019-04-24 [SC] [expires: 2021-04-23]
      2D0D1706366FC4AEF79669E24D09C55BBA3FD728
    uid           [ultimate] tttemp 
    sub   rsa3072 2019-04-24 [E] [expires: none]
  • Uploading our public key to the key server
    gpg --keyserver keys.gnupg.net --send-key 2D0D1706366FC4AEF79669E24D09C55BBA3FD728
    gpg: sending key 2D0D1706366FC4AEF79669E24D09C55BBA3FD728 to hkp server keys.gnupg.net
  • Get the private key
    gpg --export-secret-keys --armor 2D0D1706366FC4AEF79669E24D09C55BBA3FD728
    -----BEGIN PGP PRIVATE KEY BLOCK-----
    lQWGBFzAqp8BDADN41CPwJ/gQwiKEbyA902DKw/WSB1AvZQvV/ZFV77xGeG4K7k5
    ...
    =2Wd2
    -----END PGP PRIVATE KEY BLOCK-----
  • Go to project settings -> Settings -> CI / CD -> Variables and save the private key in the variable GPG_SECRET_KEY
    Setting up GitLab CI for uploading a Java project to Maven Central

To the content

Setting up Maven

  • Creating master key
    mvn --encrypt-master-password password
    {hnkle5BJ9HUHUMP+CXfGBl8dScfFci/mpsur/73tR2I=}
  • Go to project settings -> Settings -> CI / CD -> Variables and save in the variable SETTINGS_SECURITY_XML the following lines:
    {hnkle5BJ9HUHUMP+CXfGBl8dScfFci/mpsur/73tR2I=}
  • Encrypting password for the Sonatype account
    mvn --encrypt-password SONATYPE_PASSWORD
    {98Wv5+u+Tn0HX2z5G/kR4R8Z0WBgcDBgi7d12S/un+SCU7uxzaZGGmJ8Cu9pAZ2J}
  • Go to project settings -> Settings -> CI / CD -> Variables and save in the variable SETTINGS_XML the following lines:
    env
            
                true
            
            
                GPG_SECRET_KEY_PASSPHRASE
            
        
    
    
        
            sonatype
            sonatype_username
            {98Wv5+u+Tn0HX2z5G/kR4R8Z0WBgcDBgi7d12S/un+SCU7uxzaZGGmJ8Cu9pAZ2J}

where,
GPG_SECRET_KEY_PASSPHRASE — password for the GPG key
SONATYPE_USERNAME — login for the Sonatype account

To the content

Deploying Docker image

  • Create a simple Dockerfile for running deployment tasks with the required version of Java. Below is an example for alpine.
    FROM java:8u111-jdk-alpine
    RUN apk add gnupg maven git --update-cache 
    --repository http://dl-4.alpinelinux.org/alpine/edge/community/ --allow-untrusted && 
    mkdir ~/.m2/
  • Building a container for your project
    docker build -t registry.gitlab.com/group/deploy .
  • Authenticating and uploading the container to the registry.
    docker login -u USER -p PASSWORD registry.gitlab.com
    docker push registry.gitlab.com/group/deploy

To the content

GitLab CI

Deploy project

Adding the .gitlab-ci.yml file to the root of the deploy project
The script contains two mutually exclusive deployment tasks: Specific Runner or Shared Runner, respectively.

.gitlab-ci.yml

stages:
  - deploy

Specific Runner:
  extends: .java_deploy_template
  # The task will run on your shell runner
  tags:
    - deploy

Shared Runner:
  extends: .java_deploy_template
  # The task will run on a public docker runner
  tags:
    - docker
  # Image from the GitLab Runner section -> Shared Runner -> Docker
  image: registry.gitlab.com/group/deploy-project:latest
  before_script:
    # Importing GPG key
    - printf "${GPG_SECRET_KEY}" | gpg --batch --import
    # Saving maven configuration
    - printf "${SETTINGS_SECURITY_XML}" > ~/.m2/settings-security.xml
    - printf "${SETTINGS_XML}" > ~/.m2/settings.xml

.java_deploy_template:
  stage: deploy
  # The task will trigger if the DEPLOY variable is set to java
  only:
    variables:
    - $DEPLOY == "java"
  variables:
    # Disable cloning the current project
    GIT_STRATEGY: none
  script:
    # Allow storing the password in plain text
    - git config --global credential.helper store
    # Save temporary credentials for gitlab-ci-token user
    # The token works for all public projects on gitlab.com and for group projects
    - echo "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com" >> ~/.git-credentials
    # Completely clean the current directory
    - rm -rf .* *
    # Clone the project that will be deployed to Sonatype Nexus
    - git clone ${DEPLOY_CI_REPOSITORY_URL} .
    # Switch to the desired commit
    - git checkout ${DEPLOY_CI_COMMIT_SHA} -f
    # If any pom.xml file contains the autoReleaseAfterClose parameter, abort the build.
    # Otherwise, there's a risk of uploading raw artifacts to maven central
    - >
      for pom in $(find . -name pom.xml); do
        if [[ $(grep -q autoReleaseAfterClose "$pom" && echo $?) == 0 ]]; then
          echo "File $pom contains prohibited setting: ";
          exit 1;
        fi;
      done
    # If the DEPLOY_CI_COMMIT_TAG parameter is empty, force set the SNAPSHOT version
    - >
      if [[ "${DEPLOY_CI_COMMIT_TAG}" != "" ]]; then
        mvn versions:set -DnewVersion=${DEPLOY_CI_COMMIT_TAG}
      else
        VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
        if [[ "${VERSION}" == *-SNAPSHOT ]]; then
          mvn versions:set -DnewVersion=${VERSION}
        else
          mvn versions:set -DnewVersion=${VERSION}-SNAPSHOT
        fi
      fi
    # Running the task to build and deploy artifacts
    - mvn clean deploy -DskipTests=true

To the content

Java project

In Java projects that are intended to be uploaded to public repositories, it is necessary to add two steps for uploading Release and Snapshot versions.

.gitlab-ci.yml

stages:
  - build
  - test
  - verify
  - deploy



Release:
  extends: .trigger_deploy
  # Run the job only on tags.
  only:
    - tags

Snapshot:
  extends: .trigger_deploy
  # Manually trigger the publish task for the SNAPSHOT version
  when: manual
  # Do not run the job if a tag is set.
  except:
    - tags

.trigger_deploy:
  stage: deploy
  variables:
    # Disable cloning of the current project
    GIT_STRATEGY: none
    # URL for the deploy job trigger
    URL: "https://gitlab.com/api/v4/projects//trigger/pipeline"
    # Variables for the deploy job
    POST_DATA: "
      token=${DEPLOY_TOKEN}&
      ref=master&
      variables[DEPLOY]=${DEPLOY}&
      variables[DEPLOY_CI_REPOSITORY_URL]=${CI_REPOSITORY_URL}&
      variables[DEPLOY_CI_PROJECT_NAME]=${CI_PROJECT_NAME}&
      variables[DEPLOY_CI_COMMIT_SHA]=${CI_COMMIT_SHA}&
      variables[DEPLOY_CI_COMMIT_TAG]=${CI_COMMIT_TAG}
      "
  script:
    # Do not use cURL, as with the flags --fail --show-error
    # it does not output the response body if HTTP code 400 or higher 
    - wget --content-on-error -qO- ${URL} --post-data ${POST_DATA}

In this solution, I went a bit further and decided to use a single CI template for Java projects.

More details

I created a separate project gitlab-ci where I placed the CI template for Java projects common.yml.

common.yml

stages:
  - build
  - test
  - verify
  - deploy

variables:
  SONAR_ARGS: "
  -Dsonar.gitlab.commit_sha=${CI_COMMIT_SHA} 
  -Dsonar.gitlab.ref_name=${CI_COMMIT_REF_NAME} 
  "

.build_java_project:
  stage: build
  tags:
    - touchbit-shell
  variables:
    SKIP_TEST: "false"
  script:
    - mvn clean
    - mvn package -DskipTests=${SKIP_TEST}
  artifacts:
    when: always
    expire_in: 30 day
    paths:
      - "*\/target\/reports"

.build_sphinx_doc:
  stage: build
  tags:
    - touchbit-shell
  variables:
    DOCKERFILE: .indirect\/docs\/Dockerfile
  script:
    - docker build --no-cache -t ${CI_PROJECT_NAME}\/doc -f ${DOCKERFILE} .

.junit_module_test_run:
  stage: test
  tags:
    - touchbit-shell
  variables:
    MODULE: ""
  script:
    - cd ${MODULE}
    - mvn test
  artifacts:
    when: always
    expire_in: 30 day
    paths:
      - "*\/target\/reports"

.junit_test_run:
  stage: test
  tags:
    - touchbit-shell
  script:
    - mvn test
  artifacts:
    when: always
    expire_in: 30 day
    paths:
    - "*\/target\/reports"

.sonar_review:
  stage: verify
  tags:
    - touchbit-shell
  dependencies: []
  script:
    - >
      if [ "$CI_BUILD_REF_NAME" == "master" ]; then
        mvn compile sonar:sonar -Dsonar.login=$SONAR_LOGIN $SONAR_ARGS
      else
        mvn compile sonar:sonar -Dsonar.login=$SONAR_LOGIN $SONAR_ARGS -Dsonar.analysis.mode=preview
      fi

.trigger_deploy:
  stage: deploy
  tags:
    - touchbit-shell
  variables:
    URL: "https:\/\/gitlab.com\/api\/v4\/projects\/10345765\/trigger\/pipeline"
    POST_DATA: "
      token=${DEPLOY_TOKEN}&
      ref=master&
      variables[DEPLOY]=${DEPLOY}&
      variables[DEPLOY_CI_REPOSITORY_URL]=${CI_REPOSITORY_URL}&
      variables[DEPLOY_CI_PROJECT_NAME]=${CI_PROJECT_NAME}&
      variables[DEPLOY_CI_COMMIT_SHA]=${CI_COMMIT_SHA}&
      variables[DEPLOY_CI_COMMIT_TAG]=${CI_COMMIT_TAG}
      "
  script:
  - wget --content-on-error -qO- ${URL} --post-data ${POST_DATA}

.trigger_release_deploy:
  extends: .trigger_deploy
  only:
    - tags

.trigger_snapshot_deploy:
  extends: .trigger_deploy
  when: manual
  except:
    - tags

As a result, the .gitlab-ci.yml files in the Java projects are quite compact and succinct.

.gitlab-ci.yml

include: https:\/\/gitlab.com\/TouchBIT\/gitlab-ci\/raw\/master\/common.yml

Shields4J:
  extends: .build_java_project

Sphinx doc:
  extends: .build_sphinx_doc
  variables:
    DOCKERFILE: .docs\/Dockerfile

Sonar review:
  extends: .sonar_review
  dependencies:
    - Shields4J

Release:
  extends: .trigger_release_deploy

Snapshot:
  extends: .trigger_snapshot_deploy

To the content

Configuration of pom.xml

This topic is described in great detail. Googolplex downward API support (simultaneously with this in Configuring Maven for automatic signing and uploading of artifacts to snapshot and staging repositories., therefore I will describe some nuances of using plugins. I will also explain how easily and effortlessly one can use nexus-staging-maven-plugin, if you do not want to or cannot use org.sonatype.oss:oss-parent as the parent for your project.

maven-install-plugin

Installs modules in the local repository.
Very useful for local verification of solutions in other projects, as well as checksums.

org.apache.maven.plugins
  maven-install-plugin
  
    
      install-project
      
      install
      
      
        target/${project.artifactId}-${project.version}.jar
        target/${project.artifactId}-${project.version}-sources.jar
        dependency-reduced-pom.xml
        
        true
        
        true

To the content

maven-javadoc-plugin

Generating javadoc for the project.

org.apache.maven.plugins
  maven-javadoc-plugin
  
    
      
        jar
      
      
      prepare-package
      
        
        true
        true
        
        false

If you have a module that does not contain Java (for example, only resources)
Or you simply do not want to generate javadoc, then consider this option maven-jar-plugin

org.apache.maven.plugins
  maven-jar-plugin
  
    
      empty-javadoc-jar
      generate-resources
      
        jar
      
      
        javadoc
        ${basedir}/javadoc

To the content

maven-gpg-plugin

org.apache.maven.plugins
  maven-gpg-plugin
  
    
      sign-artifacts
      
      
      deploy
      
        sign

To the content

nexus-staging-maven-plugin

Configuration:


  
    
      
      
        org.sonatype.plugins
        nexus-staging-maven-plugin
      
    
    
      
        
          org.sonatype.plugins
          nexus-staging-maven-plugin
          true
          
            sonatype
            https://oss.sonatype.org/
            
            
            true
          
        
        
          org.apache.maven.plugins
          maven-deploy-plugin
          
            
            true
          
        
      
    
  
  
    
      sonatype
      Nexus Snapshot Repository
      https://oss.sonatype.org/content/repositories/snapshots/
    
    
      sonatype
      Nexus Release Repository
      https://oss.sonatype.org/service/local/staging/deploy/maven2/

If you have a multi-module project and do not need to upload a specific module to the repository, you need to add nexus-staging-maven-plugin with the flag skipNexusStagingDeployMojo

org.sonatype.plugins
      nexus-staging-maven-plugin
      
        true

After uploading, snapshot/release versions are available in the staging repository

SonatypeNexus
    https://oss.sonatype.org/content/groups/staging/
    

Other advantages

  • A very rich list of goals for working with the Nexus repository (mvn help:describe -Dplugin=org.sonatype.plugins:nexus-staging-maven-plugin).
  • Automatic release validation for uploading to Maven Central

To the content

Result

Publishing SNAPSHOT version

When building the project, there is an option to manually trigger the task to upload the SNAPSHOT version to Nexus

Setting up GitLab CI for uploading a Java project to Maven Central

When this task is executed, a corresponding task in the deploy project is triggered (an example).

Trimmed log

Running with gitlab-runner 11.10.0 (3001a600)
  on Deploy runner JSKWyxUw
Using Shell executor...
Running on ih1174328.vds.myihor.ru...
Skipping Git repository setup
Skipping Git checkout
Skipping Git submodules setup
$ rm -rf .* *
$ git config --global credential.helper store
$ echo "https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com" >> ~/\.git-credentials
$ git clone ${DEPLOY_CI_REPOSITORY_URL} .
Cloning into 'shields4j'...
$ git checkout ${DEPLOY_CI_COMMIT_SHA}
Note: checking out '850f86aa317194395c5387790da1350e437125a7'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
  git checkout -b new_branch_name
HEAD is now at 850f86a... skip deploy test-core
$ for pom in $(find . -name pom.xml); do # collapsed multi-line command
$ if [[ "${DEPLOY_CI_COMMIT_TAG}" != "" ]]; then # collapsed multi-line command
[INFO] Scanning for projects...
[INFO] Inspecting build with total of 4 modules...
[INFO] Installing Nexus Staging features:
[INFO]   ... total of 4 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] Shields4J                                                          [pom]
[INFO] test-core                                                          [jar]
[INFO] Shields4J client                                                   [jar]
[INFO] TestNG listener                                                    [jar]
[INFO] 
[INFO] -----------------------------
[INFO] Building Shields4J 1.0.0                                           [1/4]
[INFO] --------------------------------[ pom ]---------------------------------
[INFO] 
[INFO] --- versions-maven-plugin:2.5:set (default-cli) @ shields4j-parent ---
[INFO] Searching for local aggregator root...
[INFO] Local aggregation root: /home/gitlab-deployer/JSKWyxUw/0/TouchBIT/deploy/shields4j
[INFO] Processing change of org.touchbit.shields4j:shields4j-parent:1.0.0 -> 1.0.0-SNAPSHOT
[INFO] Processing org.touchbit.shields4j:shields4j-parent
[INFO]     Updating project org.touchbit.shields4j:shields4j-parent
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO] 
[INFO] Processing org.touchbit.shields4j:client
[INFO]     Updating parent org.touchbit.shields4j:shields4j-parent
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO]     Updating dependency org.touchbit.shields4j:test-core
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO] 
[INFO] Processing org.touchbit.shields4j:test-core
[INFO]     Updating parent org.touchbit.shields4j:shields4j-parent
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO] 
[INFO] Processing org.touchbit.shields4j:testng
[INFO]     Updating parent org.touchbit.shields4j:shields4j-parent
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO]     Updating dependency org.touchbit.shields4j:client
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO]     Updating dependency org.touchbit.shields4j:test-core
[INFO]         from version 1.0.0 to 1.0.0-SNAPSHOT
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] Shields4J 1.0.0 .................................... SUCCESS [  0.992 s]
[INFO] test-core .......................................... SKIPPED
[INFO] Shields4J client ................................... SKIPPED
[INFO] TestNG listener 1.0.0 .............................. SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.483 s
[INFO] Finished at: 2019-04-21T02:40:42+03:00
[INFO] ------------------------------------------------------------------------
$ mvn clean deploy -DskipTests=${SKIP_TESTS}
[INFO] Scanning for projects...
[INFO] Inspecting build with total of 4 modules...
[INFO] Installing Nexus Staging features:
[INFO]   ... total of 4 executions of maven-deploy-plugin replaced with nexus-staging-maven-plugin
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO] 
[INFO] Shields4J                                                          [pom]
[INFO] test-core                                                          [jar]
[INFO] Shields4J client                                                   [jar]
[INFO] TestNG listener                                                    [jar]
[INFO] 
[INFO] -----------------------------
[INFO] Building Shields4J 1.0.0-SNAPSHOT                                  [1/4]
[INFO] --------------------------------[ pom ]---------------------------------
...
DELETED
...
[INFO]  * Bulk deploy of locally gathered snapshot artifacts finished.
[INFO] Remote deploy finished with success.
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] Shields4J 1.0.0-SNAPSHOT ........................... SUCCESS [  2.375 s]
[INFO] test-core .......................................... SUCCESS [  3.929 s]
[INFO] Shields4J client ................................... SUCCESS [  3.815 s]
[INFO] TestNG listener 1.0.0-SNAPSHOT ..................... SUCCESS [ 36.134 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 47.629 s
[INFO] Finished at: 2019-04-21T02:41:32+03:00
[INFO] ------------------------------------------------------------------------

As a result, the version has been uploaded to nexus 1.0.0-SNAPSHOT.

All snapshot versions can be deleted from the repository on the website oss.sonatype.org under your account.

Setting up GitLab CI for uploading a Java project to Maven Central

To the content

Publishing release version

When a tag is set, the corresponding task in the deploy project is automatically triggered to upload the release version to nexus (an example).

Setting up GitLab CI for uploading a Java project to Maven Central

The best part is that the close release in nexus is triggered automatically.

[INFO] Performing remote staging...
[INFO] 
[INFO]  * Remote staging into staging profile ID "9043b43f77dcc9"
[INFO]  * Created staging repository with ID "orgtouchbit-1037".
[INFO]  * Staging repository at https://oss.sonatype.org:443/service/local/staging/deployByRepositoryId/orgtouchbit-1037
[INFO]  * Uploading locally staged artifacts to profile org.touchbit
[INFO]  * Upload of locally staged artifacts finished.
[INFO]  * Closing staging repository with ID "orgtouchbit-1037".
Waiting for operation to complete...
.........
[INFO] Remote staged 1 repositories, finished with success.
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] Shields4J 1.0.0 .................................... SUCCESS [  9.603 s]
[INFO] test-core .......................................... SUCCESS [  3.419 s]
[INFO] Shields4J client ................................... SUCCESS [  9.793 s]
[INFO] TestNG listener 1.0.0 .............................. SUCCESS [01:23 min]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:47 min
[INFO] Finished at: 2019-04-21T04:05:46+03:00
[INFO] ------------------------------------------------------------------------

And if something goes wrong, the task will definitely fail

[INFO] Performing remote staging...
[INFO] 
[INFO]  * Remote staging into staging profile ID "9043b43f77dcc9"
[INFO]  * Created staging repository with ID "orgtouchbit-1038".
[INFO]  * Staging repository at https://oss.sonatype.org:443/service/local/staging/deployByRepositoryId/orgtouchbit-1038
[INFO]  * Uploading locally staged artifacts to profile org.touchbit
[INFO]  * Upload of locally staged artifacts finished.
[INFO]  * Closing staging repository with ID "orgtouchbit-1038".
Waiting for operation to complete...
.......
[ERROR] Rule failure while trying to close staging repository with ID "orgtouchbit-1039".
[ERROR] 
[ERROR] Nexus Staging Rules Failure Report
[ERROR] ==================================
[ERROR] 
[ERROR] Repository "orgtouchbit-1039" failures
[ERROR]   Rule "signature-staging" failures
[ERROR]     * No public key: Key with id: (1f42b618d1cbe1b5) was not able to be located on http://keys.gnupg.net:11371/. Upload your public key and try the operation again.
...
[ERROR] Cleaning up local stage directory after a Rule failure during close of staging repositories: [orgtouchbit-1039]
[ERROR]  * Deleting context 9043b43f77dcc9.properties
[ERROR] Cleaning up remote stage repositories after a Rule failure during close of staging repositories: [orgtouchbit-1039]
[ERROR]  * Dropping failed staging repository with ID "orgtouchbit-1039" (Rule failure during close of staging repositories: [orgtouchbit-1039]).
[ERROR] Remote staging finished with a failure: Staging rules failure!
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] Shields4J 1.0.0 .................................... SUCCESS [  4.073 s]
[INFO] test-core .......................................... SUCCESS [  2.788 s]
[INFO] Shields4J client ................................... SUCCESS [  3.962 s]
[INFO] TestNG listener 1.0.0 .............................. FAILURE [01:07 min]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

As a result, we are left with only one choice. Either delete this version or publish it.

Setting up GitLab CI for uploading a Java project to Maven Central

After the release, the artifacts will appear in Setting up GitLab CI for uploading a Java project to Maven Central

off-topic

It was a surprise to me that Maven indexes other public repositories.
I had to add a robots.txt since it indexed my old repository.

To the content

Conclusion

What we have

  • A separate deploy project where multiple CI tasks for uploading artifacts to public repositories for various programming languages can be implemented.
  • The deploy project is isolated from external interference and can be modified only by users with the Owner and Maintainer roles.
  • A separate Specific Runner with a 'hot' cache for running only deploy tasks.
  • Publication of snapshot/release versions in a public repository.
  • Automated check of the release version's readiness for publication in Maven Central.
  • Protection against the automatic publication of 'raw' versions in Maven Central.
  • Building and publishing snapshot versions 'at the click'.
  • A single repository for obtaining snapshot/release versions.
  • A general pipeline for building/testing/publishing a Java project.

Setting up GitLab CI isn't as complicated as it seems at first glance. You just need to configure CI 'turnkey' a couple of times, and you'll no longer be a novice in this field. Moreover, GitLab documentation is quite extensive. Don't be afraid to take the first step. The path emerges under the feet of those who walk (I can't remember who said that 🙂 ).

I would appreciate your feedback.

In the next article, I'll explain how to configure GitLab CI for concurrent task execution with integration tests (launching the tested services using docker-compose), if you only have one shell runner.

To the content

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster