Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ the add-an-image workflow.

- `update` finds `IMAGES` with a regex. Keep `IMAGES = (` and its closing `)`
at column 0 with only indented, non-blank lines between them. Otherwise
`update` silently writes nothing.
`update` exits with an error.
- Pin digests only for architectures in `ARCHS`.
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,6 @@ with mutable tags, such as `latest` or `14`, pick up new digests. Run
`python3 main.py update --dry-run` to print the rewritten file without saving
it.

`update` fails with HTTP 400 when it reaches a single-architecture entry, such
as `checkr/flagr:latest`. Until that is fixed, add the new entry's digests by
hand. Get them with `docker buildx imagetools inspect <image>:<tag>`.

CI runs `python3 main.py sync --dry-run` on pull requests to list the images it
would push. After merge, CI runs `sync` on `main` and pushes each image that is
missing one of its pinned digests on ghcr.io.
Expand Down
14 changes: 10 additions & 4 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from typing import Mapping
from typing import NamedTuple

ARCHS = frozenset(('amd64', 'arm64', 'arm64/v8'))
ARCHS = frozenset(('amd64', 'arm64'))

LIST = 'application/vnd.docker.distribution.manifest.list.v2+json'
SINGLE = 'application/vnd.docker.distribution.manifest.v2+json'
Expand Down Expand Up @@ -60,12 +60,12 @@ def _digests(registry: str, image: str, tag: str) -> list[tuple[str, str]]:
req = urllib.request.Request(
f'https://{registry}/v2/{image}/manifests/{tag}',
headers={
'Authorization': f'Bearer {token}',
# annoyingly, even if we only "Accept" the list, docker.io will
# send us a single manifest
'Accept': f'{LIST}, {INDEX}, {SINGLE};q=.9',
},
)
req.add_unredirected_header('Authorization', f'Bearer {token}')
resp = urllib.request.urlopen(req)
ret = json.load(resp)
if resp.headers['Content-Type'] in {LIST, INDEX}:
Expand All @@ -77,8 +77,10 @@ def _digests(registry: str, image: str, tag: str) -> list[tuple[str, str]]:
blob = ret['config']['digest']
req = urllib.request.Request(
f'https://{registry}/v2/{image}/blobs/{blob}',
headers={'Authorization': f'Bearer {token}'},
)
# registries redirect blob downloads to object storage, which
# rejects requests that also carry the registry's bearer token
req.add_unredirected_header('Authorization', f'Bearer {token}')
ret = json.load(urllib.request.urlopen(req))
return [(ret['architecture'], resp.headers['Docker-Content-Digest'])]
else:
Expand Down Expand Up @@ -549,7 +551,11 @@ def main() -> int:
with open(__file__) as f:
src = f.read()

src = re.sub(r'IMAGES = \(\n( +.+\n)+\)', '\n'.join(lines), src)
src, n = re.subn(
r'IMAGES = \(\n(?: +\S.*\n)+\)', '\n'.join(lines), src,
)
if n != 1:
raise SystemExit(f'expected 1 IMAGES block, found {n}')

if args.dry_run:
print(src)
Expand Down
Loading