This commit is contained in:
2026-02-12 09:42:59 +02:00
commit 906adb539d
87 changed files with 5288 additions and 0 deletions

127
commands/enter.py Normal file
View File

@@ -0,0 +1,127 @@
"""flow enter — connect to a development instance via SSH."""
import getpass
import os
import sys
from flow.core.config import FlowContext
# Default host templates per platform
HOST_TEMPLATES = {
"orb": "<namespace>.orb",
"utm": "<namespace>.utm.local",
"core": "<namespace>.core.lan",
}
def register(subparsers):
p = subparsers.add_parser("enter", help="Connect to a development instance via SSH")
p.add_argument("target", help="Target: [user@]namespace@platform")
p.add_argument("-u", "--user", help="SSH user (overrides target)")
p.add_argument("-n", "--namespace", help="Namespace (overrides target)")
p.add_argument("-p", "--platform", help="Platform (overrides target)")
p.add_argument("-s", "--session", default="default", help="Tmux session name (default: 'default')")
p.add_argument("--no-tmux", action="store_true", help="Skip tmux attachment")
p.add_argument("-d", "--dry-run", action="store_true", help="Show command without executing")
p.set_defaults(handler=run)
def _parse_target(target: str):
"""Parse [user@]namespace@platform into (user, namespace, platform)."""
user = None
namespace = None
platform = None
if "@" in target:
platform = target.rsplit("@", 1)[1]
rest = target.rsplit("@", 1)[0]
else:
rest = target
if "@" in rest:
user = rest.rsplit("@", 1)[0]
namespace = rest.rsplit("@", 1)[1]
else:
namespace = rest
return user, namespace, platform
def _build_destination(user: str, host: str, preserve_host_user: bool = False) -> str:
if "@" in host:
host_user, host_name = host.rsplit("@", 1)
effective_user = host_user if preserve_host_user else (user or host_user)
return f"{effective_user}@{host_name}"
if not user:
return host
return f"{user}@{host}"
def run(ctx: FlowContext, args):
# Warn if already inside an instance
if os.environ.get("DF_NAMESPACE") and os.environ.get("DF_PLATFORM"):
ns = os.environ["DF_NAMESPACE"]
plat = os.environ["DF_PLATFORM"]
ctx.console.error(
f"Not recommended inside an instance. Currently in: {ns}@{plat}"
)
sys.exit(1)
user, namespace, platform = _parse_target(args.target)
# Apply overrides
if args.user:
user = args.user
if args.namespace:
namespace = args.namespace
if args.platform:
platform = args.platform
user_was_explicit = bool(user)
if not user:
user = os.environ.get("USER") or getpass.getuser()
if not namespace:
ctx.console.error("Namespace is required in target")
sys.exit(1)
if not platform:
ctx.console.error("Platform is required in target")
sys.exit(1)
# Resolve SSH host from template or config
host_template = HOST_TEMPLATES.get(platform)
ssh_identity = None
# Check config targets for override
for tc in ctx.config.targets:
if tc.namespace == namespace and tc.platform == platform:
host_template = tc.ssh_host
ssh_identity = tc.ssh_identity
break
if not host_template:
ctx.console.error(f"Unknown platform: {platform}")
sys.exit(1)
ssh_host = host_template.replace("<namespace>", namespace)
destination = _build_destination(user, ssh_host, preserve_host_user=not user_was_explicit)
# Build SSH command
ssh_cmd = ["ssh", "-tt"]
if ssh_identity:
ssh_cmd.extend(["-i", os.path.expanduser(ssh_identity)])
ssh_cmd.append(destination)
if not args.no_tmux:
ssh_cmd.extend([
"tmux", "new-session", "-As", args.session,
"-e", f"DF_NAMESPACE={namespace}",
"-e", f"DF_PLATFORM={platform}",
])
if args.dry_run:
ctx.console.info("Dry run command:")
print(" " + " ".join(ssh_cmd))
return
os.execvp("ssh", ssh_cmd)