kopia lustrzana https://github.com/jupyterhub/repo2docker
Add a docker registry integration test with real registry
rodzic
364a18932c
commit
82e049fcec
|
@ -65,6 +65,7 @@ jobs:
|
||||||
- unit
|
- unit
|
||||||
- venv
|
- venv
|
||||||
- contentproviders
|
- contentproviders
|
||||||
|
- registry
|
||||||
# Playwright test
|
# Playwright test
|
||||||
- ui
|
- ui
|
||||||
include:
|
include:
|
||||||
|
@ -73,22 +74,9 @@ jobs:
|
||||||
python_version: "3.9"
|
python_version: "3.9"
|
||||||
repo_type: venv
|
repo_type: venv
|
||||||
|
|
||||||
services:
|
|
||||||
# So that we can test this in PRs/branches
|
|
||||||
local-registry:
|
|
||||||
image: registry:2
|
|
||||||
ports:
|
|
||||||
- 5000:5000
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "${{ matrix.python_version }}"
|
python-version: "${{ matrix.python_version }}"
|
||||||
|
|
|
@ -37,7 +37,7 @@ from .buildpacks import (
|
||||||
RBuildPack,
|
RBuildPack,
|
||||||
)
|
)
|
||||||
from .engine import BuildError, ContainerEngineException, ImageLoadError
|
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):
|
class Repo2Docker(Application):
|
||||||
|
@ -660,7 +660,7 @@ class Repo2Docker(Application):
|
||||||
container_port = int(container_port_proto.split("/", 1)[0])
|
container_port = int(container_port_proto.split("/", 1)[0])
|
||||||
else:
|
else:
|
||||||
# no port specified, pick a random one
|
# 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.ports = {f"{container_port}/tcp": host_port}
|
||||||
self.port = host_port
|
self.port = host_port
|
||||||
# To use the option --NotebookApp.custom_display_url
|
# To use the option --NotebookApp.custom_display_url
|
||||||
|
@ -744,17 +744,6 @@ class Repo2Docker(Application):
|
||||||
if exit_code:
|
if exit_code:
|
||||||
sys.exit(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):
|
def find_image(self):
|
||||||
# if this is a dry run it is Ok for dockerd to be unreachable so we
|
# if this is a dry run it is Ok for dockerd to be unreachable so we
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
@ -545,3 +546,14 @@ def get_platform():
|
||||||
else:
|
else:
|
||||||
warnings.warn(f"Unexpected platform '{m}', defaulting to linux/amd64")
|
warnings.warn(f"Unexpected platform '{m}', defaulting to linux/amd64")
|
||||||
return "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
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Smallest possible dockerfile, used only for building images to be tested
|
||||||
|
FROM scratch
|
|
@ -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
|
|
@ -22,43 +22,3 @@ def test_git_credential_env():
|
||||||
.strip()
|
.strip()
|
||||||
)
|
)
|
||||||
assert out == credential_env
|
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)
|
|
||||||
|
|
Ładowanie…
Reference in New Issue