From 82e049fcec44db30d3f7c90f05392e62454158b6 Mon Sep 17 00:00:00 2001 From: YuviPanda Date: Thu, 27 Feb 2025 20:49:09 -0800 Subject: [PATCH] Add a docker registry integration test with real registry --- .github/workflows/test.yml | 14 +-------- repo2docker/app.py | 15 ++-------- repo2docker/utils.py | 12 ++++++++ tests/registry/Dockerfile | 2 ++ tests/registry/test_registry.py | 50 +++++++++++++++++++++++++++++++++ tests/unit/test_docker.py | 40 -------------------------- 6 files changed, 67 insertions(+), 66 deletions(-) create mode 100644 tests/registry/Dockerfile create mode 100644 tests/registry/test_registry.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 924b23164..07bfc368f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -65,6 +65,7 @@ jobs: - unit - venv - contentproviders + - registry # Playwright test - ui include: @@ -73,22 +74,9 @@ jobs: python_version: "3.9" repo_type: venv - services: - # So that we can test this in PRs/branches - local-registry: - image: registry:2 - ports: - - 5000:5000 - steps: - uses: actions/checkout@v4 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - with: - # Allows pushing to registry on localhost:5000 - driver-opts: network=host - - uses: actions/setup-python@v5 with: python-version: "${{ matrix.python_version }}" diff --git a/repo2docker/app.py b/repo2docker/app.py index 0d8174525..418ef12d7 100755 --- a/repo2docker/app.py +++ b/repo2docker/app.py @@ -37,7 +37,7 @@ RBuildPack, ) from .engine import BuildError, ContainerEngineException, ImageLoadError -from .utils import ByteSpecification, R2dState, chdir, get_platform +from .utils import ByteSpecification, R2dState, chdir, get_platform, get_free_port class Repo2Docker(Application): @@ -660,7 +660,7 @@ def start_container(self): container_port = int(container_port_proto.split("/", 1)[0]) else: # no port specified, pick a random one - container_port = host_port = str(self._get_free_port()) + container_port = host_port = str(get_free_port()) self.ports = {f"{container_port}/tcp": host_port} self.port = host_port # To use the option --NotebookApp.custom_display_url @@ -744,17 +744,6 @@ def wait_for_container(self, container): if exit_code: sys.exit(exit_code) - def _get_free_port(self): - """ - Hacky method to get a free random port on local host - """ - import socket - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.bind(("", 0)) - port = s.getsockname()[1] - s.close() - return port def find_image(self): # if this is a dry run it is Ok for dockerd to be unreachable so we diff --git a/repo2docker/utils.py b/repo2docker/utils.py index 9c2769e1d..ebae3da96 100644 --- a/repo2docker/utils.py +++ b/repo2docker/utils.py @@ -1,4 +1,5 @@ import os +import socket import platform import re import subprocess @@ -545,3 +546,14 @@ def get_platform(): else: warnings.warn(f"Unexpected platform '{m}', defaulting to linux/amd64") return "linux/amd64" + + +def get_free_port(): + """ + Hacky method to get a free random port on local host + """ + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("", 0)) + port = s.getsockname()[1] + s.close() + return port \ No newline at end of file diff --git a/tests/registry/Dockerfile b/tests/registry/Dockerfile new file mode 100644 index 000000000..bd083ea7f --- /dev/null +++ b/tests/registry/Dockerfile @@ -0,0 +1,2 @@ +# Smallest possible dockerfile, used only for building images to be tested +FROM scratch \ No newline at end of file diff --git a/tests/registry/test_registry.py b/tests/registry/test_registry.py new file mode 100644 index 000000000..03b005662 --- /dev/null +++ b/tests/registry/test_registry.py @@ -0,0 +1,50 @@ +from pathlib import Path +import subprocess +import pytest +from repo2docker.__main__ import make_r2d +from repo2docker.utils import get_free_port +import time +import requests +import secrets + +HERE = Path(__file__).parent + +@pytest.fixture +def registry(): + port = get_free_port() + cmd = [ + "docker", "run", "-it", "-p", f"{port}:5000", "registry:3.0.0-rc.3" + ] + proc = subprocess.Popen(cmd) + health_url = f'http://localhost:{port}/v2' + # Wait for the registry to actually come up + for i in range(10): + try: + resp = requests.get(health_url) + if resp.status_code in (401, 200): + break + except requests.ConnectionError: + # The service is not up yet + pass + time.sleep(i) + else: + raise TimeoutError("Test registry did not come up in time") + + try: + yield f"localhost:{port}" + finally: + proc.terminate() + proc.wait() + + +def test_registry(registry): + image_name = f"{registry}/{secrets.token_hex(8)}:latest" + r2d = make_r2d([ + "--image", image_name, + "--push", "--no-run", str(HERE) + ]) + + r2d.start() + + proc = subprocess.run(["docker", "manifest", "inspect", "--insecure", image_name]) + assert proc.returncode == 0 diff --git a/tests/unit/test_docker.py b/tests/unit/test_docker.py index c47aa4fa0..a41bafa3a 100644 --- a/tests/unit/test_docker.py +++ b/tests/unit/test_docker.py @@ -22,43 +22,3 @@ def test_git_credential_env(): .strip() ) assert out == credential_env - - -class MockDockerEngine(DockerEngine): - def __init__(self, *args, **kwargs): - self._apiclient = Mock() - - -def test_docker_push_no_credentials(): - engine = MockDockerEngine() - - engine.push("image") - - assert len(engine._apiclient.method_calls) == 1 - engine._apiclient.push.assert_called_once_with("image", stream=True) - - -def test_docker_push_dict_credentials(): - engine = MockDockerEngine() - engine.registry_credentials = {"username": "abc", "password": "def"} - - engine.push("image") - - assert len(engine._apiclient.method_calls) == 2 - engine._apiclient.login.assert_called_once_with(username="abc", password="def") - engine._apiclient.push.assert_called_once_with("image", stream=True) - - -def test_docker_push_env_credentials(): - engine = MockDockerEngine() - with patch.dict( - "os.environ", - { - "CONTAINER_ENGINE_REGISTRY_CREDENTIALS": '{"username": "abc", "password": "def"}' - }, - ): - engine.push("image") - - assert len(engine._apiclient.method_calls) == 2 - engine._apiclient.login.assert_called_once_with(username="abc", password="def") - engine._apiclient.push.assert_called_once_with("image", stream=True)