Skip to content

Commit 4e151c2

Browse files
shirshankaclaude
andauthored
feat(dev): multi-worktree isolation and remote runner plugin for datahub-dev (#17550)
Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent a6beeae commit 4e151c2

10 files changed

Lines changed: 1478 additions & 55 deletions

File tree

AGENTS.md

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,9 @@ A stdlib-only Python CLI for agent-driven development. No venv needed — runs w
468468
scripts/dev/datahub-dev.sh <command>
469469
```
470470

471-
Run `scripts/dev/datahub-dev.sh --help` to see all available subcommands (`start`, `stop`, `setup`,
472-
`frontend`, `docs`, `status`, `wait`, `rebuild`, `test`, `flag list/get`, `env`, `sync-flags`, `reset`, `nuke`).
471+
Run `scripts/dev/datahub-dev.sh --help` to see all available subcommands (`start`, `stop`, `suspend`,
472+
`setup`, `frontend`, `docs`, `status`, `wait`, `rebuild`, `test`, `flag list/get`, `env`,
473+
`sync-flags`, `reset`, `nuke`, `instances list/clean`, `shell-env`).
473474

474475
### End-to-End Workflow
475476

@@ -529,6 +530,95 @@ or after a fresh clone.
529530
When starting, `datahub-dev start` automatically detects and stops conflicting DataHub instances
530531
from other worktrees/compose projects that occupy the same ports.
531532

533+
### Remote Runners
534+
535+
`datahub-dev.sh` supports a **runner plugin** that proxies operations to a remote machine
536+
(EC2, Kubernetes pod, or any SSH-accessible host) instead of running Docker locally.
537+
538+
**Configure a runner** in `~/.datahub/dev/config.json`:
539+
540+
```json
541+
{
542+
"max_local_instances": 2,
543+
"max_remote_instances": 10,
544+
"runner": "/path/to/your-runner.sh"
545+
}
546+
```
547+
548+
Or export `DATAHUB_RUNNER=/path/to/runner.sh` in your shell for a one-off session.
549+
550+
**Remote lifecycle** (all commands work identically to local once a runner is set):
551+
552+
```bash
553+
# One-time bootstrap — provisions the remote environment
554+
scripts/dev/datahub-dev.sh setup --remote
555+
556+
# Start — syncs changed local files, runs quickstartDebug on the remote,
557+
# then sets up port tunnels so local ports reach the remote instance
558+
scripts/dev/datahub-dev.sh start
559+
560+
# Stop containers only (remote compute keeps running)
561+
scripts/dev/datahub-dev.sh stop
562+
563+
# Stop containers AND halt the remote compute (no billing while suspended).
564+
# 'start' will automatically resume the instance when needed.
565+
scripts/dev/datahub-dev.sh suspend
566+
567+
# All other commands (status, wait, rebuild, test, flag, env, nuke, …)
568+
# proxy through the runner transparently — use them exactly as you would locally.
569+
scripts/dev/datahub-dev.sh status
570+
```
571+
572+
**Multi-instance management** — each git worktree gets its own isolated instance
573+
(separate Docker project, volumes, and port assignment):
574+
575+
```bash
576+
# List all registered instances (local and remote) with their ports and status
577+
scripts/dev/datahub-dev.sh instances list
578+
579+
# Remove stale entries for worktrees that no longer exist
580+
scripts/dev/datahub-dev.sh instances clean
581+
582+
# Print export statements for the current instance's CLI environment
583+
eval $(scripts/dev/datahub-dev.sh shell-env)
584+
# → sets DATAHUB_GMS_URL to the correct local port (tunnel or direct)
585+
```
586+
587+
**Port assignment** — each instance gets a slot; ports = base + slot × 1000:
588+
589+
| Slot | GMS | Frontend | Notes |
590+
| ---- | ----- | -------- | ------------------------- |
591+
| 0 | 8080 | 9002 | First local instance |
592+
| 1 | 9080 | 10002 | Second local instance |
593+
| 2 | 10080 | 11002 | First remote instance |
594+
|||| Each worktree is isolated |
595+
596+
**Backwards compatibility / opting out of isolation** — if the new per-worktree
597+
project names cause problems (lost data in old volumes, tooling that expects
598+
`datahub-*` container names, CI environments that don't need isolation), set
599+
`compose_project` in `~/.datahub/dev/config.json`:
600+
601+
```json
602+
{ "compose_project": "datahub" }
603+
```
604+
605+
This reverts to the old single-instance behaviour: one `datahub` Docker project,
606+
same container names, existing volumes fully accessible. The env var
607+
`COMPOSE_PROJECT_NAME=datahub` has the same effect without touching the config file.
608+
609+
**Runner interface** — a runner is any executable that speaks four verbs:
610+
611+
```bash
612+
runner init # one-time environment bootstrap
613+
runner sync # push changed local files to the remote
614+
runner exec -- <cmd> [args...] # execute a command in the remote workspace
615+
runner tunnel <local:remote> ... # set up port forwarding
616+
runner resume # start compute if stopped (no-op if running)
617+
runner suspend # stop containers + halt compute
618+
```
619+
620+
A reference Kubernetes runner is at `scripts/dev/runners/k8s.sh`.
621+
532622
### Recovery Escalation
533623

534624
**When to use each:**

docker/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ apply from: "../gradle/versioning/versioning.gradle"
1010

1111
ext {
1212
compose_base = "profiles/docker-compose.yml"
13-
project_name = "datahub"
13+
project_name = System.getenv("COMPOSE_PROJECT_NAME") ?: "datahub"
1414

1515
backend_profile_modules = [
1616
':datahub-upgrade',

docker/profiles/docker-compose.gms.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ x-datahub-mae-consumer-service: &datahub-mae-consumer-service
209209
hostname: datahub-mae-consumer
210210
image: ${DATAHUB_MAE_CONSUMER_IMAGE:-${DATAHUB_REPO:-acryldata}/datahub-mae-consumer}:${DATAHUB_MAE_VERSION:-${DATAHUB_VERSION:-quickstart}}
211211
ports:
212-
- 9091:9091
212+
- ${DATAHUB_MAPPED_MAE_CONSUMER_PORT:-9091}:9091
213213
expose:
214214
- 4319
215215
env_file:
@@ -240,7 +240,7 @@ x-datahub-mce-consumer-service: &datahub-mce-consumer-service
240240
hostname: datahub-mce-consumer
241241
image: ${DATAHUB_MCE_CONSUMER_IMAGE:-${DATAHUB_REPO:-acryldata}/datahub-mce-consumer}:${DATAHUB_MCE_VERSION:-${DATAHUB_VERSION:-quickstart}}
242242
ports:
243-
- 9090:9090
243+
- ${DATAHUB_MAPPED_MCE_CONSUMER_PORT:-9090}:9090
244244
expose:
245245
- 4319
246246
env_file:
@@ -258,8 +258,8 @@ x-datahub-mce-consumer-service-dev: &datahub-mce-consumer-service-dev
258258
<<: *datahub-mce-consumer-service
259259
image: ${DATAHUB_MCE_CONSUMER_IMAGE:-${DATAHUB_REPO:-acryldata}/datahub-mce-consumer}:${DATAHUB_MCE_VERSION:-${DATAHUB_VERSION:-debug}}
260260
ports:
261-
- 9090:9090
262-
- 5009:5009
261+
- ${DATAHUB_MAPPED_MCE_CONSUMER_PORT:-9090}:9090
262+
- ${DATAHUB_MAPPED_MCE_DEBUG_PORT:-5009}:5009
263263
environment: &datahub-mce-consumer-env-dev
264264
<<: [*datahub-dev-telemetry-env, *datahub-mce-consumer-env]
265265
ENTITY_VERSIONING_ENABLED: ${ENTITY_VERSIONING_ENABLED:-true}

metadata-integration/java/datahub-client/src/main/resources/MetadataChangeProposal.avsc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,6 @@
172172
"type" : [ "null", "string" ],
173173
"doc" : "Aspect version\n Initial implementation will use the aspect version's number, however stored as\n a string in the case where a different aspect versioning scheme is later adopted.",
174174
"default" : null
175-
}, {
176-
"name" : "schemaVersion",
177-
"type" : [ "null", "long" ],
178-
"doc" : "Schema version of the aspect data model. Used to determine aspects that need migrations.\nDefaults to 1 when not present as a baseline. Incremented when an aspect is modified.",
179-
"default" : null
180175
}, {
181176
"name" : "aspectCreated",
182177
"type" : [ "null", {

metadata-integration/java/datahub-event/src/main/resources/MetadataChangeProposal.avsc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,6 @@
172172
"type" : [ "null", "string" ],
173173
"doc" : "Aspect version\n Initial implementation will use the aspect version's number, however stored as\n a string in the case where a different aspect versioning scheme is later adopted.",
174174
"default" : null
175-
}, {
176-
"name" : "schemaVersion",
177-
"type" : [ "null", "long" ],
178-
"doc" : "Schema version of the aspect data model. Used to determine aspects that need migrations.\nDefaults to 1 when not present as a baseline. Incremented when an aspect is modified.",
179-
"default" : null
180175
}, {
181176
"name" : "aspectCreated",
182177
"type" : [ "null", {

scripts/dev/datahub-dev.sh

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,56 @@
33
# scripts/datahub-dev.sh status
44
# scripts/datahub-dev.sh rebuild --wait
55
# etc.
6+
7+
# Read per-user config from ~/.datahub/dev/config.json.
8+
# Reads two keys (runner, compose_project) in a single Python call to avoid
9+
# paying the interpreter startup cost twice.
10+
if [ "$DATAHUB_REMOTE_EXEC" != "1" ]; then
11+
_dh_cfg=$(python3 - <<'EOF' 2>/dev/null
12+
import json, pathlib, sys
13+
cfg = pathlib.Path.home() / ".datahub/dev/config.json"
14+
try:
15+
d = json.loads(cfg.read_text())
16+
r = d.get("runner", "")
17+
p = d.get("compose_project", "")
18+
if r:
19+
r = str(pathlib.Path(r).expanduser())
20+
print(r)
21+
print(p)
22+
except Exception:
23+
print("")
24+
print("")
25+
EOF
26+
)
27+
if [ -z "$DATAHUB_RUNNER" ]; then
28+
DATAHUB_RUNNER=$(echo "$_dh_cfg" | sed -n '1p')
29+
[ -n "$DATAHUB_RUNNER" ] && export DATAHUB_RUNNER
30+
fi
31+
if [ -z "$COMPOSE_PROJECT_NAME" ]; then
32+
COMPOSE_PROJECT_NAME=$(echo "$_dh_cfg" | sed -n '2p')
33+
[ -n "$COMPOSE_PROJECT_NAME" ] && export COMPOSE_PROJECT_NAME
34+
fi
35+
unset _dh_cfg
36+
fi
37+
38+
# When a runner is configured and we're not already on the remote side,
39+
# proxy ALL commands through the runner — EXCEPT the ones that are inherently
40+
# local (managing the local registry or printing local connection info).
41+
# 'start' and 'setup --remote' are handled specially inside datahub_dev.py
42+
# itself, so they also skip this proxy and reach Python directly.
43+
if [ -n "$DATAHUB_RUNNER" ] && [ "$DATAHUB_REMOTE_EXEC" != "1" ]; then
44+
case "${1:-}" in
45+
instances|shell-env|start|setup|suspend)
46+
# Local-aware commands: let Python handle them (start/setup/suspend have
47+
# their own runner logic; instances/shell-env read the local registry).
48+
;;
49+
*)
50+
# Everything else (stop, reset, nuke, status, wait, rebuild, flag, env,
51+
# sync-flags, test, docs, frontend) runs on the remote.
52+
exec "$DATAHUB_RUNNER" exec -- \
53+
env DATAHUB_REMOTE_EXEC=1 "$(realpath "$0")" "$@"
54+
;;
55+
esac
56+
fi
57+
658
exec uv run --python 3.11 --no-project "$(dirname "$0")/datahub_dev.py" "$@"

0 commit comments

Comments
 (0)