Using GitLab CI
Example
Rust-based GitLab CI file starting point
In Continuous Integration/Continuous Development (CI/CD) parlance, pipelines automate the steps required to develop code. This typically involves steps that initiate code builds, run tests, deploy staging environments, and puplish to production.
Conventionally, pipelines are triggered by a code commit, after the commit tests are conducted code quality checkers can be done here and unit tests, next is build once the code is tested and integrated the code needs to be built, next the Testing stage, this stage checks the interaction between builds and ensures the app is working correctly, last is the deploy stage where the code is deployed to production. (ABOVE PARAGRAPH NEEDS FIXING)
What you need to use GitLab CI/CD:
- A Git codebase
- A YAML file in the root path of your repo named
.gitlab-ci.yml
In the gitlab-ci.yml
you can define scripts to run, define include and cache dependencies where you want to deploy application, etc. This file determines the structure of the pipeline, what steps to execute, and make decisions on conditions.
GitLab Python API
See https://python-gitlab.readthedocs.io/en/stable/api-usage.html and https://python-gitlab.readthedocs.io/en/stable/index.html
How to write a YAML file for CI/CD in GitLab
- Define the Docker image being used to run the scripts
- Define your stages: build, test, deploy, etc.
- Define your image
- Define the elements of the various stages in sequence... everything in this
.gitlab-ci.yml
is run in sequence.
Pro Strats
If it helps modularity or reusability a project's gitlab-ci.yml file can point to gitlab-ci.yml files in other projects, using the other project's pipeline files within their own. To do this, add the following items (adapted for your specific case).
include:
- project: "<group>/<directory>/<target-project>"
ref: "master" # this can also be a tag, which can be better practice
file: "<custom-gitlab-ci>.yml"
include:
- project: "<group>/<directory>/<target-project>"
ref: "master" # this can also be a tag, which can be better practice
file: "<custom-gitlab-ci>.yml"
You can skip the GitLab pipeline by adding [skip ci]
to any commit message.
A good way of reusing code in any YAML file is the use YAML Anchors.
.reusable_script: &reusable_script
- echo "Your custom reusable script"
build:
script:
- *reusable_script
.reusable_script: &reusable_script
- echo "Your custom reusable script"
build:
script:
- *reusable_script
For additional details on using YAML anchors see the additional resources at the bottom of this page.
How to Setup a GitLab Runner
Assumption: Linux
- Install
gitlab-runner
Add the gitlab-runner
repository
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
Install the GitLab runner
sudo apt-get install gitlab-runner
sudo apt-get install gitlab-runner
- Register the runner
- To register a group runner go to your group and select CI/CD > Runners
- There should be a button Register a group runner with instruction on how the correctly register the runner
- Follow the instructions
- Select
shell
to run on the raw Linux machine anddocker
to run your pipelines in a docker image - You may need to update the sudoers file so that the gitlab-runner user can call commands without sudo access
Debugging the GitLab Runner
If your GitLab runner is a shell executor and you have root access to the device, then you can login as the gitlab-runner
user (or whatever you named the user). To change the password for the GitLab runner use
sudo su
passwd gitlab-runner
# Follow the prompts to provide a new password
sudo login gitlab-runner
sudo su
passwd gitlab-runner
# Follow the prompts to provide a new password
sudo login gitlab-runner
GitLab Pages: Code Coverage Reports
Hosting code coverage reports on GitLab pages is straightforward. To begin you have to generate the coverage report in HTML. This is typically provided in some way by your testing library or by a separate code coverage library such as lcov for C++.
To begin, you need to generate the coverage report during the test stage of your CI pipeline. Then, upload the path as an artifact in the .gitlab-ci.yml file.
test:
stage: test
...
artifacts:
when: always
paths:
- build/coverage/
test:
stage: test
...
artifacts:
when: always
paths:
- build/coverage/
Next, add a pages job to your pipeline and move the coverage report into a folder named public/. Below is a simple working job; there are additional options available.
pages:
stage: deploy
dependencies:
- test
script:
- mv build/coverage/ public/
artifacts:
paths:
- public
expire_in: 30 days
only:
- master
pages:
stage: deploy
dependencies:
- test
script:
- mv build/coverage/ public/
artifacts:
paths:
- public
expire_in: 30 days
only:
- master
Once the pipeline completes you should be able to access the coverage report. The URL where it was uploaded uses, by default, the following convention:
- If the project URL is https://gitlab.com/GROUP/SUBGROUP/PROJECT
- The GitLab pages URL will be https://GROUP.gitlab.io/SUBGROUP/PROJECT See Settings > Pages to show the URL where your pages are served.
Making Releases
A common practice is to use a master/main branch for production code, everytime a feature branch is merged into it, the GitLab CI pipeline creates a release. The below workflow does three things:
- Uploads relevant packages to the package registry
- Create a tag for the merge commit
- Create an entry on the releases page
To begin, during the build stage of your pipeline, add the relevant packages and version information to a variables.env file. Ensure this is added as an artifact as a reports:dotenv field.
build:
stage: build
needs: []
only:
- main
script:
- *build-and-package
- echo "PACKAGE=$PACKAGE" >> variables.env
- echo "VERSION=$VERSION" >> variables.env
artifacts:
paths:
- "build/$PACKAGE"
reports:
dotenv: variables.env
build:
stage: build
needs: []
only:
- main
script:
- *build-and-package
- echo "PACKAGE=$PACKAGE" >> variables.env
- echo "VERSION=$VERSION" >> variables.env
artifacts:
paths:
- "build/$PACKAGE"
reports:
dotenv: variables.env
Next, create a release job. This job requires specific stage and image fields, and a user defined release field. Also ensure you add the job from the build stage that exports the variables.env file.
release_job:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest # <- Alpine Linux
needs:
- job: "build"
artifacts: true
only:
- master
before_script: []
script:
- apk add curl
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "build/$PACKAGE" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$CI_PROJECT_NAME/$VERSION/$PACKAGE"'
release:
tag_name: "$VERSION"
name: "$CI_PROJECT_NAME v$VERSION"
description: "Release for $CI_PROJECT_NAME"
assets:
links:
- name: "$PACKAGE"
url: "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$CI_PROJECT_NAME/$VERSION/$PACKAGE"
release_job:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest # <- Alpine Linux
needs:
- job: "build"
artifacts: true
only:
- master
before_script: []
script:
- apk add curl
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "build/$PACKAGE" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$CI_PROJECT_NAME/$VERSION/$PACKAGE"'
release:
tag_name: "$VERSION"
name: "$CI_PROJECT_NAME v$VERSION"
description: "Release for $CI_PROJECT_NAME"
assets:
links:
- name: "$PACKAGE"
url: "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$CI_PROJECT_NAME/$VERSION/$PACKAGE"
Viola! Once this is prepared, every merge time a Merge Request into master/main completes and the pipeline execuetes, a new release will be created.
Examples
Basic
stages:
- lint-css
- lint-js
- unit-test
image: node:6.11.2
lint css:
stage: lint-css
before_script:
- npm install
cache:
untracked: true
only:
- master
script:
- ./node_modules/gulp/bin/gulp.js lint-css
lint js:
stage: lint-js
cache:
untracked: true
policy: pull
only:
- master
script:
- ./node_modules/gulp/bin/gulp.js lint-js
run unit test:
stage: unit-test
cache:
untracked: true
policy: pull
only:
- master
script:
- ./node_modules/gulp/bin/gulp.js test
stages:
- lint-css
- lint-js
- unit-test
image: node:6.11.2
lint css:
stage: lint-css
before_script:
- npm install
cache:
untracked: true
only:
- master
script:
- ./node_modules/gulp/bin/gulp.js lint-css
lint js:
stage: lint-js
cache:
untracked: true
policy: pull
only:
- master
script:
- ./node_modules/gulp/bin/gulp.js lint-js
run unit test:
stage: unit-test
cache:
untracked: true
policy: pull
only:
- master
script:
- ./node_modules/gulp/bin/gulp.js test
Create a tag in GitLab CI
If you want to create a tag and a release, the best option is to use the following example.
release_job:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
needs:
- job: "test"
artifacts: true
only:
- master
before_script: []
script:
- apk add curl
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "build/$PACKAGE" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$CI_PROJECT_NAME/$VERSION/$PACKAGE"'
release:
tag_name: "${VERSION}"
name: "Version ${VERSION}"
description: "Release version ${VERSION} for ${CI_PROJECT_NAME}"
assets:
links:
- name: "${PACKAGE}"
url: "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$CI_PROJECT_NAME/$VERSION/$PACKAGE"
release_job:
stage: release
image: registry.gitlab.com/gitlab-org/release-cli:latest
needs:
- job: "test"
artifacts: true
only:
- master
before_script: []
script:
- apk add curl
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file "build/$PACKAGE" "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$CI_PROJECT_NAME/$VERSION/$PACKAGE"'
release:
tag_name: "${VERSION}"
name: "Version ${VERSION}"
description: "Release version ${VERSION} for ${CI_PROJECT_NAME}"
assets:
links:
- name: "${PACKAGE}"
url: "$CI_API_V4_URL/projects/$CI_PROJECT_ID/packages/generic/$CI_PROJECT_NAME/$VERSION/$PACKAGE"
Otherwise, if you only want to create a tag, use something like
tag job:
stage: tag
only:
- master
needs:
- test
script:
- git remote set-url origin ${PROJECT_URL}
- git config --global user.name "${CI_USERNAME}"
- git config --global user.email "${CI_EMAIL}"
- git fetch origin ${CI_COMMIT_REF_NAME}
- git checkout ${CI_COMMIT_REF_NAME}
- DATE_VERSION=$(date +%x)
- git tag ${DATE_VERSION}
- git push ${DATE_VERSION}
tag job:
stage: tag
only:
- master
needs:
- test
script:
- git remote set-url origin ${PROJECT_URL}
- git config --global user.name "${CI_USERNAME}"
- git config --global user.email "${CI_EMAIL}"
- git fetch origin ${CI_COMMIT_REF_NAME}
- git checkout ${CI_COMMIT_REF_NAME}
- DATE_VERSION=$(date +%x)
- git tag ${DATE_VERSION}
- git push ${DATE_VERSION}
Note here that you need to create the variable ${PROJECT_URI}
, this involves creating a CI user with the appropriate credentials and setting the variable earlier in the CI pipeline.
PROJECT_URI: "https://${CI_GIT_USERNAME}:${CI_GIT_ACCESS_TOKEN}@gitlab.com/${CI_PROJECT_PATH}.git"
PROJECT_URI: "https://${CI_GIT_USERNAME}:${CI_GIT_ACCESS_TOKEN}@gitlab.com/${CI_PROJECT_PATH}.git"
Rust Cargo Caching
You can cache between pipelines by adding the cache:key:
(see the gitlab-ci.yml
reference). See: https://stackoverflow.com/questions/74881598/how-to-speed-up-gitlab-ci-job-with-cache-and-artifacts
ADDITIONAL RESOURCES Code Coverare with GitLab Pages
- https://about.gitlab.com/blog/2016/11/03/publish-code-coverage-report-with-gitlab-pages/
- https://docs.gitlab.com/ee/user/project/pages/getting_started_part_one.html Custom GitLab Runner
- https://kyosenaharidza.medium.com/how-to-create-your-own-gitlab-runner-a37cd54bae33#:~:text=On gitlab%2C go to settings,custom runner for this project GitLab Artifactos
- https://docs.gitlab.com/ee/ci/jobs/job_artifacts.html