CI pattern for single-platform external snaps

I call an “external snap” (in contrast to “organic snap”) a snap that is build outside the repo of the app being snapped.

Currently I have this CI in place for a single-platform snap, but I plan to extend it to multiple platforms in the future.

Assumptions

CI workflows for external snaps make assumptions about the app maintainer’s practices. The assumptions I made about nushell aren’t too wild and likely compatible with many other apps:

  • The maintainer of the app merges changes into the main branch.
  • Once in a while, the maintainer tags a commit with a semver.
  • No more than one tag per commit.

Pattern

  • Current main goes into edge channel. This is run daily.
  • If the commit is tagged with a semver, promote to beta.
  • On my machine, I snap-install the beta channel.
  • After using the beta channel for a while, promote to stable manually. Here I’m skipping the “candidate” channel because there isn’t anything else to gate the promotion to stable with.

CI steps

You can inspect the full workflow here. In the next section I’ll go over the steps & rationale.

Check versions

The current version is always obtained from tag. We assume the maintainer practices tagging. If no tag is present, we’d get the commit hash, which is totally fine for the edge channel:

CURRENT=$(git describe --tags --always)

To obtain the edge and beta version we need to use the store API:

JSON=$(curl -s -H "Snap-Device-Series: 16" "https://api.snapcraft.io/v2/snaps/info/nushell?fields=version&architecture=amd64")
EDGE=$(echo "$JSON" | jq -r '."channel-map"[] | select(.channel.track == "latest" and .channel.risk == "edge") | .version')
BETA=$(echo "$JSON" | jq -r '."channel-map"[] | select(.channel.track == "latest" and .channel.risk == "beta") | .version')

Promote from edge to beta

We want to promote from edge to beta when the version in edge differs from the version in beta, and also matches a semver string:

if [[ "$EDGE" =~ ^v?[0-9]+(\.[0-9]+){1,2}$ && "$EDGE" != "$BETA" ]]; then
  echo "promote=yes" >> $GITHUB_OUTPUT
else
  echo "promote=no" >> $GITHUB_OUTPUT
fi

Build snap

We want to build only if the current version differs from the version in edge:

if [[ "$CURRENT" != "$EDGE" ]]; then
  echo "build=yes" >> $GITHUB_OUTPUT
else
  echo "build=no" >> $GITHUB_OUTPUT
fi

Install snap (test)

Before publishing to edge, we want to make sure the snap installs ok.

Publish snap

As of writing, snap info nushell reflects the above pattern:

  • The version in edge is the commit object (hash + number of commits from last tag). In the example below, the snap was packed from a commit in main that was 36 commits ahead of the 0.98 version.
  • The version in beta, candidate and stable are semver versions.
channels:
  latest/stable:    0.98.0               2024-09-20 (4) 39MB classic
  latest/candidate: 0.98.0               2024-09-20 (4) 39MB classic
  latest/beta:      0.98.0               2024-09-20 (4) 39MB classic
  latest/edge:      0.98.0-36-g8200831b0 2024-09-29 (5) 39MB classic
installed:          0.98.0                          (4) 39MB classic

See also