feat: initialize XAS test console with native execution
This commit is contained in:
+111
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Start only this project's CPU-only Rundeck container (run via normal sudo)."""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
CONTAINER = "adamwen-test-console"
|
||||
IMAGE = "adamwen/test-console:20261013-2"
|
||||
|
||||
|
||||
def validate_runtime(project):
|
||||
project = project.resolve(strict=True)
|
||||
runtime = project / "runtime"
|
||||
candidates = [runtime] + [runtime / name for name in ("auth", "data", "logs", "projects", "results", "proxy")]
|
||||
for path in candidates:
|
||||
if path.is_symlink() or not path.resolve().is_relative_to(project):
|
||||
raise ValueError("Runtime directories must remain inside this project without symlinks")
|
||||
if path.exists() and not path.is_dir():
|
||||
raise ValueError("Runtime paths must be directories")
|
||||
return runtime
|
||||
|
||||
|
||||
def main():
|
||||
from lan_config import load_settings, proxy_config, PROXY_IMAGE, NETWORK, PROXY
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--project-root", type=Path, required=True)
|
||||
args = parser.parse_args()
|
||||
project = args.project_root.resolve(strict=True)
|
||||
try:
|
||||
runtime = validate_runtime(project)
|
||||
except ValueError as exc:
|
||||
parser.error(str(exc))
|
||||
settings = load_settings(project / "deploy/lan-settings.json")
|
||||
for name in (CONTAINER, PROXY):
|
||||
existing = subprocess.run(["docker", "container", "inspect", name],
|
||||
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
if existing.returncode == 0:
|
||||
parser.error(f"{name} already exists; inspect it instead of replacing it")
|
||||
with socket.socket() as probe:
|
||||
probe.bind((settings['address'], settings['port']))
|
||||
network = subprocess.run(["docker", "network", "inspect", NETWORK], capture_output=True, text=True)
|
||||
if network.returncode == 0:
|
||||
if json.loads(network.stdout)[0].get('Labels', {}).get('cn.aisuan.owner') != CONTAINER:
|
||||
parser.error('Existing network does not belong to this project')
|
||||
else:
|
||||
subprocess.run(['docker','network','create','--label',f'cn.aisuan.owner={CONTAINER}',NETWORK],check=True)
|
||||
for name in ("data", "logs", "projects", "results", "proxy"):
|
||||
path = runtime / name
|
||||
if path.is_symlink():
|
||||
parser.error("Symlink runtime directories are forbidden")
|
||||
path.mkdir(parents=True, exist_ok=True, mode=0o700)
|
||||
os.chown(path, 1000, 0)
|
||||
os.chmod(path, 0o700)
|
||||
config = runtime / 'proxy/nginx.conf'
|
||||
if config.is_symlink():
|
||||
parser.error('Proxy config must not be a symlink')
|
||||
config.write_text(proxy_config(settings), encoding='utf-8')
|
||||
os.chmod(config, 0o644)
|
||||
url = f"http://{settings['address']}:{settings['port']}"
|
||||
command = ["docker", "run", "-d", "--name", CONTAINER,
|
||||
"--label", "cn.aisuan.owner=adamwen-test-console",
|
||||
"--runtime", "runc", "--user", "1000:0", "--init",
|
||||
"--cpus", "2", "--memory", "2g", "--memory-swap", "2g",
|
||||
"--pids-limit", "256", "--cap-drop", "ALL",
|
||||
"--security-opt", "no-new-privileges:true",
|
||||
"--restart", "no", "--network", f"container:{PROXY}",
|
||||
"--log-opt", "max-size=10m", "--log-opt", "max-file=3",
|
||||
"-e", f"RUNDECK_GRAILS_URL={url}",
|
||||
"-e", "RUNDECK_SERVER_ADDRESS=127.0.0.1",
|
||||
"-e", "RUNDECK_SERVER_FORWARDED=true",
|
||||
"-e", "RUNDECK_PREAUTH_ENABLED=true",
|
||||
"-e", "RUNDECK_PREAUTH_ATTRIBUTE_NAME=REMOTE_USER_GROUPS",
|
||||
"-e", "RUNDECK_PREAUTH_USERNAME_HEADER=X-Forwarded-Uuid",
|
||||
"-e", "RUNDECK_PREAUTH_ROLES_HEADER=X-Forwarded-Roles",
|
||||
"-e", "RUNDECK_SERVER_SESSION_TIMEOUT=1800",
|
||||
"-e", "RUNDECK_QUARTZ_THREADPOOL_THREADCOUNT=2",
|
||||
"-e", "JVM_MAX_RAM_PERCENTAGE=60",
|
||||
"-e", "NVIDIA_VISIBLE_DEVICES=void",
|
||||
"-e", "CUDA_VISIBLE_DEVICES="]
|
||||
mounts = [(project / 'deploy/realm.disabled.properties', "/home/rundeck/secure/realm.properties", "ro"),
|
||||
(runtime / "data", "/home/rundeck/server/data", "rw"),
|
||||
(runtime / "logs", "/home/rundeck/var/logs", "rw"),
|
||||
(runtime / "projects", "/home/rundeck/projects", "rw"),
|
||||
(runtime / "results", "/home/rundeck/test-results", "rw"),
|
||||
(project / "deploy" / "operator.aclpolicy", "/home/rundeck/etc/xas-operator.aclpolicy", "ro"),
|
||||
(project / "deploy" / "jaas-loginmodule.conf.template", "/etc/remco/templates/jaas-loginmodule.conf", "ro")]
|
||||
for source, destination, access in mounts:
|
||||
command += ["--mount", f"type=bind,src={source},dst={destination}" + (",readonly" if access == "ro" else "")]
|
||||
command.append(IMAGE)
|
||||
proxy_base = ['docker','run','--runtime','runc','--network',NETWORK,'--user','101:101',
|
||||
'--cpus','0.25','--memory','64m','--pids-limit','32','--cap-drop','ALL',
|
||||
'--security-opt','no-new-privileges:true','--read-only',
|
||||
'--tmpfs','/tmp:rw,noexec,nosuid,size=16m',
|
||||
'--mount',f'type=bind,src={config},dst=/etc/nginx/nginx.conf,readonly',
|
||||
'--entrypoint','nginx']
|
||||
subprocess.run(proxy_base + ['--rm',PROXY_IMAGE,'-t'],check=True)
|
||||
subprocess.run(proxy_base + ['-d','--name',PROXY,'--label',f'cn.aisuan.owner={CONTAINER}',
|
||||
'--publish',f"{settings['address']}:{settings['port']}:8080",
|
||||
'--restart','no','--log-opt','max-size=5m','--log-opt','max-file=2',
|
||||
PROXY_IMAGE,'-g','daemon off;'],check=True)
|
||||
# Namespace loopback cannot be reached via the container IP from the shared host.
|
||||
subprocess.run(command, check=True)
|
||||
print(json.dumps({"container":CONTAINER,"proxy":PROXY,"url":url,"allowed_cidrs":settings['allowed_cidrs'],"gpu":False}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user