Part 4 – Customizing CICD pipelines for Embedded Linux Projects

In this series of posts, we propose a simple and full open source CI/CD pipeline for your Linux device development on GitLab that takes advantage of the power of Pantacor Hub and Pantavisor Linux.

Table of Contents (4 part series)
Part 1: Automating CICD Pipelines for Embedded Linux Projects
Part 2 – Generate Flashable Images with CI/CD Pipelines
Part 3 – Templated CICD pipelines for Embedded Linux Devices
Part 4 – Customizing CICD pipelines for Embedded Linux Projects

In the previous post, we explained the internals of our CI/CD pipeline in order to understand how it works.

In the fourth part, we are going to show you how to customize the pipeline to fit your needs.


Let’s return to the device-ci diagram we saw in Part 3.

cicd pipeline for embedded Linux

Since we need to change the behavior of the templates, and we are interested in overrriding them from our project in .gitlab-ci.yml, we can take advantage of Gitlab’s CI capability for this.

For demonstration purposes, we will add a new test stage to the pipeline that takes the test results from the device after each update. For the sake of keeping this post simple we will build an image for all device states instead of from any tags. This will trigger the update whenever a change in a dependency is pushed, and will store images to a different cloud service instead of AWS. In addition to this we’ll add more content to the images metadata, etc.

How to customize the CI pipeline

I’m going to link to this great GitLab CI/CD pipeline configuration reference. I recommend that you take a look at it if you really want to get into how GitLab’s CI/CD works. Personally I always have it at hand when doing the changes, since it contains almost everything you will ever need.

As mentioned before, we are going to make a simple change to the pipeline. We’ll add a test stage to the pipeline that takes a mock test result from the board with a failed test. This is going to require changes on the device side so that we can publish mock test results. Also, from the CI/CD project, you’ll need to add the new testing stage and also check the results uploaded by the device.

Device side changes

Go back to your forked gpio app project.

We will simulate the test and directly push the fail or pass to the device metadata from Pantacor Hub. Pantacor Hub metadata is accesible from the pvr cli which makes it is perfect for communicating between the device and the pipeline script.

To upload the metadata from a container running on the device, you’ll need to install the pvmeta utility to it. These are the changes that you must make to the Dockerfile in order to install pvmeta:

--- a/Dockerfile.ARM32V6
+++ b/Dockerfile.ARM32V6
@@ -1,5 +1,22 @@
+FROM alpine as qemu
+RUN wget -O /qemu-arm-static; \
+       chmod a+x /qemu-arm-static
+FROM as pvtoolbox
 FROM arm32v6/bash
+COPY --from=qemu /qemu-arm-static /usr/bin/
+COPY --chown=0:0 --from=pvtoolbox /usr/local/bin/pvsocket /usr/local/bin/pvsocket
+COPY --chown=0:0 --from=pvtoolbox /usr/local/bin/pvlog /usr/local/bin/pvlog
+COPY --chown=0:0 --from=pvtoolbox /usr/local/bin/pvmeta /usr/local/bin/pvmeta
+RUN chmod +x /usr/local/bin/pvsocket; \
+       chmod +x /usr/local/bin/pvlog; \
+       chmod +x /usr/local/bin/pvmeta
 ADD /usr/bin/
 ENTRYPOINT [ "/usr/bin/" ]

The actual pushing of the metadata can be done in the gpio script. Let’s name the metadata key gpio-test and, for now, set its value to fail:

--- a/
+++ b/
@@ -33,6 +33,7 @@ set_gpio_value() {
 while [ true ];
+       pvmeta update gpio-test=fail
        for i in `ls $GPIO_META_PATH`;
                set_gpio_value $i `cat $GPIO_META_PATH/$i`

CI/CD project changes

Now we need to make the changes in the device-ci project that we created in Part 1. There are two changes in our .gitlab-ci.yml that will override the template jobs: redefine the stage list with a new test stage after validation and specify the test job that will get the gpio-test key from device metadata and finally, exit 1 if its value is not a pass:

--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,6 +3,13 @@ include:
   ref: 35f3cf627279aabb2b241ca79bf00f7b16e7d12c
   file: '/yml/update-ci.yml'
+  - post
+  - push
+  - build
+  - validate
+  - test
+  - deploy
   extends: .update-scheduled
@@ -25,3 +32,12 @@ check-gpio-stable-device:
   extends: .check-status-stable
     PH_TARGET_DEVICE: "gpio-stable-device"
+  stage: test
+  script:
+    - TOKEN=`http --ignore-stdin POST username=$PHUSER password=$PHPASS | jq -r .token`
+    - if [ $(pvr -a $TOKEN dev get anibal/gpio-latest-device | jq '."device-meta"."gpio-test"') != "\"pass\"" ]; then exit 1; fi
+  except:
+    - schedules
+    - web

We didn’t bother to make the device name configurable. Note that this is mandatory if you are doing this in production.

Test it

After you’ve pushed all the changes to their respective master branches, you’re ready to manually trigger the update jobs from the GitLab user interface so that the device gets the (failing) new update.

Time to make it pass, by commiting this to the gpio app source code:

--- a/
+++ b/
@@ -33,7 +33,7 @@ set_gpio_value() {
 while [ true ];
-       pvmeta update gpio-test=fail
+       pvmeta update gpio-test=pass
        for i in `ls $GPIO_META_PATH`;
                set_gpio_value $i `cat $GPIO_META_PATH/$i

After manually spinning up a new update job, the test will pass, since the device now contains the pass uploading version.


We have finally reached the end of this series on automating CICD pipelines for embedded Linux device development. We’ve gone through setting up a basic CICD pipeline that keeps devices up to date; we built and released flashable images from the state of those devices, and described how it works. Finally we created templates for the pipelines and also customized it.

I hope it was useful to you in some way. I’d really love to hear your feedback if you liked it, hated it, though something was missing or had used a similar solution. You can also contact us on slack if you’d like to discuss embedded Linux development with Pantavisor or anything else.