7
0
mirror of https://github.com/ChristianLight/tutor.git synced 2024-06-01 22:00:48 +00:00
tutor/tutor/bindmount.py
Régis Behmo 18ce1f2fe4 feat: persistent bind-mounts
This is an important change, where we get remove the previous `--mount`
option, and instead opt for persistent bind-mounts.

Persistent bind mounts have several advantages:
- They make it easier to remember which folders need to be bind-mounted.
- Code is *much* less clunky, as we no longer need to generate temporary
  docker-compose files.
- They allow us to bind-mount host directories *at build time* using the
  buildx `--build-context` option.
- The transition from development to production becomes much easier, as
  images will automatically be built using the host repo.

The only drawback is that persistent bind-mounts are slightly less
portable: when a config.yml file is moved to a different folder, many
things will break if the repo is not checked out in the same path.

For instance, this is how to start working on a local fork of
edx-platform:

    tutor config save --append MOUNTS=/path/to/edx-platform

And that's all there is to it. No, this fork will be used whenever we
run:

    tutor images build openedx
    tutor local start
    tutor dev start

This change is made possible by huge improvements in the build time
performance. These improvements make it convenient to re-build Docker
images often.

Related issues:
https://github.com/openedx/wg-developer-experience/issues/71
https://github.com/openedx/wg-developer-experience/issues/66
https://github.com/openedx/wg-developer-experience/issues/166
2023-06-14 21:08:49 +02:00

72 lines
2.3 KiB
Python

from __future__ import annotations
from functools import lru_cache
import os
import re
import typing as t
from tutor import hooks
def iter_mounts(user_mounts: list[str], name: str) -> t.Iterable[str]:
"""
Iterate on the bind-mounts that are available to any given compose service. The list
of bind-mounts is parsed from `user_mounts` and we yield only those for service
`name`.
Calling this function multiple times makes repeated calls to the parsing functions,
but that's OK because their result is cached.
"""
for user_mount in user_mounts:
for service, host_path, container_path in parse_mount(user_mount):
if service == name:
yield f"{host_path}:{container_path}"
def parse_mount(value: str) -> list[tuple[str, str, str]]:
"""
Parser for mount arguments of the form "service1[,service2,...]:/host/path:/container/path".
Returns a list of (service, host_path, container_path) tuples.
"""
mounts = parse_explicit_mount(value) or parse_implicit_mount(value)
return mounts
@lru_cache(maxsize=None)
def parse_explicit_mount(value: str) -> list[tuple[str, str, str]]:
"""
Argument is of the form "containers:/host/path:/container/path".
"""
# Note that this syntax does not allow us to include colon ':' characters in paths
match = re.match(
r"(?P<services>[a-zA-Z0-9-_, ]+):(?P<host_path>[^:]+):(?P<container_path>[^:]+)",
value,
)
if not match:
return []
mounts: list[tuple[str, str, str]] = []
services: list[str] = [service.strip() for service in match["services"].split(",")]
host_path = os.path.abspath(os.path.expanduser(match["host_path"]))
host_path = host_path.replace(os.path.sep, "/")
container_path = match["container_path"]
for service in services:
if service:
mounts.append((service, host_path, container_path))
return mounts
@lru_cache(maxsize=None)
def parse_implicit_mount(value: str) -> list[tuple[str, str, str]]:
"""
Argument is of the form "/host/path"
"""
mounts: list[tuple[str, str, str]] = []
host_path = os.path.abspath(os.path.expanduser(value))
for service, container_path in hooks.Filters.COMPOSE_MOUNTS.iterate(
os.path.basename(host_path)
):
mounts.append((service, host_path, container_path))
return mounts