How to Release Artifacts Using Gitlab CICD

Vaibhav Yadav's avatar

Vaibhav Yadav

Gitlab Artifacts

Jobs can output an archive of files and directories. This output is known as a job artifact.

Learn more about Gitlab artifacts here.

Gitlab Release

Gitlab releases are nothing different from a normal software release. Its a final version of a particular software made available for the general public after enhancements and bug fixes.

Gitlab makes it easier to manage them by giving us some additional options to create/share them right from the repository.

Learn more about Gitlab release here.

For this blog post, we will take a NodeJS CLI application. Our objective is to generate executable files for Linux and Mac as job artifacts and publish them as a release.

We'll achieve our goal in two stages:

  • Generate artifacts
  • Publish artifacts as Gitlab release

Setting up Gitlab CI

Depending upon whether you want to trigger the pipeline for other branches than the default one, you can have a workflow field in your .gitlab-ci.yml file. You can also tweak the condition as per the requirement of your project.

workflow:
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Next, add cache to make the build faster. We're caching the node modules for this example.

cache:
  paths:
    - node_modules/

To finish the basic configuration, we add the two stages in .gitlab-ci.yml file.

stages:
  - generate_executables
  - release

Generating the artifacts

Every stage in gitlab-ci.yml is assigned a dynamic ID whenever it is executed by a runner. This ID is also referred to as job ID. It is accessible by predefined CICD variable $CI_JOB_ID. We will need the job ID of the stage responsible for generating the artifacts when we publish the artifacts as a release.

Learn more about predefined variables here.

We can store this value in a .env file for it to be read in the next stage.

After storing the job ID in a .env file, we execute the commands to generate the files we need as artifacts.

generate_executables:
  stage: generate_executables
  image: node:16.6.1
  before_script:
    - echo $CI_JOB_ID
    # Writing GE_JOB_ID variable to environment file, will need the value in the next stage.
    - echo GE_JOB_ID=$CI_JOB_ID >> generate_executables.env
    - npm install
  script:
    # Commands responsible to generate the artifacts.
    - npm run pkg
    - echo "Executables generated successfully."
  artifacts:
    paths:
      # Should be relative to your root directory
      - dist/cloudwatch-linux
      - dist/cloudwatch-macos
    reports:
      # To ensure we've access to this file in the next stage
      dotenv: generate_executables.env
  only:
    # Can be removed if you want to trigger the pipeline for merge request or other branches
    - main

Remember the job ID we talked about? Under artifacts field, the paths determine which executable files needs to be added to the job artifact. We use reports:dotenv for generate_executables.env file which contains the job ID required by the release stage.

Releasing the artifacts

This stage depends on Gitlab's release CLI, and needs to be installed on the machine running Gitlab runner.

Learn more about Gitlab's release CLI here.

First of all, notice that in needs field we've mentioned that it requires artifacts from generate_executables stage.

Example:

release:
  stage: release
  ...
needs:
  - job: generate_executables
    artifacts: true

Learn more about passing an environment variable to another job here.

Coming down to the release field the name should be different every time you publish a new release. To dynamically change the name field value at all times I'm using $CI_COMMIT_SHORT_SHA. You can also employ any other predefined variable, or also use semantic versioning. In case of semantic versioning, you'll have to update the name field manually before you run the jobs.

release:
  stage: release
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  script:
    - echo 'running release_job'
    - echo 'Previous Job ID is printed below'
    - echo $GE_JOB_ID
  # Specifying that this job requires artifacts from the previous job to succeed
  needs:
    - job: generate_executables
      artifacts: true
  release:
    name: 'Release Executables $CI_COMMIT_SHORT_SHA'
    description: 'Created using the release-cli'
    # tag_name is a mendatory field and can not be an empty string
    tag_name: '$CI_COMMIT_SHORT_SHA'
    assets:
      links:
        - name: 'Linux executable'
          url: 'https://gitlab.com/codemancers/engineering/cw/-/jobs/${GE_JOB_ID}/artifacts/file/dist/cloudwatch-linux'
        - name: 'Mac executable'
          url: 'https://gitlab.com/codemancers/engineering/cw/-/jobs/${GE_JOB_ID}/artifacts/file/dist/cloudwatch-macos'
  only:
    # Can be removed if you want to trigger the pipeline for merge request or other branches
    - main

The tag_name field can not be an empty string, and it cannot be absent either. Bear that in mind.

The most important field in the second stage is assets. It is here we will use the job ID we have stored in the environment variable during the last stage. As you can see in the url field we've used the variable as ${GE_JOB_ID}, to dynamically get the right URL of the generated artifacts.

Once the job succeeds, you can get the link to the release page from the pipeline logs. Or, you can visit the main page of your repository, and there you can see a link to all your project's releases, right next to a 🚀rocket icon.

PS: In this case, we've used a shell executor with bash for the Gitlab runner. If you're using a docker or docker+machine executor, the string interpolation with URL will require tweaking.