|
| 1 | +""" |
| 2 | +Follow homebrew image index to get the 'hello' bottle specific to your platform |
| 3 | +""" |
| 4 | +import re |
| 5 | + |
| 6 | +import oras.client |
| 7 | +import oras.provider |
| 8 | +from oras import decorator |
| 9 | + |
| 10 | + |
| 11 | +class MyRegistry(oras.provider.Registry): |
| 12 | + """ |
| 13 | + Oras registry with support for image indexes. |
| 14 | + """ |
| 15 | + |
| 16 | + @decorator.ensure_container |
| 17 | + def get_image_index(self, container, allowed_media_type=None): |
| 18 | + """ |
| 19 | + Get an image index as a manifest. |
| 20 | +
|
| 21 | + This is basically Registry.get_manifest with the following changes |
| 22 | +
|
| 23 | + - different default allowed_media_type |
| 24 | + - no JSON schema validation |
| 25 | + """ |
| 26 | + if not allowed_media_type: |
| 27 | + default_image_index_media_type = "application/vnd.oci.image.index.v1+json" |
| 28 | + allowed_media_type = [default_image_index_media_type] |
| 29 | + |
| 30 | + headers = {"Accept": ";".join(allowed_media_type)} |
| 31 | + |
| 32 | + manifest_url = f"{self.prefix}://{container.manifest_url()}" |
| 33 | + response = self.do_request(manifest_url, "GET", headers=headers) |
| 34 | + self._check_200_response(response) |
| 35 | + manifest = response.json() |
| 36 | + # this would be a good point to validate the schema of the manifest |
| 37 | + # jsonschema.validate(manifest, schema=...) |
| 38 | + return manifest |
| 39 | + |
| 40 | + |
| 41 | +def get_uri_for_digest(uri, digest): |
| 42 | + """ |
| 43 | + Given a URI for an image, return a URI for the related digest. |
| 44 | +
|
| 45 | + URI may be in any of the following forms: |
| 46 | +
|
| 47 | + ghcr.io/homebrew/core/hello |
| 48 | + ghcr.io/homebrew/core/hello:2.10 |
| 49 | + ghcr.io/homebrew/core/hello@sha256:ff81...47a |
| 50 | + """ |
| 51 | + base_uri = re.split(r"[@:]", uri, maxsplit=1)[0] |
| 52 | + return f"{base_uri}@{digest}" |
| 53 | + |
| 54 | + |
| 55 | +def get_image_for_platform(client, uri, download_to, platform_details): |
| 56 | + def matches_platform(manifest): |
| 57 | + platform = manifest.get("platform", {}) |
| 58 | + return all( |
| 59 | + platform.get(key) == requested_value |
| 60 | + for key, requested_value in platform_details.items() |
| 61 | + ) |
| 62 | + |
| 63 | + index_manifest = client.remote.get_image_index(container=uri) |
| 64 | + # use first compatible manifest. YMMV and a tie-breaker may be more suitable |
| 65 | + for manifest in index_manifest["manifests"]: |
| 66 | + if matches_platform(manifest): |
| 67 | + break |
| 68 | + else: |
| 69 | + raise RuntimeError( |
| 70 | + f"No manifest definition matched platform {platform_details}" |
| 71 | + ) |
| 72 | + |
| 73 | + platform_image_uri = get_uri_for_digest(uri, manifest["digest"]) |
| 74 | + client.pull(target=platform_image_uri, outdir=download_to) |
| 75 | + |
| 76 | + |
| 77 | +if __name__ == "__main__": |
| 78 | + client = oras.client.OrasClient(registry=MyRegistry()) |
| 79 | + platform_details = { |
| 80 | + "architecture": "amd64", |
| 81 | + "os": "darwin", |
| 82 | + "os.version": "macOS 10.14", |
| 83 | + } |
| 84 | + get_image_for_platform( |
| 85 | + client, |
| 86 | + "ghcr.io/homebrew/core/hello:2.10", |
| 87 | + download_to="downloads", |
| 88 | + platform_details=platform_details, |
| 89 | + ) |
0 commit comments