Getting mvn:release to work with Gitlab CI

Hello,

I am trying to get the maven release process (release:prepare and release:perform) working with the GitLab CI.
I have the following in my .gitlab-ci.yml

  stage: deploy
  image: java:8u102-jdk
  script:
    - mvn release:prepare --settings .ci/settings.xml -B -DskipTests=true
    - mvn release:perform --settings .ci/settings.xml -B -DskipTests=true
  only:
    - master

However, I am running into an error in that job.

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-release-plugin:2.5.3:prepare (default-cli) on project test-1: An error is occurred in the checkin process: Exception while executing SCM command. Detecting the current branch failed: fatal: ref HEAD is not a symbolic ref -> [Help 1]

On googling, i read a post that said the gitlab ci runner works on detached head. https://gitlab.com/gitlab-org/gitlab-ce/issues/19421

I tried adding the CI_BUILD_REF_NAME variable, but still see the error.

Any pointers to get the maven release working?

Thanks!

1 Like

Just answering my question here and documenting what I did to get this working.

  • Maven release plugin needs to commit and push changes to pom.xml for the versions used for release and subsequent development.
  • For this, we need to set up a user that has the permissions to do so on the repo.
  • Add the user’s SSH Key as a VARIABLE with name SSH_PRIVATE_KEY in the repositories settings
  • This key should NOT have a pass phrase
  • Next, if you are using a Docker image to run the build operations, you need to set up an ssh agent and pass on the SSH_PRIVATE_KEY
  • Example:
  stage: deploy
  image: java:8u102-jdk
  script:
    # Install ssh-agent if not already installed, it is required by Docker.
    # (change apt-get to yum if you use a CentOS-based image)
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    
    # Run ssh-agent (inside the build environment)
    - eval $(ssh-agent -s)
    
    # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
    - ssh-add <(echo "$SSH_PRIVATE_KEY")
    
    # For Docker builds disable host key checking. Be aware that by adding that
    # you are susceptible to man-in-the-middle attacks.
    # WARNING: Use this only with the Docker executor, if you use it with shell
    # you will overwrite your user's SSH config.
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    ...    
  only:
    - master
  • Gitlab CI runner works on DETACHED HEAD, but maven release plugin needs to be on a branch
  • So, set the VARIABLE with the name CI_BUILD_REF_NAME with the branch from which you want to release
  • Add the script to checkout that branch as shown below:
  stage: deploy
  image: java:8u102-jdk
  script:
    # Install ssh-agent if not already installed, it is required by Docker.
    # (change apt-get to yum if you use a CentOS-based image)
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    
    # Run ssh-agent (inside the build environment)
    - eval $(ssh-agent -s)
    
    # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
    - ssh-add <(echo "$SSH_PRIVATE_KEY")
    
    # For Docker builds disable host key checking. Be aware that by adding that
    # you are susceptible to man-in-the-middle attacks.
    # WARNING: Use this only with the Docker executor, if you use it with shell
    # you will overwrite your user's SSH config.
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    - git checkout -B "$CI_BUILD_REF_NAME"  
    ...    
  only:
    - master
  • Finally, add the release prepare and perform steps
  stage: deploy
  image: java:8u102-jdk
  script:
    # Install ssh-agent if not already installed, it is required by Docker.
    # (change apt-get to yum if you use a CentOS-based image)
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    
    # Run ssh-agent (inside the build environment)
    - eval $(ssh-agent -s)
    
    # Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
    - ssh-add <(echo "$SSH_PRIVATE_KEY")
    
    # For Docker builds disable host key checking. Be aware that by adding that
    # you are susceptible to man-in-the-middle attacks.
    # WARNING: Use this only with the Docker executor, if you use it with shell
    # you will overwrite your user's SSH config.
    - mkdir -p ~/.ssh
    - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
    - git checkout -B "$CI_BUILD_REF_NAME"  
    - mvn release:prepare --settings .ci/settings.xml -B -DskipTests=true
    - mvn release:perform --settings .ci/settings.xml -B -DskipTests=true    
  only:
    - master

Referred to the following links:

6 Likes

Hi @vpothnis, thanks for your detailed explanation.
I’m beginning with gitlab-ci and maven release plugin. My gitlab runner is dockerized, and I’m having trouble understanding where exactly do you need to set up the user (with the permission to do whatever on the repo) and how to add the SSH key.
Any help is appreciated,
Regards!

Not OP, but adding the user and the key to use is just the straightforward way to add any user:

https://docs.gitlab.com/ee/user/project/members/#add-a-user

The user is only special in how they are used – other than that they look like you or me. You would add the ssh key and grant access to this user as you would any user.

Hello @vpothnis , your proposal works perfect but only the first part. Once the release is finished, a new change is generated in the master branch with the next SNAPSHOT version. So the process runs automatically again and again. How did you organize the configuration so that it does not run infinitely?

Hello @rubenqba ,

another approach is to use the versions-maven-plugin.

  • create a job which only reacts on tags:
release_job:
  only:
     - tags
  script:
    - mvn versions:set -DnewVersion=$CI_COMMIT_TAG
    - mvn deploy

Then you might create the tag on your wirksatation and just push it, thereby creating the release.

1 Like

I think you have to use the [skip ci] phrase in the commit… or get maven to use it.

I don’t know, traditionally one tags the release after verifying that everything works in a “clean” environment.

I have seen this proposal in many places, but feels strange, doesn’t it?

Well, tagging afterwards is harder.
I would build only releases from master using the versions-maven-plugin and she pipeline id. You coiuld push the tag after alll your tests ran successfully… We use a staging Maven repository and promote the binaries later on after the test. We keep all git Tags as they consume almost no resources.

Yes, I think that promoting binaries from staging to production maven repository is the missing link. You can then handle potentially “bad” and “good” releases later on.

Hi all, I have been using this discussion to setup my gitlap pipeline with mvn release plugin and it works perfectly. However, I am encountering a problem: in the release step, maven prepare tries to push a tag and it fails (updates were rejected because the tip of your current branch is behind).
I saw that someone else pushed on master in between my pipeline was running and this should be the problem. I thought that I might solve this allowing maven to ‘git push --force’ or blocking commits while the release pipeline is running.
Any idea or suggestion about this?

Thank you @vpothnis for answering your own question.
I faced a similar issue trying to setup this npm package release-it to create releases in my pipeline.

The command git checkout -B master helped resolve a serie of issues I had with a dangling git HEAD

It looks like this also has a problem if you have when: manual to run the release task. Since we are only checking out master, if anyone pushes to master before the user manually runs the release, we will get the head of master, not the commit associated with this build.

That in fact could happen even if the release runs automatically, if someone pushes while earlier jobs in the pipeline are running.

I think that could be resolved by branching from the commit and building in that branch. You would want to merge back to master to get the new version numbers, which is going to fail (or be very complicated) if anyone has pushed to master in the meantime.

Hello @udalrich,

you could skip pushing back to master and just the commit with the new tag.

Run

  • mvn release:prepare release:perform -DpushChanges=false
  • git push —tags

Additional benefit: you do not spoil the master’s commit history with the two release-commits.
Of course now you need to bump your version somehow.
You could use the ci_pipeline_id approach via inclusion of https://github.com/1and1/ono-maven-shared/blob/master/README.md

For anyone coming here trying to get this to work like me, to stop the release plugin from triggering more builds, you can add this to your pom:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-release-plugin</artifactId>
            <version>2.5.1</version>
            <configuration>
                <scmCommentPrefix>[skip ci] [maven-release-plugin]</scmCommentPrefix>
            </configuration>
        </plugin>
    </plugins>
</build>
2 Likes

That is why I have made sure release jobs only execute when run manually.

prepare-perform-release:
    stage: release
    only:
        - master
    when: manual
    script:
        - git checkout -B "$CI_COMMIT_REF_NAME"
        - mvn -Darguments="-Dmaven.javadoc.skip=true -DskipTests=true" release:clean release:prepare release:perform -B

This is not a perfect solution as the automatic jobs will still run. But they will run only once because the manual job will be skipped breaking the chain.