Playing with GitHub Container Registry REST API
- 4 minutes read - 760 wordsI’ve a day to catch up on blogging. I’m building a “thing” and getting this near to the finish line consumes my time and has meant that I’m not originating anything particularly new. However, there are a couple of tricks in my deployment process that may be of interest to others.
I’ve been a long-term using of Google’s [Cloud Build] and like the simplicity (everything’s a container, alot!). Because I’m using GitHub repos, I’ve been using GitHub Actions to (re)build containers on pushes and GitHub Container registry (GHCR) to store the results. I know that Google provides analogs for GitHub repos and (forces me to use) Artifact Registry (to deploy my Cloud Run services) but even though I dislike GitHub Actions, it’s really easy to do everything in one place.
For this reason, my source-of-truth of my container images is GHCR and, when I’m deploying containers, I need to be able to reliably determine the most recent image.
GitHub provides a REST API but I find it more challenging to use than other REST APIs that I’ve encountered. I think this is primarly due to its less good (less accurate) documentation.
To use the API, you’ll need to Authenticate to GitHub Container registry. For what follows, I’ll assume you’ve a TOKEN
.
I’m storing container images under a GitHub Organization so I’m also going to be focused on the organization-specific API methods for interacting with GHCR. There is a different set of methods for container images by user and by authenticated user.
You can easily confirm your organization name (and the value for {org}
) below by browsing an organization’s packages: https://github.com/orgs/${org}/packages
List packages for an organization
REST API: /orgs/{org}/packages
TOKEN="..."
ORG="..."
curl \
--silent \
--header "Authorization: Bearer ${TOKEN}" \
https://api.github.com/orgs/${ORG}/packages?package_type=container
NOTE For container images you must append
package_type=container
To just enumerate the container URLs, pipe the results through jq -r '.[]|.html_url'
Get package versions for an organization
REST API: `/orgs/{org}/packages/container/${name}/versions
TOKEN=
ORG=
NAME=
curl \
--silent \
--header "Authorization: Bearer ${TOKEN}" \
https://api.github.com/orgs/${ORG}/packages/container/${NAME}/versions
Where NAME
is the container (package) name and the last component of the URL returned by the list
method above.
NOTE You can’t just plug what was returned from the
list
method into this method because the URL structure differs :-(
To just enumerate the 2 most recent (!) versions, pipe the results through js -r '.[0:2]'
Why am I interested in the 2 most recent versions? Because I’m using Sigstore to sign containers in GitHub Actions.
This generates 2 versions per push. In my case container tags are the SHA-1 of the source’s git commit (that’s just my system) and so I get tags of the form:
- the container:
[0-9a-z]{40}
- the signature:
sha256-[0-9a-z]{64}.sig
The Sigstore signature package should be easy to disambiguate.
So, the first step is to grab both of these. We extend the jq
filter jq -r ".[0:2]|.[]|.metadata.container.tags[0]"
. For each of those 2 most recent versions, grab the first (0th) metadata.container.tags
.
Now, unfortunately, these may appear in either order 0=container, 1=signature or vice versa. We’re only interested in the non .sig
tag and we can use jq
to grab this:
SIGSTORE="sha256-[0-9a-z]{64}.sig"
FILTER="[
.[0:2]
| .[]
| .metadata.container.tags[0]
]
| if (
.[0]
| test(\"${SIGSTORE}\")
)
then .[1]
else .[0]
end
"
curl \
--silent \
--header "Authorization: Bearer ${TOKEN}" \
https://api.github.com/orgs/{ORG}}/packages/container/${NAME}/versions | jq -r "${FILTER}"
SIGSTORE
defines a regex that is applied to jq
’s test
which returns a boolean. We pick one of our 2 options ([0]
) and if matches the regex, then it’s the Sigstore signature package and we want the other package ([1]
) our image.
NOTE This assumes that the REST API preserves this ordering of most recent first and that there’s a Sigstore signature image produced for each container image.
Most Recent Images cache
I use this mechanism to iterate across a subset of my organization’s container images to maintain a local cache of the most recent versions.
NOTE I’m using
git rev-parse HEAD
to tag my images. Your container images will have different tags.
.env
:
IMAGE_A="a:1234567890123456789012345678901234567890"
IMAGE_B="a:1234567890123456789012345678901234567890"
IMAGE_C="a:1234567890123456789012345678901234567890"
Then:
TOKEN=
ORG=
SIGSTORE=
FILTER=
IMAGES=(
"IMAGE_A"
"IMAGE_B"
"IMAGE_C"
)
for IMAGE in ${IMAGES[@]}
do
# Split the IMAGE into NAME:TAG
IFS=':' read NAME TAG <<< ${IMAGE}
# Grab the IMAGE's LATEST tag from GHCR
LATEST=$(curl \
--silent \
--header "Authorization: Bearer ${TOKEN}" \
https://api.github.com/orgs/${ORG}/packages/container/${IMAGE}/versions \
| jq -r "${FILTER}")
# If LATEST differs from the current TAG
if [ "${TAG}" != "${LATEST}" ]
then
printf "Updating: %s --> %s\n" ${TAG} ${LATEST}
# Update the environment file cache
sed \
--in-place \
--expressions="s|${IMAGE}|${NAME}:${LATEST}|g" \
.env
else
printf "Current\n"
fi
done
That’s all!