7
0
mirror of https://github.com/ChristianLight/tutor.git synced 2024-05-30 12:50:48 +00:00
tutor/tutor/bindmounts.py
Régis Behmo d9486018a2 feat: add --mount option to local/dev
The `--mount` option is available both with `tutor local`
and `tutor dev` commands. It allows users to easily bind-mount containers from
the host to containers. Yes, I know, we already provide that possibility with
the `bindmount` command and the `--volume=/path/` option. But these suffer from
the following drawbacks:

- They are difficult to understand.
- The "bindmount" command name does not make much sense.
- It's not convenient to mount an arbitrary folder from the host to multiple
  containers, such as the many lms/cms containers (web apps, celery workers and
  job runners).

To address this situation, we now recommend to make use of --mount:

1. `--mount=service1[,service2,...]:/host/path:/container/path`: manually mount
   `/host/path` to `/container/path` in container "service1" (and "service2").
2. `--mount=/host/path`: use the new v1 plugin API to discover plugins that
   will detect this option and select the right containers in which to bind-mount
   volumes. This is really nifty...

Close https://github.com/overhangio/2u-tutor-adoption/issues/43
2022-04-20 19:33:17 +02:00

84 lines
2.5 KiB
Python

import os
from typing import List, Tuple
import click
from .exceptions import TutorError
from .jobs import BaseComposeJobRunner
from .utils import get_user_id
def create(
runner: BaseComposeJobRunner,
service: str,
path: str,
) -> str:
volumes_root_path = get_root_path(runner.root)
volume_name = get_name(path)
container_volumes_root_path = "/tmp/volumes"
command = """rm -rf {volumes_path}/{volume_name}
cp -r {src_path} {volumes_path}/{volume_name}
chown -R {user_id} {volumes_path}/{volume_name}""".format(
volumes_path=container_volumes_root_path,
volume_name=volume_name,
src_path=path,
user_id=get_user_id(),
)
# Create volumes root dir if it does not exist. Otherwise it is created with root owner and might not be writable
# in the container, e.g: in the dev containers.
if not os.path.exists(volumes_root_path):
os.makedirs(volumes_root_path)
runner.docker_compose(
"run",
"--rm",
"--no-deps",
"--user=0",
"--volume",
f"{volumes_root_path}:{container_volumes_root_path}",
service,
"sh",
"-e",
"-c",
command,
)
return os.path.join(volumes_root_path, volume_name)
def get_path(root: str, container_bind_path: str) -> str:
bind_basename = get_name(container_bind_path)
return os.path.join(get_root_path(root), bind_basename)
def get_name(container_bind_path: str) -> str:
# We rstrip slashes, otherwise os.path.basename returns an empty string
# We don't use basename here as it will not work on Windows
name = container_bind_path.rstrip("/").split("/")[-1]
if not name:
raise TutorError("Mounting a container root folder is not supported")
return name
def get_root_path(root: str) -> str:
return os.path.join(root, "volumes")
def parse_volumes(docker_compose_args: List[str]) -> Tuple[List[str], List[str]]:
"""
Parse `-v/--volume` options from an arbitrary list of arguments.
"""
@click.command(context_settings={"ignore_unknown_options": True})
@click.option("-v", "--volume", "volumes", multiple=True)
@click.argument("args", nargs=-1)
def custom_docker_compose(
volumes: List[str], args: List[str] # pylint: disable=unused-argument
) -> None:
pass
if isinstance(docker_compose_args, tuple):
docker_compose_args = list(docker_compose_args)
context = custom_docker_compose.make_context("custom", docker_compose_args)
return context.params["volumes"], context.params["args"]