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
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']), )
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']), )
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,
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']), )
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()
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,