diff --git a/.devcontainer/manage/dev-template-configure.sh b/.devcontainer/manage/dev-template-configure.sh index 47ad570..f80cd55 100755 --- a/.devcontainer/manage/dev-template-configure.sh +++ b/.devcontainer/manage/dev-template-configure.sh @@ -18,6 +18,7 @@ ADDITIONS_DIR="$DEVCONTAINER_DIR/additions" # Source libraries source "$SCRIPT_DIR/lib/uis-bridge.sh" +source "$ADDITIONS_DIR/lib/git-identity.sh" #------------------------------------------------------------------------------ # Script Metadata @@ -337,6 +338,21 @@ _configure_service() { local extra_args=() [ -n "$database" ] && extra_args+=("--database" "$database") + # K8s namespace + secret name prefix (Phase 1, item 1.9 of + # INVESTIGATE-improve-template-docs-with-services). + # + # namespace: where the deployed app lives — uses subdomain (user-friendly app + # name) if set, otherwise app_name, otherwise the git repo name. + # secret_name_prefix: matches the deployment manifest's existing + # {{REPO_NAME}}-db convention. Always the git repo name. + # + # Both flags are passed only when GIT_REPO is set (i.e., the project has a + # git remote). Without it, UIS works in legacy mode (no K8s secret). + if [ -n "${GIT_REPO:-}" ]; then + local namespace="${PARAMS[subdomain]:-${PARAMS[app_name]:-$GIT_REPO}}" + extra_args+=("--namespace" "$namespace" "--secret-name-prefix" "$GIT_REPO") + fi + # Handle init file if [ -n "$init_file" ]; then local init_path="$CALLER_DIR/$init_file" @@ -389,7 +405,7 @@ _configure_service() { fi succeeded_services+=("$service") - # Write connection details to .env + # Write local connection URL to .env (for local development) if [ -n "$UIS_LOCAL_URL" ]; then local env_key="$env_var" # Append to .env (create if doesn't exist) @@ -399,10 +415,16 @@ _configure_service() { else echo "${env_key}=${UIS_LOCAL_URL}" >> "$CALLER_DIR/.env" fi - echo " → .env: ${env_key}=${UIS_LOCAL_URL}" + echo " → .env: ${env_key}=${UIS_LOCAL_URL} (local)" fi - if [ -n "$UIS_CLUSTER_URL" ]; then + # Report the K8s Secret if UIS created one (cluster credentials live there, + # not in .env.cluster — the deployment manifest's secretKeyRef reads it). + if [ -n "${UIS_SECRET_NAME:-}" ] && [ -n "${UIS_SECRET_NAMESPACE:-}" ]; then + echo " → K8s Secret: ${UIS_SECRET_NAME} in namespace ${UIS_SECRET_NAMESPACE} (cluster)" + elif [ -n "$UIS_CLUSTER_URL" ]; then + # Legacy fallback: no secret created (e.g., older UIS or no GIT_REPO). + # Write to .env.cluster so callers that read it still work. local env_key="$env_var" if [ -f "$CALLER_DIR/.env.cluster" ] && grep -q "^${env_key}=" "$CALLER_DIR/.env.cluster"; then sed -i "s|^${env_key}=.*|${env_key}=${UIS_CLUSTER_URL}|" "$CALLER_DIR/.env.cluster" @@ -455,6 +477,11 @@ if ! uis_bridge_check; then exit 1 fi +# Detect git identity for namespace + secret_name_prefix. +# Best-effort: if there's no git remote, GIT_REPO will be empty and we fall +# back to legacy mode (no K8s secret created). +detect_git_identity "$CALLER_DIR" 2>/dev/null || true + # Read template-info.yaml YAML_FILE="$CALLER_DIR/template-info.yaml" read_template_info_yaml "$YAML_FILE" diff --git a/.devcontainer/manage/lib/uis-bridge.sh b/.devcontainer/manage/lib/uis-bridge.sh index 44ce49c..a14b43d 100644 --- a/.devcontainer/manage/lib/uis-bridge.sh +++ b/.devcontainer/manage/lib/uis-bridge.sh @@ -8,8 +8,9 @@ # # Functions: # uis_bridge_check — verify Docker CLI and UIS container are available -# uis_bridge_run — run a command in UIS container +# uis_bridge_run — run a command in UIS container (no TTY, no stdin) # uis_bridge_run_stdin — run a command piping stdin (for init files) +# uis_bridge_run_tty — run a command with TTY (for interactive commands) # uis_bridge_configure — call uis configure, parse JSON response #------------------------------------------------------------------------------ @@ -22,14 +23,13 @@ UIS_CONTAINER="uis-provision-host" # Returns: 0 if ready, 1 if not (with error message) #------------------------------------------------------------------------------ uis_bridge_check() { - # Check Docker CLI + # Check Docker CLI (provided by docker-outside-of-docker devcontainer feature) if ! command -v docker >/dev/null 2>&1; then - echo "❌ Docker CLI is not installed." + echo "❌ Docker CLI is not available." echo "" - echo " Install it with: dev-setup" - echo " (Select 'Docker CLI' from Infrastructure & Configuration)" - echo "" - echo " Or directly: bash .devcontainer/additions/install-tool-docker-cli.sh" + echo " This devcontainer should have it via the docker-outside-of-docker" + echo " feature. Check .devcontainer/devcontainer.json includes:" + echo ' "features": { "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {} }' return 1 fi @@ -76,6 +76,19 @@ uis_bridge_run_stdin() { docker exec -i "$UIS_CONTAINER" uis "$@" } +#------------------------------------------------------------------------------ +# Run a command in UIS container with a TTY allocated (for interactive commands +# like `uis connect`, `uis status`, etc. that produce formatted/coloured output) +# +# Arguments: +# $@ — command and arguments +# +# Returns: exit code from docker exec +#------------------------------------------------------------------------------ +uis_bridge_run_tty() { + docker exec -it "$UIS_CONTAINER" uis "$@" +} + #------------------------------------------------------------------------------ # Call uis configure and parse the JSON response # @@ -88,18 +101,26 @@ uis_bridge_run_stdin() { # # Returns: 0 on success, 1 on error # Sets globals: -# UIS_RESPONSE — full JSON response -# UIS_STATUS — "ok" or "error" -# UIS_LOCAL_URL — local connection URL (e.g., DATABASE_URL for local dev) -# UIS_CLUSTER_URL — cluster connection URL (for K8s deployment) -# UIS_ERROR_PHASE — error phase if failed -# UIS_ERROR_DETAIL — error detail if failed +# UIS_RESPONSE — full JSON response +# UIS_STATUS — "ok", "already_configured", or "error" +# UIS_LOCAL_URL — local connection URL (e.g., DATABASE_URL for local dev) +# UIS_CLUSTER_URL — cluster connection URL (deprecated, kept for one cycle) +# UIS_SECRET_NAME — K8s secret name (set when --namespace + --secret-name-prefix passed) +# UIS_SECRET_NAMESPACE — K8s namespace where the secret lives +# UIS_SECRET_ENV_VAR — env var name in the K8s secret (e.g., DATABASE_URL) +# UIS_ERROR_PHASE — error phase if failed +# UIS_ERROR_DETAIL — error detail if failed #------------------------------------------------------------------------------ uis_bridge_configure() { local service="$1" local app_name="$2" shift 2 + # Reset secret fields so callers never read stale values from a previous call + UIS_SECRET_NAME="" + UIS_SECRET_NAMESPACE="" + UIS_SECRET_ENV_VAR="" + local has_stdin=false local args=("configure" "$service" "--app" "$app_name" "--json") @@ -142,6 +163,10 @@ uis_bridge_configure() { ok|already_configured) UIS_LOCAL_URL=$(echo "$response" | jq -r '.local.database_url // .local.url // ""') UIS_CLUSTER_URL=$(echo "$response" | jq -r '.cluster.database_url // .cluster.url // ""') + # K8s Secret fields (set when --namespace + --secret-name-prefix passed) + UIS_SECRET_NAME=$(echo "$response" | jq -r '.secret_name // ""') + UIS_SECRET_NAMESPACE=$(echo "$response" | jq -r '.secret_namespace // ""') + UIS_SECRET_ENV_VAR=$(echo "$response" | jq -r '.env_var // ""') return 0 ;; *) diff --git a/.devcontainer/manage/uis.sh b/.devcontainer/manage/uis.sh new file mode 100755 index 0000000..cf927e2 --- /dev/null +++ b/.devcontainer/manage/uis.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# File: .devcontainer/manage/uis.sh +# Symlinked to: /usr/local/bin/uis (so users can type bare `uis ...`) +# +# Purpose: +# Thin wrapper around the UIS CLI that lives inside the uis-provision-host +# container. Routes commands via docker exec with the right TTY/stdin mode. +# +# Why this exists: +# The UIS CLI is not installed in DCT — it lives inside the +# uis-provision-host container managed by urbalurba-infrastructure. Without +# this shim, users would have to type: +# docker exec uis-provision-host uis configure postgresql --app myapp ... +# With this shim: +# uis configure postgresql --app myapp ... +# +# Modes: +# Interactive TTY (terminal): docker exec -it (uis_bridge_run_tty) +# Piped stdin (cat foo | uis): docker exec -i (uis_bridge_run_stdin) +# Non-TTY no stdin (script): docker exec (uis_bridge_run) +# help/--help/-h/no args: bypass container check, show local help +# +# See: helpers-no/dev-templates → INVESTIGATE-improve-template-docs-with-services.md +# (Phase 1, item 1.8) + +set -e + +# Resolve script dir from symlink target +SCRIPT_REAL_PATH="$(readlink -f "${BASH_SOURCE[0]}")" +SCRIPT_DIR="$(dirname "$SCRIPT_REAL_PATH")" + +# Source the bridge library +# shellcheck source=lib/uis-bridge.sh +source "$SCRIPT_DIR/lib/uis-bridge.sh" + +# Fast path: help and no-args output is local-only. +# Don't require the UIS container to be running for help. +case "${1:-}" in + ""|help|--help|-h) + # If UIS container is up, forward to real `uis help` for accurate info + if uis_bridge_check 2>/dev/null; then + uis_bridge_run_tty "$@" + exit $? + fi + # Container is not running — show local help + cat <<'EOF' +uis — UIS CLI (proxied from DCT via docker-outside-of-docker) + +Usage: uis [args] + +This is a DCT shim that forwards commands to the uis-provision-host +container. UIS provides commands for managing data services, deployments, +templates, and more. + +Common commands (require uis-provision-host running): + uis status Show status of all UIS components + uis status Show status of one service + uis deploy Deploy a service (postgresql, redis, ...) + uis configure Configure a service for an app + uis connect [db] Connect to a service (psql, redis-cli, ...) + uis template list List available UIS stack templates + uis template install Install a UIS stack template + uis expose Expose a service via port-forward + +⚠️ uis-provision-host container is not running. + Start it from the urbalurba-infrastructure repo. +EOF + exit 0 + ;; + *) + # All other commands require the UIS container. + uis_bridge_check || exit 1 + + # Pick the right exec mode based on stdin/stdout state. + if [ -t 0 ] && [ -t 1 ]; then + # Interactive: terminal in and out — allocate TTY + uis_bridge_run_tty "$@" + elif [ ! -t 0 ]; then + # Stdin is piped (e.g., echo SQL | uis configure --init-file -) + uis_bridge_run_stdin "$@" + else + # Non-TTY no stdin (e.g., uis status > out.txt) + uis_bridge_run "$@" + fi + ;; +esac diff --git a/image/Dockerfile b/image/Dockerfile index a4e9dd6..fe56c7a 100644 --- a/image/Dockerfile +++ b/image/Dockerfile @@ -128,6 +128,7 @@ RUN sudo ln -sf /opt/devcontainer-toolbox/manage/dev-setup.sh /usr/local/bin/dev sudo ln -sf /opt/devcontainer-toolbox/manage/dev-log.sh /usr/local/bin/dev-log && \ sudo ln -sf /opt/devcontainer-toolbox/manage/dev-tools.sh /usr/local/bin/dev-tools && \ sudo ln -sf /opt/devcontainer-toolbox/additions/config-host-info.sh /usr/local/bin/config-host-info && \ + sudo ln -sf /opt/devcontainer-toolbox/manage/uis.sh /usr/local/bin/uis && \ # Make all scripts executable (files owned by vscode via --chown) chmod +x /opt/devcontainer-toolbox/manage/*.sh && \ chmod +x /opt/devcontainer-toolbox/additions/*.sh 2>/dev/null || true && \ diff --git a/version.txt b/version.txt index 0e98cde..6b4a49b 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.7.33 +1.7.34 diff --git a/website/docs/ai-developer/plans/active/PLAN-p1-dct-shim.md b/website/docs/ai-developer/plans/active/PLAN-p1-dct-shim.md new file mode 100644 index 0000000..d874225 --- /dev/null +++ b/website/docs/ai-developer/plans/active/PLAN-p1-dct-shim.md @@ -0,0 +1,198 @@ +# Plan: Phase 1 DCT Work — uis Shim + Namespace/Secret Flags + +> **IMPLEMENTATION RULES:** Before implementing this plan, read and follow: +> - [WORKFLOW.md](../../WORKFLOW.md) - The implementation process +> - [PLANS.md](../../PLANS.md) - Plan structure and best practices + +## Status: Active + +**Goal**: Ship DCT's two Phase 1 items from `INVESTIGATE-improve-template-docs-with-services.md`: (1.8) the `uis` shim that lets users type bare `uis ...` commands inside DCT, and (1.9) updates to `dev-template-configure` to pass `--namespace` and `--secret-name-prefix` to UIS. + +**Priority**: High — 1.8 unblocks TMP's README rewrites; 1.9 fixes the deploy-time crash-loop bug. + +**Last Updated**: 2026-04-09 + +**Investigation**: `helpers-no/dev-templates` → `INVESTIGATE-improve-template-docs-with-services.md` (active) + +**Cross-team dependencies**: +- 1.8: zero dependencies, can start immediately +- 1.9: depends on UIS 1.10 (`uis configure --namespace`/`--secret-name-prefix` flags + new JSON fields) + +--- + +## Overview + +The Phase B integration work (completed 2026-04-06) confirmed the architecture works end-to-end, but real-user testing of `python-basic-webserver-database` surfaced UX problems: + +1. **C2: "uis: command not found" from inside DCT.** Users see `uis configure` in docs and try to run it. The CLI lives inside the `uis-provision-host` container — not in DCT. The workaround is `docker exec uis-provision-host uis ...` everywhere, which makes READMEs unreadable. + +2. **Deploy-time crash-loop.** The template's `deployment.yaml` references a Kubernetes Secret (`{{REPO_NAME}}-db`) that doesn't exist in the cluster. ArgoCD deploys the app, the pod tries to read the secret, fails, crash-loops. + +This plan ships the DCT half of the fix. + +--- + +## Phase 1: The `uis` shim (1.8) — IN PROGRESS + +### Tasks + +- [x] 1.1 Add `uis_bridge_run_tty()` function to `.devcontainer/manage/lib/uis-bridge.sh`: + ```bash + uis_bridge_run_tty() { + docker exec -it "$UIS_CONTAINER" uis "$@" + } + ``` +- [x] 1.2 Created `.devcontainer/manage/uis.sh` with TTY/stdin/no-TTY routing + help fast path + ```bash + #!/bin/bash + # /usr/local/bin/uis — DCT shim for the UIS CLI + set -e + source /opt/devcontainer-toolbox/manage/lib/uis-bridge.sh + + # Fast path: help/no-args output is local-only — don't require UIS container + case "${1:-}" in + ""|help|--help|-h) + # Print DCT-flavoured usage that mentions the shim + # Falls back to running 'uis help' if container is up; otherwise shows local message + if uis_bridge_check 2>/dev/null; then + uis_bridge_run_tty "$@" + else + cat <<'EOF' + uis — UIS CLI (proxied from DCT via docker-outside-of-docker) + + Usage: uis [args] + + This is a DCT shim that forwards commands to the uis-provision-host + container. UIS provides commands for managing data services, deployments, + templates, and more. + + Common commands (require uis-provision-host running): + uis status Show status of all UIS components + uis deploy Deploy a service (postgresql, redis, ...) + uis configure Configure a service for an app + uis connect Connect to a service (psql, redis-cli, ...) + uis template install Install a UIS stack template + uis expose Expose a service via port-forward + + ⚠️ uis-provision-host container is not running. + Start it from the urbalurba-infrastructure repo. + EOF + exit 0 + fi + ;; + *) + uis_bridge_check || exit 1 + if [ -t 0 ] && [ -t 1 ]; then + uis_bridge_run_tty "$@" + elif [ ! -t 0 ]; then + uis_bridge_run_stdin "$@" + else + uis_bridge_run "$@" + fi + ;; + esac + ``` +- [x] 1.3 Added `uis` symlink in `image/Dockerfile` (alongside config-host-info) +- [x] 1.4 `chmod +x` already covered by existing `chmod +x /opt/devcontainer-toolbox/manage/*.sh` block +- [ ] 1.5 Test all four input modes (needs new image build): + - Interactive TTY: `uis status` (should show formatted output, no garbled escape codes) + - Piped stdin: `echo "SELECT 1;" | uis configure postgresql --init-file -` (should work, no TTY allocated) + - Non-TTY no stdin: `uis status > out.txt` (should redirect cleanly) + - Container down: `docker stop uis-provision-host && uis status` (should print clear error) + - Help fast path: `docker stop uis-provision-host && uis help` (should print local help, exit 0) + +### Validation + +`uis help` works regardless of UIS state. `uis status`, `uis connect`, `uis configure` work when UIS is running. README rewrites can drop the `docker exec uis-provision-host` noise. + +--- + +## Phase 2: `dev-template-configure` passes namespace + secret name prefix (1.9) — IN PROGRESS + +UIS 1.10 shipped 2026-04-09 (PR #121, all 6 tester verification steps PASS). + +### Tasks + +- [x] 2.1 Resolved `namespace` from `${PARAMS[subdomain]:-${PARAMS[app_name]:-$GIT_REPO}}` — done in `_configure_service` +- [x] 2.2 Resolved `secret_name_prefix` from `$GIT_REPO` (matches deployment.yaml `{{REPO_NAME}}-db`) +- [x] 2.3 Pass both as new args via `extra_args` array — `uis_bridge_configure` already forwards them transparently +- [x] 2.4 `uis_bridge_configure` already forwards arbitrary args via `"$@"` — no change needed +- [x] 2.5 Updated JSON parsing in `uis-bridge.sh`: added `UIS_SECRET_NAME`, `UIS_SECRET_NAMESPACE`, `UIS_SECRET_ENV_VAR` globals, parsed in both `ok` and `already_configured` branches, reset on each call +- [x] 2.6 Updated completion message: shows `→ .env: DATABASE_URL=... (local)` and `→ K8s Secret: my-app-db in namespace my-app (cluster)` when UIS returned secret fields. Falls back to writing `.env.cluster` for legacy callers (no GIT_REPO, older UIS). +- [x] 2.7 Sourced `git-identity.sh` in `dev-template-configure.sh` and call `detect_git_identity` early to populate `GIT_REPO` + +### Validation + +After running `dev-template-configure` on `python-basic-webserver-database`: +- `.env` contains a working local URL +- `kubectl get secret -db -n ` shows the secret exists +- `kubectl get namespace ` shows the namespace exists +- Re-running is idempotent (UIS returns `already_configured`) +- The secret's `DATABASE_URL` key matches the manifest's `secretKeyRef` +- A pod deployed via `kubectl apply -f manifests/deployment.yaml` would NOT crash-loop on missing secret + +--- + +## Phase 3: Test end-to-end with TMP's rewritten templates + +### Tasks (after TMP finishes 1.5/1.6 README rewrites) + +- [ ] 3.1 In a fresh DCT devcontainer with UIS running, follow the new `python-basic-webserver-database` README literally: + - `dev-template python-basic-webserver-database` + - Edit `template-info.yaml` params + - `dev-template-configure` + - Run the Flask app, confirm `/tasks` returns seeded rows +- [ ] 3.2 Verify `uis connect postgresql ` from the new "Verify" section works +- [ ] 3.3 Verify `uis status postgresql` from the "Before you start" section works +- [ ] 3.4 Verify `uis help` works even when the UIS container is stopped +- [ ] 3.5 Test `kubectl get secret -db -n ` shows the secret with correct key + +### Validation + +End-to-end happy path works with zero `docker exec` mentions in the README. + +--- + +## Acceptance Criteria + +- [ ] `/usr/local/bin/uis` exists and works as a transparent shim to UIS CLI +- [ ] `uis_bridge_run_tty()` function added to `lib/uis-bridge.sh` +- [ ] `uis help` / `uis --help` / `uis -h` / `uis` (no args) work without UIS container running +- [ ] All other `uis ...` commands fail clearly when container is down, work when up +- [ ] Stdin pipe (`uis configure ... --init-file -`) routes via `docker exec -i` without TTY +- [ ] Interactive TTY (`uis connect postgresql mydb`) routes via `docker exec -it` +- [ ] `dev-template-configure` passes `--namespace` and `--secret-name-prefix` to UIS +- [ ] `uis-bridge.sh` reads new JSON fields (`secret_name`, `secret_namespace`, `env_var`) +- [ ] `dev-template-configure` completion message mentions both local and cluster secret +- [ ] Re-run of `dev-template-configure` is idempotent (UIS returns `already_configured`) +- [ ] CI passes (static + unit + image build) +- [ ] Tested in fresh devcontainer with TMP's rewritten templates + +--- + +## Files to Create/Modify + +**New:** +- `.devcontainer/manage/uis.sh` — the shim + +**Modify:** +- `.devcontainer/manage/lib/uis-bridge.sh` — add `uis_bridge_run_tty()`, update `uis_bridge_configure()` to forward new flags, update JSON parsing +- `.devcontainer/manage/dev-template-configure.sh` — resolve namespace + secret_name_prefix, pass to bridge, update completion message +- `image/Dockerfile` — add `uis` symlink + +**No template changes needed** — Option A (use existing `{{REPO_NAME}}` placeholder) means the deployment.yaml stays as-is. + +--- + +## Cross-team coordination + +| Item | DCT depends on | DCT blocks | +|---|---|---| +| 1.8 (shim) | Nothing | TMP 1.5, 1.6, 1.7, 1.12 (README rewrites) | +| 1.9 (--namespace) | UIS 1.10 (--namespace flag in `uis configure`) | Nothing — TMP READMEs reference the result, not the flag | + +**Recommended sequence (from TMP 7MSG):** +- Day 1: DCT starts 1.8 (this plan Phase 1) in parallel with TMP and UIS +- Day 2-3: DCT 1.8 lands → TMP can rewrite postgresql-demo README +- Day 2-3: UIS 1.10 lands → DCT starts 1.9 (this plan Phase 2) +- Day 3-5: DCT Phase 3 verification once TMP READMEs are ready