Categories
Snapcraft

Use GitHub Actions or any CI/CD based on Docker containers to build your Snap Packages

When it comes to Snap Packages the ease of entry is incredibly low. This is especially true with the Snapcraft Build Service, which is hosted at Snapcraft.io. The Build Service ties into a Snap Package author’s GitHub account to automatically build a Snap Package from a linked repository. When the Snap Package has finished building the Build Service will then release the Snap into the ‘edge’ track.

The problem

The Build Service has the drawback that, while being quite easy to get started with, it is very restricted in its ability to perform customised release schedules and structures. Another problem is that developers often have their own processes already in place to build and test their app using services such as Travis CI or GitHub Actions. These services are much easier to program to tailor their behaviour than the Snapcraft Build Service.

I maintain many Snap Packages and have run-up against the limitations of the Snapcraft Build Service for several of them. The affected Snap Packages that are causing frustration include those where the Upstream project has several Release Types. One of these problematic Snap packages is OpenRA, which has two types of release: Stable and Playtest. A frequent feature request for the OpenRA Snap Package has been that I release the Playtests in addition to the Stable releases, but the Build Service has prevented me from achieving this goal.

With the readily available resource of services like Travis-CI and GitHub Actions, I have been investigating the possibility of building my Snap Packages using one of these. The Snapcraft Team have long provided a Container Image of Snapcraft in Docker Hub, but until now they have only been able to provide an AMD64 variant. Unfortunately, this means that there is limited scope for compiling Snap Packages using the Container Image for architectures other than the 64bit x86 platform.

The solution

Enter from stage right Docker Buildx, which can run Container Image Builds for multiple architectures. Container Images are built for the architectures specified at build time and tied into a single namespace via a Container Image Manifest on a Container Registry such as Docker Hub. When using GitHub Actions, you can get a fully functional Buildx setup to run your builds with the Docker Buildx Action. The Action utilises qemu user-mode emulation to transparently run the multiple architecture builds on GitHub Actions, which would normally be limited to the amd64 and i386 architectures.

Buildx

Step 1 of my master plan uses Buildx to release an experimental build of Snapcraft under my own Docker Hub namespace which is compiled for Core, Core18, and Core20 for all supported Snap Architectures (excepting s390x for the Core variant because there appears to be a BASH-related issue when combined with qemu-user.) My Snapcraft Container Image is on Docker Hub at diddledan/snapcraft.

Qemu

Now for the really fun part, and this is going to be a whirlwind: we have the readily available build of Snapcraft for all architectures in a convenient Container Image, but how do we use that to build our Packages for the Snap Store?

Hold onto your hats, because here we go… We can use qemu-user-static to execute the foreign-architecture containers built above to build for the Snap Store!

We simply need to wire-up the GitHub Actions or Travis-CI configuration, which configures qemu-user-static and runs our build. Below I’ll show my own GitHub Actions configuration:

name: Build and Release Snap on: push: branches: - master jobs: build-stable: runs-on: ubuntu-latest # Let's build for many architectures.. strategy: matrix: architecture: - linux/amd64 - linux/386 - linux/arm64 - linux/arm/v7 steps: # Checkout the code of our Repository - uses: actions/checkout@v2 # Do the build! - id: build run: | # Enable docker daemon support for --platform parameter echo '{"experimental": true}' | sudo tee /etc/docker/daemon.json > /dev/null sudo systemctl restart docker # Configure qemu-user-static docker run --rm --tty \ --security-opt apparmor:unconfined \ --cap-add SYS_ADMIN \ multiarch/qemu-user-static --reset -p yes # Run snapcraft docker run --rm --tty \ --security-opt apparmor:unconfined \ --cap-add SYS_ADMIN \ --device /dev/fuse \ --volume /sys \ --volume /sys/fs/cgroup:/sys/fs/cgroup:ro \ --volume $GITHUB_WORKSPACE:$GITHUB_WORKSPACE \ --workdir $GITHUB_WORKSPACE \ --platform "${{ matrix.architecture }}" \ --env PLAYTEST="${{ matrix.playtest }}" \ diddledan/snapcraft:core18 echo ::set-output name=snap::$(find $GITHUB_WORKSPACE -maxdepth 1 -type f -name "*.snap" | head -n1) # Release this Snap to the channel we choose. - uses: snapcore/action-publish@v1 with: store_login: ${{ secrets.STORE_LOGIN }} snap: ${{ steps.build.outputs.snap }} release: beta

Store credentials for publishing

The only thing left is to add a STORE_LOGIN secret to the GitHub Repository with the contents of store-credentials.secret, which you generate with the following command in a Linux Terminal:

snapcraft export-login --snaps="$SNAP_NAME" --acls package_access,package_push,package_update,package_release store-credentials.secret

Make sure you replace $SNAP_NAME with the name of your snap and you’ll be set.

Conclusion

And that is the last step; my plan is complete. With the build of Snapcraft in a Container Image for all Snap Architectures, combined with qemu-user-static, we can now build Snap Packages using GitHub Actions, Travis-CI, or any other competing Continuous Integration runner, for all architectures. We can now release to the Snap Store in whichever track we desire using rules that we choose and codify into our build setup.

That’s it from me, folks. Now it’s your turn to Snap something awesome using this simple setup and your choice of CI provider! I can’t wait to see what you build.

Leave a Reply

Your email address will not be published. Required fields are marked *