]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
container tooling: poe run now runs in a container, containers no longer include...
authorVasek Sraier <git@vakabus.cz>
Fri, 26 Mar 2021 12:17:39 +0000 (13:17 +0100)
committerAleš Mrázek <ales.mrazek@nic.cz>
Fri, 8 Apr 2022 14:17:52 +0000 (16:17 +0200)
15 files changed:
manager/containers/debian/Containerfile
manager/containers/dev/Containerfile
manager/integration/runner.py
manager/integration/tests/basic_startup/test.toml
manager/integration/tests/worker_count/test.toml
manager/knot_resolver_manager/__main__.py
manager/knot_resolver_manager/compat/__init__.py
manager/knot_resolver_manager/utils/__init__.py
manager/poetry.lock
manager/pyproject.toml
manager/pyrightconfig.json [new file with mode: 0644]
manager/scripts/container-build [moved from manager/scripts/container-buildall with 50% similarity]
manager/scripts/container-run.py
manager/scripts/run [new file with mode: 0755]
manager/scripts/run-debug

index 01b1490adfd38d991d8e0cabb6edd1392c78342a..5fd349b94533de6d2c18a919466551ddd09d41cc 100644 (file)
@@ -73,7 +73,7 @@ RUN poetry --version \
   # and install the dependencies
   && poetry install --no-dev --no-interaction --no-ansi
 
-# Copy the remaining code
-COPY . /code
+# Here, we would copy the remaining code if we wanted to permanently keep it in the container. We don't do that, we use read-only bind mounts
+COPY . /code
 
 CMD ["/bin/systemd"]
\ No newline at end of file
index ac7985acb394573c739a18dd8ec0b077eeeb2ffe..d18c707627a7953690677983f01b8dea637e721a 100644 (file)
@@ -78,7 +78,7 @@ RUN echo "Running in $KNOT_ENV" \
     --no-interaction --no-ansi \
   && if test "$KNOT_ENV" = "dev"; then yarn install; fi
 
-# Copy the remaining code
-COPY . /code
+# Here, we would copy the remaining code if we wanted to permanently keep it in the container. We don't do that, we use read-only bind mounts
+COPY . /code
 
 CMD ["/bin/systemd"]
\ No newline at end of file
index 7b739cb93b40abe13f09355b48332cc4b6586dc6..6955e2bd8e174b082ba75255467fc6428de6c030 100644 (file)
@@ -394,7 +394,7 @@ class TestRunner:
                     print(f"Skipping test {Colors.YELLOW}{test.name}{Colors.RESET}")
                     continue
 
-                test.run(manager)
+                test.run(manager, inspect_failed)
 
 
 if __name__ == "__main__":
index 7a316d7b260e6f683c4be68d64aac1626c035948..d6bd3880a1f46bdcc157cf3dadef9214573d93b3 100644 (file)
@@ -2,4 +2,5 @@ image = "knot-manager:debian"
 cmd = ["/test/run"]
 
 [mount]
-"/test" = "integration/tests/basic_startup"
\ No newline at end of file
+"/test" = "integration/tests/basic_startup"
+"/code" = "."
\ No newline at end of file
index bbc2fb5bd74a27796f4632bd7f26fd61666b40a8..dcb25097efffd53f72d984bce65f2ecdadf09b3a 100644 (file)
@@ -2,4 +2,5 @@ image = "knot-manager:debian"
 cmd = ["/test/run"]
 
 [mount]
-"/test" = "integration/tests/worker_count"
\ No newline at end of file
+"/test" = "integration/tests/worker_count"
+"/code" = "."
\ No newline at end of file
index 50bd9285c022c3bcb19b1afac6b2c046f779dbc0..575a02fa39d88a13cb61154842cb2c366389e302 100644 (file)
@@ -1,8 +1,15 @@
+from typing import Optional
+from pathlib import Path
+import sys
+
 from aiohttp import web
-from knot_resolver_manager.kresd_manager import KresdManager
+import click
 
+from .kresd_manager import KresdManager
+from .utils import ignore_exceptions
 from . import configuration
 
+# when changing this, change the help message in main()
 _SOCKET_PATH = "/tmp/manager.sock"
 
 
@@ -17,7 +24,14 @@ async def apply_config(request: web.Request) -> web.Response:
     return web.Response(text="OK")
 
 
-def main():
+@click.command()
+@click.argument("listen", type=str, nargs=1, required=False, default=None)
+def main(listen: Optional[str]):
+    """Knot Resolver Manager
+
+    [listen] ... numeric port or a path for a Unix domain socket, default is \"/tmp/manager.sock\"
+    """
+
     app = web.Application()
 
     # initialize KresdManager
@@ -32,9 +46,21 @@ def main():
     # configure routing
     app.add_routes([web.get("/", hello), web.post("/config", apply_config)])
 
-    # run forever
-    web.run_app(app, path=_SOCKET_PATH)
+    # run forever, listen at the appropriate place
+    maybe_port = ignore_exceptions(None, ValueError, TypeError)(int)(listen)
+    if listen is None:
+        web.run_app(app, path=_SOCKET_PATH)
+    elif maybe_port is not None:
+        web.run_app(app, port=maybe_port)
+    elif Path(listen).parent.exists():
+        web.run_app(app, path=listen)
+    else:
+        print(
+            "Failed to parse LISTEN argument. Not an integer, not a valid path to a file in an existing directory.",
+            file=sys.stderr,
+        )
+        sys.exit(1)
 
 
 if __name__ == "__main__":
-    main()
+    main()  # pylint: disable=no-value-for-parameter
index 2f41e5328e638b08c1963941a42d787f4fb1dccc..efb04f4633841c047f85b684eb8632e7cbda26d5 100644 (file)
@@ -1,4 +1,5 @@
 from . import asyncio
+from . import dataclasses
 
 
-__all__ = ["asyncio"]
+__all__ = ["asyncio", "dataclasses"]
index cf8fa8974a17a15ea5314073d10ef32a55a80558..de056390cdf8b0e0ba72518ca2472377fabf6ed1 100644 (file)
@@ -5,4 +5,22 @@ from .dataclasses_yaml import (
 )
 
 
-__all__ = ["dataclass_strictyaml_schema", "dataclass_strictyaml", "StrictyamlParser"]
+def ignore_exceptions(default, *exception):
+    def decorator(func):
+        def f(*nargs, **nkwargs):
+            try:
+                return func(*nargs, **nkwargs)
+            except exception:
+                return default
+
+        return f
+
+    return decorator
+
+
+__all__ = [
+    "dataclass_strictyaml_schema",
+    "dataclass_strictyaml",
+    "StrictyamlParser",
+    "ignore_exceptions",
+]
index c83cf3e3a4836525a5bb71060ff948b9c582dbea..271f72302cc86dbc48d3662c5e09a322aac1e3bc 100644 (file)
@@ -128,7 +128,7 @@ python-versions = "*"
 name = "click"
 version = "7.1.2"
 description = "Composable command line interface toolkit"
-category = "dev"
+category = "main"
 optional = false
 python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
 
@@ -954,7 +954,7 @@ testing = ["pytest (>=3.5,!=3.7.3)", "pytest-checkdocs (>=1.2.3)", "pytest-flake
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.6.12"
-content-hash = "e19083953f5e7f21555443d72701b765528ea26a5a9bbb746bb2093fb551acef"
+content-hash = "84b5fb8bb68a208f7a3b4027815766c8c6c2e82681789eacacf5838199b14a3d"
 
 [metadata.files]
 aiohttp = [
index ce7d98b61afe703c72a3ba6df9d5360ac3c7f326..fdf6b36a6b619e53749d275a812fc3527893a4ba 100644 (file)
@@ -14,6 +14,7 @@ strictyaml = "^1.3.2"
 pydbus = "^0.6.0"
 PyGObject = "^3.38.0"
 Jinja2 = "^2.11.3"
+click = "^7.1.2"
 
 [tool.poetry.dev-dependencies]
 pytest = "^5.2"
@@ -31,14 +32,14 @@ toml = "^0.10.2"
 debugpy = "^1.2.1"
 
 [tool.poe.tasks]
-run = { cmd = "python -m knot_resolver_manager", help = "Run the manager" }
+run = { cmd = "scripts/run", help = "Run the manager" }
 run-debug = { cmd = "scripts/run-debug", help = "Run the manager under debugger" }
 test = { cmd = "pytest --cov=knot_resolver_manager --show-capture=all tests/", help = "Run tests" }
 check = { cmd = "scripts/codecheck", help = "Run static code analysis" }
 format = { cmd = "poetry run black knot_resolver_manager/ tests/", help = "Run 'Black' code formater" }
 fixdeps = { shell = "poetry install; yarn install", help = "Install/update dependencies according to configuration files"}
 commit = { shell = "scripts/commit", help = "Invoke every single check before commiting" }
-container-build = { cmd = "scripts/container-buildall", help = "Build all containers" }
+container-build = { cmd = "scripts/container-build", help = "Build containers (no arguments = all, otherwise arguments are tags that should be built)" }
 container-run = { cmd = "scripts/container-run.py", help = "Run a container" }
 clean = """
   rm -rf .coverage
diff --git a/manager/pyrightconfig.json b/manager/pyrightconfig.json
new file mode 100644 (file)
index 0000000..61815ec
--- /dev/null
@@ -0,0 +1,8 @@
+{
+  "include": [
+    "knot_resolver_manager"
+  ],
+  "exclude": [
+    "knot_resolver_manager/compat/asyncio.py"
+  ]
+}
\ No newline at end of file
similarity index 50%
rename from manager/scripts/container-buildall
rename to manager/scripts/container-build
index 4f4132a92f266968b8943ede854f7edb861f865e..1e0a3ac1fca9e3f93e542da839b84eb17d7be411 100755 (executable)
@@ -8,7 +8,14 @@ src_dir="$(dirname "$(realpath "$0")")"
 source $src_dir/_env.sh
 
 
-# build the actual containers
-for tag in $(find containers -maxdepth 1 -type d -printf '%f\n' | grep -v containers); do
+if test "$#" -eq 0; then
+    containers="$(find containers -maxdepth 1 -type d -printf '%f\n' | grep -v containers)"
+else
+    containers="$@"
+fi
+
+
+# build all configured containers
+for tag in $containers; do
     podman build -t "knot-manager:$tag" -f "containers/$tag/Containerfile" .
 done
\ No newline at end of file
index 049f856276448b173237a44fc18a50f5fa71a050..a66a4ca85ef6ccc5b3bccd6d0c094c4ff8f153a0 100755 (executable)
 #!/usr/bin/env python
 
 import subprocess
-from typing import List, Optional
+from typing import Dict, List, Optional
 import click
 import time
-import itertools
+from pathlib import Path, PurePath
+import sys
 
 PODMAN_EXECUTABLE = "/usr/bin/podman"
 
-def start_detached(image: str, publish: List[int] = []) -> str:
+
+def start_detached(
+    image: str, publish: List[int] = [], ro_mounts: Dict[PurePath, PurePath] = {}
+) -> str:
     """Start a detached container"""
-    options = [ f"--publish={port}:{port}/tcp" for port in publish ]
+    options = [f"--publish={port}:{port}/tcp" for port in publish] + [
+        f"--mount=type=bind,source={str(src)},destination={str(dst)},ro=true"
+        for src, dst in ro_mounts.items()
+    ]
     command = ["podman", "run", "--rm", "-d", *options, image]
-    proc = subprocess.run(command, shell=False, executable=PODMAN_EXECUTABLE, stdout=subprocess.PIPE)
+    proc = subprocess.run(
+        command, shell=False, executable=PODMAN_EXECUTABLE, stdout=subprocess.PIPE
+    )
     assert proc.returncode == 0
-    return str(proc.stdout, 'utf8').strip()
+    return str(proc.stdout, "utf8").strip()
+
 
 def exec(container_id: str, cmd: List[str]) -> int:
     command = ["podman", "exec", container_id] + cmd
     return subprocess.call(command, shell=False, executable=PODMAN_EXECUTABLE)
 
+
 def exec_interactive(container_id: str, cmd: List[str]) -> int:
     command = ["podman", "exec", "-ti", container_id] + cmd
     return subprocess.call(command, shell=False, executable=PODMAN_EXECUTABLE)
 
+
 def stop(container_id: str):
     command = ["podman", "stop", container_id]
     ret = subprocess.call(command, shell=False, executable=PODMAN_EXECUTABLE)
     assert ret == 0
 
 
+def _get_git_root() -> PurePath:
+    result = subprocess.run(
+        "git rev-parse --show-toplevel", shell=True, stdout=subprocess.PIPE
+    )
+    return PurePath(str(result.stdout, encoding="utf8").strip())
+
+
 @click.command()
 @click.argument("image", nargs=1)
 @click.argument("command", nargs=-1)
-@click.option("-p", "--publish", "publish", type=int, help="Port which should we publish")
-def main(image: str, command: List[str], publish: Optional[int]):
+@click.option(
+    "-p", "--publish", "publish", multiple=True, type=int, help="Port which should be published"
+)
+@click.option(
+    "-m",
+    "--mount",
+    "mount",
+    multiple=True,
+    nargs=1,
+    type=str,
+    help="Read-only bind mounts into the container, value /path/on/host:/path/in/container",
+)
+@click.option(
+    "-c",
+    "--code",
+    "mount_code",
+    default=False,
+    is_flag=True,
+    type=bool,
+    help="Shortcut to mount gitroot into /code",
+)
+def main(
+    image: str,
+    command: List[str],
+    publish: Optional[int],
+    mount: Optional[List[str]],
+    mount_code: bool,
+):
     # make sure arguments have the correct type
     image = str(image)
     command = list(command)
-    publish = [] if publish is None else [int(publish)]
+    publish = [] if publish is None else [int(p) for p in publish]
+    mount = [] if mount is None else [x.split(":") for x in mount]
+    mount_path = {Path(x[0]).absolute(): Path(x[1]).absolute() for x in mount}
+    for src_path in mount_path:
+        if not src_path.exists():
+            print(
+                f'The specified path "{str(src_path)}" does not exist on the host system',
+                file=sys.stderr,
+            )
+            exit(1)
+    if mount_code:
+        mount_path[_get_git_root()] = Path("/code")
 
-    cont = start_detached(image, publish=publish)
+    cont = start_detached(image, publish=publish, ro_mounts=mount_path)
     # wait for the container to boot properly
     time.sleep(0.5)
     # run the command
@@ -48,5 +104,6 @@ def main(image: str, command: List[str], publish: Optional[int]):
     # stop the container
     stop(cont)
 
+
 if __name__ == "__main__":
-    main()
\ No newline at end of file
+    main()
diff --git a/manager/scripts/run b/manager/scripts/run
new file mode 100755 (executable)
index 0000000..1ed7404
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+# fail early
+set -e
+
+# ensure consistent behaviour
+src_dir="$(dirname "$(realpath "$0")")"
+source $src_dir/_env.sh
+
+# build dev container
+poe container-build dev
+
+echo Knot Manager API is accessible on http://localhost:9000
+echo -------------------------------------------------------
+
+poe container-run --code -p 9000 -- knot-manager:dev python -m knot_resolver_manager 9000
\ No newline at end of file
index c243d475e4df8f1328c47727f2c66d8154d5ca40..b175a4116f7610e19e3e96cbc97777a27b0ebf07 100755 (executable)
@@ -7,12 +7,12 @@ set -e
 src_dir="$(dirname "$(realpath "$0")")"
 source $src_dir/_env.sh
 
-# build all containers
-poe container-build
+# build dev container
+poe container-build dev
 
 echo The debug server will be listening on port localhost:5678
-echo Use VSCode remote attach feature to connect to the debugger server
+echo Use VSCode remote attach feature to connect to the debug server
 echo The manager will start after you connect
 echo ----------------------------------------
 
-poe container-run -p 5678 -- knot-manager:dev python -m debugpy --listen 0.0.0.0:5678 --wait-for-client -m knot_resolver_manager
\ No newline at end of file
+poe container-run -p 5678 --code -- knot-manager:dev python -m debugpy --listen 0.0.0.0:5678 --wait-for-client -m knot_resolver_manager
\ No newline at end of file