1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

dist/tools/buildsystem_sanity_check: check pinned docker version

This tests if the latest manifest on dockerhub matches the pinned
version. The idea is that PRs are not merged until the pinning is
fixed, so that we can ensure that `make BUILD_IN_DOCKER=1` will
always succeed with the pinned version.
This commit is contained in:
Marian Buschsieweke 2024-06-04 09:07:53 +02:00
parent 89754bf6b0
commit 17dcb97571
No known key found for this signature in database
GPG Key ID: 77AA882EC78084E6
2 changed files with 115 additions and 0 deletions

View File

@ -16,6 +16,7 @@
: "${RIOTBASE:="$(cd "$(dirname "$0")/../../../" || exit; pwd)"}"
: "${RIOTTOOLS:=${RIOTBASE}/dist/tools}"
: "${RIOTMAKE:=${RIOTBASE}/makefiles}"
# not running shellcheck with -x in the CI --> disable SC1091
# shellcheck disable=SC1091
. "${RIOTTOOLS}"/ci/github_annotate.sh
@ -380,6 +381,28 @@ check_tests_application_path() {
find tests/ -type f "${patterns[@]}" | error_with_message "Invalid application path in tests/"
}
check_pinned_docker_version_is_up_to_date() {
local pinned_digest
local pinned_repo_digest
local upstream_digest
local upstream_repo_digest
pinned_digest="$(awk '/^DOCKER_TESTED_IMAGE_ID := (.*)$/ { print substr($0, index($0, $3)); exit }' "$RIOTMAKE/docker.inc.mk")"
pinned_repo_digest="$(awk '/^DOCKER_TESTED_IMAGE_REPO_DIGEST := (.*)$/ { print substr($0, index($0, $3)); exit }' "$RIOTMAKE/docker.inc.mk")"
# not using docker and jq here but a python script to not have to install
# more stuff for the static test docker image
IFS=' ' read -r upstream_digest upstream_repo_digest <<< "$("$RIOTTOOLS/buildsystem_sanity_check/get_dockerhub_digests.py" "riot/riotbuild")"
if [ "$pinned_digest" != "$upstream_digest" ]; then
git -C "${RIOTBASE}" grep -n '^DOCKER_TESTED_IMAGE_ID :=' "$RIOTMAKE/docker.inc.mk" \
| error_with_message "Update docker image SHA256 to ${upstream_digest}"
fi
if [ "$pinned_repo_digest" != "$upstream_repo_digest" ]; then
git -C "${RIOTBASE}" grep -n '^DOCKER_TESTED_IMAGE_REPO_DIGEST :=' "$RIOTMAKE/docker.inc.mk" \
| error_with_message "Update manifest digest to ${upstream_repo_digest}"
fi
}
error_on_input() {
! grep ''
}
@ -402,6 +425,7 @@ all_checks() {
check_no_riot_config
check_stderr_null
check_tests_application_path
check_pinned_docker_version_is_up_to_date
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""
Command line utility to get the image sha256 sum and the manifest sha256 sum
of the latest image at dockerhub.
"""
import http.client
import json
import sys
def get_docker_token(repo):
"""
Get an API access token for the docker registry
:param repo: the repository the API token should be valid for
:type repo: str
:return: the access token to use
:rtype: str
"""
conn = http.client.HTTPSConnection("auth.docker.io")
conn.request("GET",
f"/token?service=registry.docker.io&scope=repository:{repo}:pull",
headers={"Accept": "*/*"})
resp = conn.getresponse()
if resp.status != 200:
raise Exception(f"Tried to get a docker token, but auth.docker.io "
f"replied with {resp.status} {resp.reason}")
decoded = json.loads(resp.read())
conn.close()
return decoded["token"]
def get_manifest(repo, tag="latest", token=None):
"""
Get the manifest of the given repo
:param repo: The repository to get the latest manifest of
:type repo: str
:param tag: The tag to get the manifest of. (Default: "latest")
:type tag: str
:param token: The authorization token to use for the given repo.
(Default: get a fresh one.)
:type token: str
:return: the parsed manifast
:rtype: dict
"""
token = get_docker_token(repo) if token is None else token
conn = http.client.HTTPSConnection("index.docker.io")
hdrs = {
"Accept": "application/vnd.docker.distribution.manifest.v2+json",
"Authorization": f"Bearer {token}"
}
conn.request("GET", f"/v2/{repo}/manifests/{tag}", headers=hdrs)
resp = conn.getresponse()
if resp.status != 200:
raise Exception(f"Tried to get a docker manifest, but "
f"index.docker.io replied with {resp.status} "
f"{resp.reason}")
repo_digest = resp.getheader("ETag")[len("\"sha256:"):-1]
decoded = json.loads(resp.read())
conn.close()
return (decoded, repo_digest)
def get_upstream_digests(repo, tag="latest", token=None):
"""
Get the SHA256 hash of the latest image of the given repo at dockerhub
as string of hex digests
:param repo: The repository to get the hash from
:type repo: str
:param tag: The tag to get the manifest of. (Default: "latest")
:type tag: str
:param token: The authorization token to use for the given repo.
(Default: get a fresh one.)
:type token: str
:return: A 2-tuple of the image digest and the repo digest
:rtype: (str, str)
"""
token = get_docker_token(repo) if token is None else token
manifest, repo_digest = get_manifest(repo, tag=tag, token=token)
digest = manifest["config"]["digest"]
return (digest[len("sha256:"):], repo_digest)
if __name__ == '__main__':
if len(sys.argv) != 2:
sys.exit(f"Usage {sys.argv[0]} <REPO_NAME>")
digest, repo_digest = get_upstream_digests(sys.argv[1])
print(f"{digest} {repo_digest}")