128 lines
3.9 KiB
Python
128 lines
3.9 KiB
Python
"""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)
|