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
6 changes: 3 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,10 @@ By default, an exception will be raised if any requests to unmocked addresses ar
.. _httpx: https://pypi.org/project/httpx/
.. _HTTPX2: https://httpx2.pydantic.dev/

Using Docker to mock calls to Vuforia from any language
-------------------------------------------------------
Using containers to mock calls to Vuforia from any language
-----------------------------------------------------------

It is possible run a Mock VWS instance using Docker containers.
You can run a Mock VWS instance using Docker or Apple's ``container`` CLI.

This allows you to run tests against a mock VWS instance regardless of the language or tooling you are using.

Expand Down
46 changes: 46 additions & 0 deletions docs/source/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,52 @@ Give an option once per backend or marker to skip.

.. _pytest-multi-backend: https://adamtheturtle.github.io/pytest-multi-backend/

Container deployment tests
--------------------------

The deployment tests build and run three separate services through the Docker Python SDK.
They use the Docker engine selected by the current Docker configuration by default:

.. code-block:: console

$ uv run --group=dev pytest tests/mock_vws/test_docker.py

.. _socktainer-setup:

To run the same tests with Apple's container runtime, complete the runtime installation steps in :ref:`apple-container-setup`, then install `Socktainer`_ 1.5.1 or later.
It provides the Docker-compatible API used by the Python deployment tests:

.. code-block:: console

$ brew install socktainer
$ socktainer --no-auto-start --no-docker-context

Keep Socktainer running in that terminal.
In another terminal, select its socket and API version for the test command:

.. code-block:: console

$ DOCKER_HOST="unix://$HOME/.socktainer/container.sock" \
DOCKER_API_VERSION=1.51 \
uv run --group=dev pytest tests/mock_vws/test_docker.py

Socktainer 1.5.1 requires the explicit API version because its automatic version negotiation is incompatible with the Docker Python SDK.
This is tracked in `Socktainer issue 433`_.
The deployment tests automatically negotiate the API version when ``DOCKER_API_VERSION`` is unset.

Both configurations exercise native health checks, published HTTP ports, application behavior and restarts.
Running the mock directly with Apple's CLI uses the same images and is documented in :ref:`apple-container-setup`.
The tests bind their published ports to loopback explicitly because Socktainer 1.5.1 ignores automatic port publishing.
See `Socktainer issue 434`_.
Test containers, image tags and networks are removed after the run, including when setup fails.
Hosted CI continues to use Docker.
Apple deployment validation runs on a local Mac.

.. _Socktainer: https://github.com/socktainer/socktainer
.. _Socktainer issue 433: https://github.com/socktainer/socktainer/issues/433
.. _Socktainer issue 434: https://github.com/socktainer/socktainer/issues/434


Verifying signed Model Target requests
--------------------------------------

Expand Down
98 changes: 92 additions & 6 deletions docs/source/docker.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
Running a server with Docker
============================
Running a server in containers
==============================

It is possible run a Mock VWS instance using Docker containers.
You can run a Mock VWS instance using Docker or Apple's `container`_ CLI.
Both use the same published images.

This allows you to run tests against a mock VWS instance regardless of the language or tooling you are using.

Expand All @@ -17,8 +18,11 @@ The VWS and VWQ containers must point to the target manager container using the

.. _creating-containers:

Creating containers
^^^^^^^^^^^^^^^^^^^
Creating containers with Docker
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Docker users can run the commands below directly.
For a deployment using Apple's CLI directly, follow :ref:`apple-container-setup`.

.. code-block:: console

Expand Down Expand Up @@ -48,6 +52,78 @@ Set it to the URL clients use to reach the published VWS port.
For clients on another machine, replace ``http://127.0.0.1:5006`` with an address those clients can reach.


.. _apple-container-setup:

Creating containers with Apple container
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This setup requires a Mac with Apple silicon and macOS 26 or later, Apple's ``container`` CLI 1.5.0 or later, and ``jq`` to read the container's address.
Install the runtime, start its services, and install the recommended Linux kernel:

.. code-block:: console

$ brew install container jq
$ container system start --disable-kernel-install
$ container system kernel set --recommended

Allow Local Network access for ``container-runtime-linux`` when macOS prompts.
If requests to a published port reset, check this permission in System Settings → Privacy & Security → Local Network, then restart the container services.

Create a network and start the target manager first.
Read its IP address on that network for the VWS and VWQ services to use:

.. code-block:: console

$ container network create vws-bridge-network
$ container run \
--detach \
--publish 127.0.0.1:5005:5000 \
--name vuforia-target-manager-mock \
--network vws-bridge-network \
ghcr.io/vws-python/vuforia-target-manager-mock
$ TARGET_MANAGER_IP="$(container inspect vuforia-target-manager-mock | jq -er '.[0].status.networks[0].ipv4Address | split("/")[0]')"
$ export TARGET_MANAGER_BASE_URL="http://$TARGET_MANAGER_IP:5000"
$ container run \
--detach \
--publish 127.0.0.1:5006:5000 \
--name vuforia-vws-mock \
--env "TARGET_MANAGER_BASE_URL=$TARGET_MANAGER_BASE_URL" \
--env VWS_BASE_URL=http://127.0.0.1:5006 \
--network vws-bridge-network \
ghcr.io/vws-python/vuforia-vws-mock
$ container run \
--detach \
--publish 127.0.0.1:5007:5000 \
--name vuforia-vwq-mock \
--env "TARGET_MANAGER_BASE_URL=$TARGET_MANAGER_BASE_URL" \
--network vws-bridge-network \
ghcr.io/vws-python/vuforia-vwq-mock

Using the target manager's IP avoids requiring DNS configuration for container names on this network.
The IP lookup removes the subnet suffix returned by ``container inspect``.
The scheme and container port are fixed, so the URL is constructed separately.
If you recreate the target manager, read its new address and recreate VWS and VWQ with the updated ``TARGET_MANAGER_BASE_URL``.
The host ports match the Docker example, so the HTTP examples below work with either deployment.
``VWS_BASE_URL`` is the URL clients use to reach VWS and download recognition reports.

Run each readiness probe until it exits successfully:

.. code-block:: console

$ container exec vuforia-target-manager-mock python /app/src/mock_vws/_flask_server/healthcheck.py
$ container exec vuforia-vws-mock python /app/src/mock_vws/_flask_server/healthcheck.py
$ container exec vuforia-vwq-mock python /app/src/mock_vws/_flask_server/healthcheck.py

To stop and remove this deployment:

.. code-block:: console

$ container rm --force vuforia-vwq-mock vuforia-vws-mock vuforia-target-manager-mock
$ container network rm vws-bridge-network

.. _container: https://github.com/apple/container


Adding a database to the mock target manager
--------------------------------------------

Expand All @@ -61,7 +137,7 @@ To add a database, make a request to the following endpoint against the target m
.. autoflask:: mock_vws._flask_server.target_manager:TARGET_MANAGER_FLASK_APP
:endpoints: create_cloud_database

For example, with the containers set up as in :ref:`creating-containers`, use ``curl``:
For example, with either deployment above, use ``curl``:

.. code-block:: console

Expand Down Expand Up @@ -200,6 +276,8 @@ VWS container
Building images from source
^^^^^^^^^^^^^^^^^^^^^^^^^^^

These commands build the images with ``docker buildx``.

.. code-block:: console

$ export REPOSITORY_ROOT="$PWD"
Expand All @@ -212,3 +290,11 @@ Building images from source
$ docker buildx build "$REPOSITORY_ROOT" --file "$DOCKERFILE" --target target-manager --tag "$TARGET_MANAGER_TAG"
$ docker buildx build "$REPOSITORY_ROOT" --file "$DOCKERFILE" --target vws --tag "$VWS_TAG"
$ docker buildx build "$REPOSITORY_ROOT" --file "$DOCKERFILE" --target vwq --tag "$VWQ_TAG"

To build the same ``Dockerfile`` stages with Apple's CLI, set the variables above and run:

.. code-block:: console

$ container build "$REPOSITORY_ROOT" --file "$DOCKERFILE" --target target-manager --tag "$TARGET_MANAGER_TAG"
$ container build "$REPOSITORY_ROOT" --file "$DOCKERFILE" --target vws --tag "$VWS_TAG"
$ container build "$REPOSITORY_ROOT" --file "$DOCKERFILE" --target vwq --tag "$VWQ_TAG"
6 changes: 3 additions & 3 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ This requires Python |minimum-python-version|\+.

.. include:: httpx2-example.rst

Using Docker to mock calls to Vuforia from any language
-------------------------------------------------------
Using containers to mock calls to Vuforia from any language
-----------------------------------------------------------

It is possible run a Mock VWS instance using Docker containers.
You can run a Mock VWS instance using Docker or Apple's ``container`` CLI.

This allows you to run tests against a mock VWS instance regardless of the language or tooling you are using.

Expand Down
2 changes: 2 additions & 0 deletions newsfragments/+apple-container.change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Document running and building the mock directly with Apple's container CLI using the same images as Docker.
Support the existing deployment tests through Socktainer's optional Docker API, allow an explicit API version, and bind all test service ports to loopback.
3 changes: 3 additions & 0 deletions spelling_private_dict.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ MyST
OAuth
Pydantic
Reco
Socktainer
Towncrier
Ubuntu
VuMark
Expand Down Expand Up @@ -73,6 +74,7 @@ learnings
linters
linting
login
loopback
macOS
matcher
matcher's
Expand Down Expand Up @@ -126,6 +128,7 @@ rfc
rgb
str
stringify
subnet
subprocess
timestamp
todo
Expand Down
29 changes: 24 additions & 5 deletions tests/mock_vws/test_docker.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import datetime
import io
import json
import os
import socket
import uuid
import zipfile
Expand Down Expand Up @@ -281,10 +282,25 @@ def _vws_client(
)


@beartype
def _docker_client() -> docker.DockerClient:
"""Return a client for the Docker API engine selected by the
environment.

Returns:
A client using the requested API version, or automatic negotiation.
"""
# Socktainer needs an explicit API version until negotiation is fixed:
# https://github.com/socktainer/socktainer/issues/433
return docker.from_env(
version=os.environ.get(key="DOCKER_API_VERSION", default="auto"),
)


@beartype
def _create_bridge_network(*, resources: ExitStack) -> Network:
"""Create a test network and register its removal immediately."""
client = docker.from_env()
client = _docker_client()
name = "test-vws-bridge-" + uuid.uuid4().hex
try:
network = client.networks.create(name=name, driver="bridge")
Expand All @@ -309,7 +325,7 @@ def _build_image(
Returns:
The built image.
"""
client = docker.from_env()
client = _docker_client()
dockerfile = f"{repository_root}/src/mock_vws/_flask_server/Dockerfile"
try:
image, _ = client.images.build(
Expand Down Expand Up @@ -371,7 +387,7 @@ def fixture_mock_deployment() -> Iterator[_MockDeployment]:
start=Path(__file__).resolve(),
)
)
client = docker.from_env()
client = _docker_client()
random = uuid.uuid4().hex

with ExitStack() as resources:
Expand Down Expand Up @@ -404,10 +420,13 @@ def fixture_mock_deployment() -> Iterator[_MockDeployment]:
vws_host_port = _free_port()
base_vws_url = f"http://127.0.0.1:{vws_host_port}"

# Target-manager and VWQ need explicit ports while Socktainer ignores
# publish_all_ports=True:
# https://github.com/socktainer/socktainer/issues/434
target_manager_container = client.containers.create(
image=target_manager_image,
name=target_manager_container_name,
publish_all_ports=True,
ports={"5000/tcp": ("127.0.0.1", _free_port())},
network=custom_bridge_network.name,
)
_start_container(
Expand All @@ -429,7 +448,7 @@ def fixture_mock_deployment() -> Iterator[_MockDeployment]:
vwq_container = client.containers.create(
image=vwq_image,
name="vws-mock-vwq-" + random,
publish_all_ports=True,
ports={"5000/tcp": ("127.0.0.1", _free_port())},
network=custom_bridge_network.name,
environment={
"TARGET_MANAGER_BASE_URL": target_manager_internal_base_url,
Expand Down
Loading