Files
metabuilder/deployment/cli/stack.py
johndoe6345789 03d07635a2 feat(deployment): add modular Python CLI, fix node-deps registry routing, bump to Node 24
- Dockerfile.node-deps: upgrade FROM node:22 to node:24
- Dockerfile.node-deps: rewrite main registry= line to Nexus when detected
  (was only rewriting scoped @esbuild-kit registry, leaving registry.npmjs.org
  unreachable inside Docker)
- Dockerfile.node-deps: fix sed ordering so cleanup of old auth lines runs
  before registry rewrite (prevents new registry= line from being deleted)
- Add deployment/cli/ modular Python CLI powered by JSON config, replacing
  12 shell scripts (build-base-images.sh, build-apps.sh, deploy.sh,
  start-stack.sh, release.sh, nexus-init.sh, nexus-ci-init.sh,
  push-to-nexus.sh, populate-nexus.sh, publish-npm-patches.sh,
  build-testcontainers.sh, artifactory-init.sh)
- Bump rocksdict 0.3.23 -> 0.3.29 (old version removed from PyPI)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 01:13:47 +00:00

131 lines
4.2 KiB
Python

"""Manage the full MetaBuilder stack (up, down, build, restart, logs, ps, clean)."""
import argparse
import subprocess
import sys
import time
from cli.helpers import (
GREEN, YELLOW, BLUE, RED, NC,
docker_compose, log_info, log_ok, log_warn, log_err, pull_with_retry, run,
)
def _pull_external_images(profiles: list[str], config: dict) -> None:
"""Pre-pull external images so compose up doesn't block."""
ext = config["definitions"]["external_images"]
images = list(ext["core"])
if "--profile" in profiles:
prof_names = [profiles[i + 1] for i in range(len(profiles)) if profiles[i] == "--profile"]
if "monitoring" in prof_names:
images += ext["monitoring"]
if "media" in prof_names:
images += ext["media"]
log_info(f"Pre-pulling {len(images)} external images...")
failed = 0
for i, img in enumerate(images, 1):
print(f" [{i}/{len(images)}] {img}")
if not pull_with_retry(img):
failed += 1
if failed:
log_warn(f"{failed} image(s) failed to pull. Stack may be incomplete.")
else:
log_ok("All images ready.")
def _wait_for_healthy(profiles: list[str], args: argparse.Namespace) -> None:
core_count = 23
profile_info = "core"
if args.monitoring or args.all_profiles:
core_count += 9
profile_info += " + monitoring"
if args.media or args.all_profiles:
core_count += 3
profile_info += " + media"
print(f"{YELLOW}Waiting for services ({profile_info})...{NC}")
max_wait = 120
for elapsed in range(0, max_wait, 2):
result = subprocess.run(
docker_compose(*profiles, "ps", "--format", "json"),
capture_output=True, text=True,
)
healthy = result.stdout.count('"healthy"')
if healthy >= core_count:
print(f"\n{GREEN}All {core_count} services healthy!{NC}")
print(f"\nPortal: {BLUE}http://localhost{NC}\n")
print("Quick commands:")
print(" python3 deployment.py stack logs")
print(" python3 deployment.py stack down")
return
sys.stdout.write(f"\r Services healthy: {healthy}/{core_count} ({elapsed}s)")
sys.stdout.flush()
time.sleep(2)
print(f"\n{YELLOW}Timeout waiting for all services.{NC}")
print(" python3 deployment.py stack ps")
def run_cmd(args: argparse.Namespace, config: dict) -> int:
profiles: list[str] = []
if args.monitoring or args.all_profiles:
profiles += ["--profile", "monitoring"]
if args.media or args.all_profiles:
profiles += ["--profile", "media"]
command = args.command or "up"
# Check docker compose
if subprocess.run(["docker", "compose", "version"], capture_output=True).returncode != 0:
log_err("docker compose not found")
return 1
if command in ("down", "stop"):
log_info("Stopping MetaBuilder stack...")
run(docker_compose(*profiles, "down"))
log_ok("Stack stopped")
return 0
if command == "restart":
run(docker_compose(*profiles, "restart"))
log_ok("Stack restarted")
return 0
if command == "logs":
run(docker_compose(*profiles, "logs", "-f"))
return 0
if command in ("ps", "status"):
run(docker_compose(*profiles, "ps"))
return 0
if command == "clean":
answer = input(f"{RED}This will remove all containers and volumes! Are you sure? (yes/no): {NC}")
if answer.strip() == "yes":
run(docker_compose(*profiles, "down", "-v"))
log_ok("Stack cleaned")
return 0
if command == "build":
log_info("Building MetaBuilder stack...")
_pull_external_images(profiles, config)
run(docker_compose(*profiles, "up", "-d", "--build"))
log_ok("Stack built and started")
return 0
if command in ("up", "start"):
log_info("Starting MetaBuilder stack...")
_pull_external_images(profiles, config)
run(docker_compose(*profiles, "up", "-d"))
print(f"\n{GREEN}Stack started!{NC}\n")
_wait_for_healthy(profiles, args)
return 0
log_err(f"Unknown command: {command}")
return 1
run = run_cmd