How to describe 100 Gitlab jobs in 100 lines using Jsonnet

kvaps
kvaps
Jan 16 · 4 min read

In addition to the previous article about deployment tools in Kubernetes, I want to tell you about how you can use Jsonnet to simplify the description of the jobs in your .gitlab-ci.yml

Given

There is a monorepo in which:

  • 10 dockerfiles
  • 30 described deployments
  • 3 environments: devel, stage and prod

Task

Configure a pipeline:

  • Building Docker images should be done by adding a git tag with a version number.
  • Each deployment operation should be performed when pushing to the environment branch and only if files changed in a specific directory
  • Each environment has its own gitlab-runner with a different tag that performs deployment only in this environment.
  • Not any application should be deployed in each of the environments. We should describe the pipeline in order to be able to make exceptions.
  • Some deployments use git submodule and should be run with the GIT_SUBMODULE_STRATEGY=normal environment variable set.

Describing all this may seem like a real hell, but do not despair, armed with Jsonnet, we can easily do it.

Solution

gitlab-ci.yml has built-in capabilities to reduce the description of repeated jobs, for example, you can use extends or include, but it does not provide full-fledged templating, which does not allow you to describe the jobs more concise and efficient.

To solve this problem, I suggest using jsonnet, which allows you to get rid of code repetition when describing any data structures.

I highly recommend you to install a plugin for your editor for working with jsonnet.

For example, there is a nice plugin vim-jsonnet for vim, that turns on syntax highlighting and automatically runs jsonnet fmt during each save (it requires installed jsonnet binary).

Let’s look at the structure of our repository:

.
├── deploy
│ ├── analyse
│ ├── basin
│ ├── brush
│ ├── copper
│ ├── dinner
│ ├── dirty
│ ├── drab
│ ├── drunk
│ ├── education
│ ├── fanatical
│ ├── faulty
│ ├── guarantee
│ ├── guitar
│ ├── hall
│ ├── harmonious
│ ├── history
│ ├── iron
│ ├── maniacal
│ ├── mist
│ ├── nine
│ ├── pleasant
│ ├── polish
│ ├── receipt
│ ├── shop
│ ├── smelly
│ ├── solid
│ ├── stroke
│ ├── thunder
│ ├── ultra
│ └── yarn
└── dockerfiles
├── dinner
├── drunk
├── fanatical
├── guarantee
├── guitar
├── harmonious
├── shop
├── smelly
├── thunder
└── yarn

Docker images will be built using kaniko

Applications will be deployed to the cluster using qbec. Each application is already described for three different environments, in order to apply changes to the cluster, it is enough to run:

qbec apply <environment> --root deploy/<app> --yes

where:

  • <app> — is application name
  • <environment> — is one of our environemtn: devel, stage or prod.

In the end, our jobs should look like this:

Build:

Where {{ image }} will replaced to the name of directory from dockerfiles

Deploy:

Where{{ app }} will replaced to the name of application from the deploy directory, and {{ environment }} will replaced to the name of environment in which we perform deploy.

Let’s describe the prototypes of our jobs as objects in a separate library lib/jobs.jsonnet:

Please note that I deliberately did not specify refs and tags to make our library more flexible and demonstrate you the capabilities of jsonnet to you more detailed, we will add them later in the main file.

Now let’s describe our .gitlab-ci.jsonnet:

Take a look at the ref, tag, and submodule functions at the beginning of the file, they allow you to build an overriding object.

A little explanation: using +: instead of : for override objects allows you to append a value to an existing object or list.

Example : for refs:

will output:

{
"only": { "refs": [ "prod" ] },
"script": [ "echo 123" ]
}

But +: for refs:

will output:

{
"only": { "refs": [ "prod", "tags" ] },
"script": [ "echo 123" ]
}

As you can see, using Jsonnet allows you to describe and merge your objects very efficiently, as a result, you always get ready-made JSON, which we can immediately write to our .gitlab-ci.yml file:

jsonnet .gitlab-ci.jsonnet > .gitlab-ci.yml

Let’s check the number of lines:

# wc -l .gitlab-ci.jsonnet lib/jobs.libsonnet .gitlab-ci.yml
77 .gitlab-ci.jsonnet
24 lib/jobs.libsonnet
1710 .gitlab-ci.yml

Very well

You can see more examples and try Jsonnet directly on the official website: jsonnet.org

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade