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 Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -707,7 +707,7 @@ local-registry: .env ## creates a local docker registry and configure simcore to
--publish 5000:5000 \
--volume $(LOCAL_REGISTRY_VOLUME):/var/lib/registry \
--name $(LOCAL_REGISTRY_HOSTNAME) \
registry:2)
registry:3)

# WARNING: environment file .env is now setup to use local registry on port 5000 without any security (take care!)...
@echo REGISTRY_AUTH=False >> .env
Expand Down
38 changes: 24 additions & 14 deletions packages/pytest-simcore/src/pytest_simcore/docker_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,35 +22,45 @@

from .helpers.host import get_localhost_ip

log = logging.getLogger(__name__)
_logger = logging.getLogger(__name__)


@pytest.fixture(scope="session")
def docker_registry(keep_docker_up: bool) -> Iterator[str]:
"""sets up and runs a docker registry container locally and returns its URL"""
yield from _docker_registry_impl(keep_docker_up, registry_version="3")


@pytest.fixture(scope="session")
def docker_registry_v2() -> Iterator[str]:
"""sets up and runs a docker registry v2 container locally and returns its URL"""
yield from _docker_registry_impl(keep_docker_up=False, registry_version="2")


def _docker_registry_impl(keep_docker_up: bool, registry_version: str) -> Iterator[str]:
"""sets up and runs a docker registry container locally and returns its URL"""
# run the registry outside of the stack
docker_client = docker.from_env()
# try to login to private registry
host = "127.0.0.1"
port = 5000
port = 5000 if registry_version == "3" else 5001
url = f"{host}:{port}"
container_name = f"pytest_registry_v{registry_version}"
volume_name = f"pytest_registry_v{registry_version}_data"

container = None
try:
docker_client.login(registry=url, username="simcore")
container = docker_client.containers.list(filters={"name": "pytest_registry"})[
0
]
container = docker_client.containers.list(filters={"name": container_name})[0]
print("Warning: docker registry is already up!")
except Exception: # pylint: disable=broad-except
container = docker_client.containers.run(
"registry:2",
ports={"5000": "5000"},
name="pytest_registry",
f"registry:{registry_version}",
ports={"5000": port},
name=container_name,
environment=["REGISTRY_STORAGE_DELETE_ENABLED=true"],
restart_policy={"Name": "always"},
volumes={
"pytest_registry_data": {"bind": "/var/lib/registry", "mode": "rw"}
},
volumes={volume_name: {"bind": "/var/lib/registry", "mode": "rw"}},
detach=True,
)

Expand Down Expand Up @@ -79,9 +89,9 @@ def docker_registry(keep_docker_up: bool) -> Iterator[str]:
os.environ["REGISTRY_SSL"] = "False"
os.environ["REGISTRY_AUTH"] = "False"
# the registry URL is how to access from the container (e.g. for accessing the API)
os.environ["REGISTRY_URL"] = f"{get_localhost_ip()}:5000"
os.environ["REGISTRY_URL"] = f"{get_localhost_ip()}:{port}"
# the registry PATH is how the docker engine shall access the images (usually same as REGISTRY_URL but for testing)
os.environ["REGISTRY_PATH"] = "127.0.0.1:5000"
os.environ["REGISTRY_PATH"] = f"127.0.0.1:{port}"
os.environ["REGISTRY_USER"] = "simcore"
os.environ["REGISTRY_PW"] = ""

Expand Down Expand Up @@ -124,7 +134,7 @@ def registry_settings(
@tenacity.retry(
wait=tenacity.wait_fixed(2),
stop=tenacity.stop_after_delay(20),
before_sleep=tenacity.before_sleep_log(log, logging.INFO),
before_sleep=tenacity.before_sleep_log(_logger, logging.INFO),
reraise=True,
)
def wait_till_registry_is_responsive(url: str) -> bool:
Expand Down
11 changes: 0 additions & 11 deletions scripts/metrics/Makefile

This file was deleted.

144 changes: 0 additions & 144 deletions scripts/metrics/compute_list_of_images_in_registry.py

This file was deleted.

5 changes: 0 additions & 5 deletions scripts/metrics/requirements.txt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ class ServiceNotAvailableError(DirectorRuntimeError):
msg_template: str = "Service {service_name}:{service_tag} is not available"


class DockerRegistryUnsupportedManifestSchemaVersionError(DirectorRuntimeError):
msg_template: str = (
"Docker registry schema version {version} issue with {service_name}:{service_tag}"
)


class ServiceUUIDNotFoundError(DirectorRuntimeError):
msg_template: str = "Service with uuid {service_uuid} was not found"

Expand Down
62 changes: 48 additions & 14 deletions services/director/src/simcore_service_director/registry_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from .constants import DIRECTOR_SIMCORE_SERVICES_PREFIX
from .core.errors import (
DirectorRuntimeError,
DockerRegistryUnsupportedManifestSchemaVersionError,
RegistryConnectionError,
ServiceNotAvailableError,
)
Expand Down Expand Up @@ -375,22 +376,55 @@ async def get_image_labels(
app: FastAPI, image: str, tag: str, *, update_cache=False
) -> tuple[dict[str, str], str | None]:
"""Returns image labels and the image manifest digest"""
with log_context(_logger, logging.DEBUG, msg=f"get {image}:{tag} labels"):
request_result, headers = await registry_request(
app,
path=f"{image}/manifests/{tag}",
method="GET",
use_cache=not update_cache,
)

_logger.debug("getting image labels of %s:%s", image, tag)
path = f"{image}/manifests/{tag}"
request_result, headers = await registry_request(
app, path=path, method="GET", use_cache=not update_cache
)
v1_compatibility_key = json_loads(request_result["history"][0]["v1Compatibility"])
container_config: dict[str, Any] = v1_compatibility_key.get(
"container_config", v1_compatibility_key["config"]
)
labels: dict[str, str] = container_config["Labels"]

headers = headers or {}
manifest_digest: str | None = headers.get(_DOCKER_CONTENT_DIGEST_HEADER, None)
schema_version = request_result["schemaVersion"]
labels: dict[str, str] = {}
match schema_version:
case 2:
# Image Manifest Version 2, Schema 2 -> defaults in registries v3 (https://distribution.github.io/distribution/spec/manifest-v2-2/)
media_type = request_result["mediaType"]
if (
media_type
== "application/vnd.docker.distribution.manifest.list.v2+json"
):
raise DockerRegistryUnsupportedManifestSchemaVersionError(
version=schema_version,
service_name=image,
service_tag=tag,
reason="Multiple architectures images are currently not supported and need to be implemented",
)
config_digest = request_result["config"]["digest"]
# Fetch the config blob
config_result, _ = await registry_request(
app,
path=f"{image}/blobs/{config_digest}",
method="GET",
use_cache=not update_cache,
)
labels = config_result.get("config", {}).get("Labels", {})
case 1:
# Image Manifest Version 2, Schema 1 deprecated in docker hub since 2024-11-04
v1_compatibility_key = json_loads(
request_result["history"][0]["v1Compatibility"]
)
container_config: dict[str, Any] = v1_compatibility_key.get(
"container_config", v1_compatibility_key.get("config", {})
)
labels = container_config.get("Labels", {})
case _:
raise DockerRegistryUnsupportedManifestSchemaVersionError(
version=schema_version, service_name=image, service_tag=tag
)

_logger.debug("retrieved labels of image %s:%s", image, tag)
headers = headers or {}
manifest_digest: str | None = headers.get(_DOCKER_CONTENT_DIGEST_HEADER, None)

return (labels, manifest_digest)

Expand Down
Loading
Loading