Add docker-ip-addr-manager initial app

This commit is contained in:
Joachim Friberg
2026-03-18 21:43:59 +01:00
parent 2fddde0129
commit 69011271fc
19 changed files with 1920 additions and 0 deletions
@@ -0,0 +1,48 @@
from __future__ import annotations
import subprocess
from typing import Callable
class CommandError(RuntimeError):
pass
Runner = Callable[[list[str]], subprocess.CompletedProcess[str]]
def _default_runner(args: list[str]) -> subprocess.CompletedProcess[str]:
return subprocess.run(args, capture_output=True, text=True, check=False)
class IpAddressManager:
def __init__(self, runner: Runner | None = None):
self._runner = runner or _default_runner
def is_present(self, ip: str, cidr: int, device: str) -> bool:
target = f"{ip}/{cidr}"
result = self._runner(["ip", "-4", "-o", "addr", "show", "dev", device])
if result.returncode != 0:
raise CommandError(f"Failed to query addresses on {device}: {result.stderr.strip()}")
for line in result.stdout.splitlines():
tokens = line.split()
if target in tokens:
return True
return False
def ensure_present(self, ip: str, cidr: int, device: str) -> None:
if self.is_present(ip, cidr, device):
return
result = self._runner(["ip", "addr", "add", f"{ip}/{cidr}", "dev", device])
if result.returncode != 0 and "File exists" not in result.stderr:
raise CommandError(f"Failed to add address {ip}/{cidr} on {device}: {result.stderr.strip()}")
def ensure_absent(self, ip: str, cidr: int, device: str) -> None:
if not self.is_present(ip, cidr, device):
return
result = self._runner(["ip", "addr", "del", f"{ip}/{cidr}", "dev", device])
if result.returncode != 0 and "Cannot assign requested address" not in result.stderr:
raise CommandError(f"Failed to remove address {ip}/{cidr} on {device}: {result.stderr.strip()}")