Ever merged a branch to only find that things are broken when when you pull the branch with the newly merged changes? Could be things like the codebase doesn’t build anymore, or unit-tests have all of a sudden started to fail. If only this could be prevented somehow 🤔…
Well, turns out - it can! You can take advantage of GitLab CI to make sure you aren’t merging anything bad!
If you’re unfamiliar with what GitLab CI is - think of it as a way of automating tasks surrounding your project. There are absolutely a ton of use-cases for CI/CD, such as deploying your website to a server, compressing assets in one of the project folders, creating new releases for NPM packages and a whole lot more.
However, in our case - we’d use it to review our code and make sure everything is all working ok before we merge it into another branch.
Getting Started
If you are using a self-hosted version of GitLab, you’ll need to install a runner - you can view the documentation here to see how you can get one setup https://docs.gitlab.com/runner/install/.
First thing you will need to do is create a .gitlab-ci.yml
file in the root of your repository. GitLab will automatically look for this file and use it as the reference for what jobs need to be ran.
Within the file, we need to define what image we are going to use for the runner. Runners use Docker to complete jobs - and we need to provide the Docker image we want to use.
For this example, I’m going to use one of the Node images - as that comes pre-installed with Node and NPM. There may be a better image which suits your needs, so have a look around and see which image works for you.
image: node:17.3.1-alpine
Next - we need to define the stages of the CI process. These define which jobs should be ran at the same time. Some jobs may require a previous job to be completed, and splitting them into stages will mean it will ensure that the previous stage is complete before it starts the next.
In our case, we will have two stages:
- dependencies - Install the dependencies and make sure the runner has everything it needs to run other jobs/stages.
- review - Review the codebase, make sure that the code is linted, unit-tests pass etc.
Stages can be defined by adding to our .gitlab-ci.yml
file:
image: node:17.3.1-alpine
stages:
- dependencies
- review
It’s also worth saying - they will run in the order as they are defined, so if we we’re to reverse the order, the dependencies stage would run after the review stage.
Installing Dependencies
As mentioned above, the ‘dependencies’ stage would be used for installing dependencies - in order for us to do that, we would need to define a job. Jobs are specific tasks which the GitLab runner would do.
image: node:17.3.1-alpine
stages:
- dependencies
- review
# The name of the job, 'dependencies'
dependencies:
# Define the stage we want this job to run at - in this case 'dependencies'
stage: dependencies
# We only are going to use this job on merge requests, so only run this job on merge requests
only:
- merge_requests
# Run the NPM install command to install dependencies for the project
script:
- npm install
# Define the node_modules folder to keep and pass between jobs and stages
artifacts:
paths:
- node_modules
This job will run npm install
on merge requests, and keep the node_modules
folder. This means we can use it again for other jobs in the review stage.
Review Stage
Now we have all of the dependencies all sorted out - we can move onto the review stage - where, as the name suggests, it will review our code 😄.
This is where things can start to go into all sorts of directions from here, as projects use different tools, and have different ways of running them.
However, as long as you follow the same format, you can pretty much execute any script! In the example I have below, I’ve defined some variables which you would change in square brackets:
[JOB_NAME]:
# We would want this to run alongside other review jobs
stage: review
# We only want this to run only on merge request pipelines
only:
- merge_requests
script:
[SCRIPT CONTENTS]
I’ve added an example (or two) in the form of checking the code is linted, and unit tests pass to our .gitlab-ci.yml
file example:
image: node:17.3.1-alpine
stages:
- dependencies
- review
# The name of the job, 'dependencies'
dependencies:
# Define the stage we want this job to run at - in this case 'dependencies'
stage: dependencies
# We only are going to use this job on merge requests, so only run this job on merge requests
only:
- merge_requests
# Run the NPM install command to install dependencies for the project
script:
- npm install
# Define the node_modules folder to keep and pass between jobs and stages
artifacts:
paths:
- node_modules
linting:
# We would want this to run alongside other review jobs
stage: review
# We only want this to run only on merge request pipelines
only:
- merge_requests
script:
- npm run lint
unit-tests:
# We would want this to run alongside other review jobs
stage: review
# We only want this to run only on merge request pipelines
only:
- merge_requests
script:
- npm test
And… That’s it! Commit the file, and push it up to GitLab. Once you create a merge request - you should see the GitLab CI kick in, and show the progress on the two stages we have.
The dependencies stage should run first, and then once that’s done, any jobs we have in the review stage should run at the same time, ensuring you have the available runners.
If one of these jobs fail - it will notify you, and you will be sure not to merge it!
Final Thoughts
Over the past few weeks and months, I’ve been trying to better myself when it comes to GitLab pipelines, and take as much advantage as I can with them. They are seriously powerful and quite useful! GitLab CI can be an absolute rabbit hole when it comes to what it can do and all the different ways of doing it. But I’m slowly figuring it out - even if it’s one job at a time 😆
Something which I’ve been trying to do is look into how you can make use of splitting out jobs and stages - and this article is a good example of how and why you should do it. While yes, you can have absolutely everything in one job, there is absolutely no stopping you. But at the same time I feel that jobs should only really have one job each such as running unit tests. I feel they shouldn’t really also install dependencies.
GitLab CI is absolutely massive - and even a bit confusing, even this article will be confusing to some I’m sure, but hopefully you’ve learned something.