Пример #1
0
def v1_manifest_to_v2(
    manifest: om.OciImageManifestV1,
    oci_client: oc.Client,
    tgt_image_ref: str,
) -> om.OciImageManifest:
    docker_cfg = v2_cfg_from_v1_manifest(manifest=manifest)
    docker_cfg = dataclasses.asdict(docker_cfg)
    docker_cfg = json.dumps(docker_cfg).encode('utf-8')

    cfg_digest = f'sha256:{hashlib.sha256(docker_cfg).hexdigest()}'
    cfg_leng = len(docker_cfg)

    oci_client.put_blob(
        image_reference=tgt_image_ref,
        digest=cfg_digest,
        octets_count=cfg_leng,
        data=docker_cfg,
    )

    manifest_v2 = om.OciImageManifest(
        config=om.OciBlobRef(
            digest=cfg_digest,
            mediaType='application/vnd.docker.container.image.v1+json',
            size=cfg_leng,
        ),
        layers=manifest.layers,
    )

    return manifest_v2, docker_cfg
Пример #2
0
            def fs_layer_to_oci_blob_ref(fs_layer: om.OciBlobRefV1):
                digest = fs_layer.blobSum

                res = self._request(
                    url=blob_url(image_reference=image_reference,
                                 digest=digest),
                    image_reference=image_reference,
                    scope=scope,
                    method='HEAD',
                    stream=False,
                    timeout=None,
                )
                return om.OciBlobRef(
                    digest=digest,
                    mediaType=res.headers['Content-Type'],
                    size=int(res.headers['Content-Length']),
                )
Пример #3
0
    def head_manifest(
        self,
        image_reference: str,
        absent_ok=False,
    ) -> typing.Optional[om.OciBlobRef]:
        '''
        issues an HTTP-HEAD request for the specified oci-artifact's manifest and returns
        the thus-retrieved metadata if it exists.

        Note that the hash digest may be absent, or incorrect, as defined by the OCI
        distribution-spec.

        if `absent_ok` is truthy, `None` is returned in case the requested manifest does not
        exist; otherwise, requests.exceptions.HTTPError is raised in this case.

        To retrieve the actual manifest, use `self.manifest` or `self.manifest_raw`
        '''
        scope = _scope(image_reference=image_reference, action='pull')

        res = self._request(
            url=self.routes.manifest_url(image_reference=image_reference),
            image_reference=image_reference,
            method='HEAD',
            scope=scope,
            stream=False,
            raise_for_status=not absent_ok,
            warn_if_not_ok=not absent_ok,
        )
        if not res.ok and absent_ok:
            return None

        headers = res.headers

        # XXX Docker-Content-Digest header may be absent or incorrect
        # -> it would be preferrable to retrieve the manifest and calculate the hash manually

        return om.OciBlobRef(
            digest=headers.get('Docker-Content-Digest', None),
            mediaType=headers['Content-Type'],
            size=int(headers['Content-Length']),
        )
Пример #4
0
                digest = f'sha256:{digest.hexdigest()}'
                octets_count = blob_overwrite_bytes.tell()
                blob_overwrite_bytes.seek(0)
            else:
                digest = f'sha256:{hashlib.sha256(blob_overwrite_bytes).hexdigest()}'
                octets_count = len(blob_overwrite_bytes)

            oci_client.put_blob(
                image_reference=tgt_ref,
                digest=digest,
                octets_count=octets_count,
                data=blob_overwrite_bytes,
            )
            return om.OciBlobRef(
                digest=digest,
                mediaType=blob.mediaType,  #XXX: pass-in new media type?
                size=octets_count,
            )
        else:
            digest = blob.digest

            src_blob: requests.models.Response = oci_client.blob(
                image_reference=src_ref,
                digest=digest,
            )

            octets_count = int(src_blob.headers['Content-Length'])

            oci_client.put_blob(
                image_reference=tgt_ref,
                digest=digest,
Пример #5
0
    def manifest(
        self,
        image_reference: typing.Union[str, om.OciImageReference],
        absent_ok: bool=False,
        accept: str=None,
    ) -> typing.Union[om.OciImageManifest, om.OciImageManifestList]:
        '''
        returns the parsed OCI Manifest for the given image reference. If the optional `accept`
        argument is passed, the given value will be set as `Accept` HTTP Header when retrieving
        the manifest (defaults to
            application/vnd.docker.distribution.manifest.list.v2+json,
            application/vnd.docker.distribution.manifest.v2+json
        , which requests a single Oci Image manifest, with a preference for the mimetype defined
        by OCI, and accepting docker's mimetype as a fallback)

        The following mimetype is also well-known:

            application/vnd.oci.image.manifest.v1+json

        If set, and the underlying OCI Artifact is a "multi-arch" artifact, than the returned
        value is (parsed into) a OciImageManifestList.

        see oci.model for both mimetype and model class definitions.

        Note that in case no `accept` header is set, the returned manifest type differs depending
        on the OCI Registry, if there actually is a multi-arch artifact.

        GCR is known to return a single OCI Image manifest (defaulting to GNU/Linux x86_64),
        whereas the registry backing quay.io will return a Manifest-List regardless of accept
        header.
        '''
        image_reference = om.OciImageReference.to_image_ref(image_reference)
        res = self.manifest_raw(
            image_reference=image_reference,
            absent_ok=absent_ok,
            accept=accept,
        )

        if not res and absent_ok:
            return None

        manifest_dict = res.json()

        distribution_list_mediatype = 'application/vnd.docker.distribution.manifest.list.v2+json'
        if manifest_dict.get('mediaType') == distribution_list_mediatype:
            manifest = dacite.from_dict(
                data_class=om.OciImageManifestList,
                data=manifest_dict,
            )
            return manifest

        if (schema_version := int(manifest_dict['schemaVersion'])) == 1:
            manifest = dacite.from_dict(
                data_class=om.OciImageManifestV1,
                data=manifest_dict,
            )
            scope = _scope(image_reference=image_reference, action='pull')

            def fs_layer_to_oci_blob_ref(fs_layer: om.OciBlobRefV1):
                digest = fs_layer.blobSum

                res = self._request(
                    url=self.routes.blob_url(image_reference=image_reference, digest=digest),
                    image_reference=image_reference,
                    scope=scope,
                    method='HEAD',
                    stream=False,
                    timeout=None,
                )
                if (mediaType := res.headers['Content-Type']) == 'application/octet-stream':
                    # legacy container-images declare application/octet-stream as content-type,
                    # which is not supported by containerd
                    # https://github.com/containerd/containerd/pull/2456#issuecomment-406478687
                    mediaType = 'application/vnd.docker.image.rootfs.diff.tar.gzip'
                return om.OciBlobRef(
                    digest=digest,
                    mediaType=mediaType,
                    size=int(res.headers['Content-Length']),
                )
Пример #6
0
            warn_if_not_ok=not absent_ok,
        )
        if not res.ok and absent_ok:
            return None

        headers = res.headers

        # XXX Docker-Content-Digest header may be absent or incorrect
        # -> it would be preferrable to retrieve the manifest and calculate the hash manually

        if size := headers.get('Content-Length'):
            size = int(size)

        return om.OciBlobRef(
            digest=headers.get('Docker-Content-Digest', None),
            mediaType=headers['Content-Type'],
            size=size,
        )

    def to_digest_hash(self, image_reference: str, accept: str=None):
        image_reference = om.OciImageReference.to_image_ref(image_reference)
        if image_reference.has_digest_tag:
            return str(image_reference)

        manifest_hash_digest = hashlib.sha256(
            self.manifest_raw(
                image_reference=image_reference,
                accept=accept,
            ).content
        ).hexdigest()
Пример #7
0
    raw_fobj = gci.oci.component_descriptor_to_tarfileobj(patched_component_descriptor)

    cd_digest = hashlib.sha256()
    while (chunk := raw_fobj.read(4096)):
        cd_digest.update(chunk)

    cd_octets = raw_fobj.tell()
    cd_digest = cd_digest.hexdigest()
    cd_digest_with_alg = f'sha256:{cd_digest}'
    raw_fobj.seek(0)

    # src component descriptor OciBlobRef for patching
    src_config_dict = json.loads(client.blob(src_ref, src_manifest.config.digest).content)
    src_component_descriptor_oci_blob_ref = om.OciBlobRef(
        **src_config_dict['componentDescriptorLayer'],
    )

    # config OciBlobRef
    cfg = gci.oci.ComponentDescriptorOciCfg(
        componentDescriptorLayer=gci.oci.ComponentDescriptorOciBlobRef(
            digest=cd_digest_with_alg,
            size=cd_octets,
        ),
    )
    cfg_raw = json.dumps(dataclasses.asdict(cfg)).encode('utf-8')

    # replicate all blobs except overwrites
    target_manifest = oci.replicate_blobs(
        src_ref=src_ref,
        src_oci_manifest=src_manifest,