Source code for oras.container
__author__ = "Vanessa Sochat"
__copyright__ = "Copyright The ORAS Authors."
__license__ = "Apache-2.0"
import re
from typing import Optional
import oras.defaults
docker_regex = re.compile(
"(?:(?P<registry>[^/@]+[.:][^/@]*)/)?"
"(?P<namespace>(?:[^:@/]+/)+)?"
"(?P<repository>[^:@/]+)"
"(?::(?P<tag>[^:@]+))?"
"(?:@(?P<digest>.+))?"
"$"
)
[docs]
class Container:
def __init__(self, name: str, registry: Optional[str] = None):
"""
Parse a container name and easily get urls for registry interactions.
:param name: the full name of the container to parse (with any components)
:type name: str
:param registry: a custom registry name, if not provided with URI
:type registry: str
"""
self.registry = registry or oras.defaults.registry.index_name
# Registry is the name takes precendence
self.parse(name)
@property
def api_prefix(self):
"""
Return the repository prefix for the v2 API endpoints.
"""
if self.namespace:
return f"{self.namespace}/{self.repository}"
return self.repository
[docs]
def get_blob_url(self, digest: str) -> str:
"""
Get the URL to download a blob
:param digest: the digest to download
:type digest: str
"""
return f"{self.registry}/v2/{self.api_prefix}/blobs/{digest}"
[docs]
def upload_blob_url(self) -> str:
return f"{self.registry}/v2/{self.api_prefix}/blobs/uploads/"
[docs]
def manifest_url(self, tag: Optional[str] = None) -> str:
"""
Get the manifest url for a specific tag, or the one for this container.
The tag provided can also correspond to a digest.
:param tag: an optional tag to provide (if not provided defaults to container)
:type tag: None or str
"""
# an explicitly defined tag has precedence over everything,
# but from the already defined ones, prefer the digest for consistency.
tag = tag or (self.digest or self.tag)
return f"{self.registry}/v2/{self.api_prefix}/manifests/{tag}"
def __str__(self) -> str:
return self.uri
@property
def uri(self) -> str:
"""
Assemble the complete unique resource identifier
"""
if self.namespace:
uri = f"{self.namespace}/{self.repository}"
else:
uri = f"{self.repository}"
if self.registry:
uri = f"{self.registry}/{uri}"
# Digest takes preference because more specific
if self.digest:
uri = f"{uri}@{self.digest}"
elif self.tag:
uri = f"{uri}:{self.tag}"
return uri
[docs]
def parse(self, name: str):
"""
Parse the container name into registry, repository, and tag.
:param name: the full name of the container to parse (with any components)
:type name: str
"""
match = re.search(docker_regex, name)
if not match:
raise ValueError(
f"{name} does not match a recognized registry unique resource identifier. Try <registry>/<namespace>/<repository>:<tag|digest>"
)
items = match.groupdict() # type: ignore
self.repository = items["repository"]
self.registry = items["registry"] or self.registry
self.namespace = items["namespace"]
self.tag = items["tag"] or oras.defaults.default_tag
self.digest = items["digest"]
# Repository is required
if not self.repository:
raise ValueError(
"You are minimally required to include a <namespace>/<repository>"
)
if self.namespace:
self.namespace = self.namespace.strip("/")