From 98af6504d69fb81d624199f6c3b89d490b4bfff5 Mon Sep 17 00:00:00 2001 From: Andy Stoneberg Date: Thu, 13 Nov 2025 14:56:52 -0500 Subject: [PATCH 1/2] chore: add tilt-based development environment closes: #41 closes: #135 Introduce a comprehensive Tilt setup for streamlined local development of the Kubeflow Notebooks workspace components (controller, backend, and frontend). Key features: * Orchestrated development workflow: Tiltfile manages the entire development lifecycle including cluster setup, dependency installation, Docker builds, and Kubernetes deployments * Automated infrastructure setup: - Kind cluster creation and verification via setup-kind.sh - Cert-manager installation via setup-cert-manager.sh - CRD installation with proper dependency ordering * Component integration: - Controller: Docker builds with code generation, CRD installation, and Kubernetes deployment with dev-specific overrides (USE_ISTIO=false) - Backend: Docker builds and Kubernetes deployment with port forwarding - Frontend: Local webpack dev server with hot reloading (not containerized for faster iteration) * Frontend Tilt support: - Environment-aware webpack configuration that loads .env.tilt when TILT_ENV=true - New start:tilt npm script for Tilt-specific development - Enables standalone frontend mode with proper backend proxy configuration * Developer experience improvements: - File watching with intelligent exclusions to prevent rebuild loops - Resource dependency management ensuring proper startup order - Optional frontend via ENABLE_FRONTEND environment variable - Comprehensive documentation in DEVELOPMENT.md * Hot reloading and live updates: - Frontend changes reflect immediately via webpack dev server - Controller/backend changes trigger automatic Docker rebuilds and redeployments - Port forwards configured for easy local access This setup enables developers to iterate quickly on all components with minimal manual setup and configuration. Signed-off-by: Andy Stoneberg --- workspaces/developing/DEVELOPMENT.md | 266 ++++++++++++++++++ workspaces/developing/OWNERS | 5 + workspaces/developing/Tiltfile | 216 ++++++++++++++ .../developing/scripts/setup-cert-manager.sh | 31 ++ workspaces/developing/scripts/setup-kind.sh | 30 ++ workspaces/frontend/.env.tilt | 13 + workspaces/frontend/config/webpack.dev.js | 7 +- workspaces/frontend/package.json | 1 + 8 files changed, 567 insertions(+), 2 deletions(-) create mode 100644 workspaces/developing/DEVELOPMENT.md create mode 100644 workspaces/developing/OWNERS create mode 100644 workspaces/developing/Tiltfile create mode 100755 workspaces/developing/scripts/setup-cert-manager.sh create mode 100755 workspaces/developing/scripts/setup-kind.sh create mode 100644 workspaces/frontend/.env.tilt diff --git a/workspaces/developing/DEVELOPMENT.md b/workspaces/developing/DEVELOPMENT.md new file mode 100644 index 00000000..f74b9cb1 --- /dev/null +++ b/workspaces/developing/DEVELOPMENT.md @@ -0,0 +1,266 @@ +# Development Guide with Tilt + +This directory contains the Tilt configuration for local development of the Kubeflow Notebooks workspace components (controller, backend, and frontend). + +## Table of Contents + +- [Prerequisites](#prerequisites) +- [Quick Start](#quick-start) +- [Configuration](#configuration) +- [Development Workflow](#development-workflow) +- [Frontend Development](#frontend-development) +- [Troubleshooting](#troubleshooting) +- [Best Practices](#best-practices) + +## Prerequisites + +Before using Tilt, ensure you have the following installed: + +- [Tilt](https://docs.tilt.dev/install.html) - v0.30.0 or later +- [Docker](https://docs.docker.com/get-docker/) - for building and running containers +- [Kubernetes cluster](https://kubernetes.io/docs/setup/) - a local cluster (e.g., [Kind](https://kind.sigs.k8s.io/)) +- [kubectl](https://kubernetes.io/docs/tasks/tools/) - configured to connect to your cluster +- [Go](https://golang.org/doc/install) - v1.21.0 or later (for controller and backend) +- [Node.js](https://nodejs.org/) - v20.0.0 or later (for frontend) + +### Verify Prerequisites + +```bash +# Check Tilt +tilt version + +# Check Docker +docker --version + +# Check kubectl +kubectl version --client + +# Check Go (for controller/backend) +go version + +# Check Node.js (for frontend, if enabled) +node --version +npm --version +``` + +### Install Kind (Optional) + +**Note**: Tilt will automatically create a Kind cluster named `tilt` if it doesn't exist. However, you still need to have `kind` installed. + +```bash +# macOS +brew install kind + +# Or follow instructions at: https://kind.sigs.k8s.io/docs/user/quick-start/#installation +``` + +**Alternative**: If you prefer to use a different Kubernetes cluster (Docker Desktop, Minikube, etc.), you can skip Kind installation. Just ensure your cluster is running and `kubectl` is configured to use it. Tilt will detect and use your current `kubectl` context. + +## Quick Start + +1. **Navigate to the developing directory**: + ```bash + cd workspaces/developing + ``` + +2. **Start Tilt**: + ```bash + tilt up + ``` + + This will: + - Open the Tilt UI in your browser (usually http://localhost:10350) + - Build the controller and backend Docker images + - Deploy them to your Kubernetes cluster + - Run the frontend locally with webpack dev server (if enabled) + - Set up port forwards for easy access + - Enable live updates when you make code changes + +3. **Monitor the Build**: + + Watch the Tilt UI in your browser. You should see: + + 1. **Setup Resources** (one-time setup): + - `setup-kind` - Creating/verifying Kind cluster (if using Kind) + - `setup-cert-manager` - Installing cert-manager (required for webhooks) + - `install-crds` - Installing CRDs + + 2. **Local Resources**: + - `controller-generate` - Generating manifests and code + - `workspaces-frontend` - Running webpack dev server (if enabled) + + 3. **Docker Resources** (building images): + - `workspaces-controller` + - `workspaces-backend` + + 4. **Kubernetes Resources** (deploying): + - Deployments, Services, etc. + + Wait until all resources show green/healthy status. + +5. **Access the componenets**: + - Controller health: `http://localhost:8081/healthz` + - Backend API: [Swagger UI](http://localhost:4000/api/v1/swagger/) + - Frontend UI: `http://localhost:9000` (if enabled) + +6. **Stop Tilt**: + ```bash + # In the terminal where Tilt is running, press Ctrl+C + # Or in another terminal: + tilt down + ``` + + This will: + - Stop all Tilt-managed resources + - Clean up deployments (but not the namespace) + +### Optional: Clean up namespace + +```bash +kubectl delete namespace kubeflow-workspaces +``` + +### Optional: Delete Kind cluster + +```bash +kind delete cluster --name tilt +``` + +## Configuration + +### Skipping Frontend + +To run Tilt without the frontend (useful for backend/controller-only development): + +```bash +ENABLE_FRONTEND=false tilt up +``` + +### Custom Ports + +Port forwards are configured in the Tiltfile. To change them, edit the `port_forwards` parameter in the `k8s_resource()` calls. + +## Development Workflow + +1. **Make code changes** in any of the workspace components + - Keep changes focused and small +2. **Tilt automatically detects changes**: + - Controller/Backend: Rebuilds Docker images and redeploys + - Frontend: Webpack dev server hot-reloads changes automatically +3. **View logs** in the Tilt UI or via `tilt logs ` +4. **Add tests** for any new features +5. **Run linting checks** to ensure code style consistency +5. **Ensure tests pass** before opening a PR +6. **Write meaningful commit messages** highlighting what your code contribution is doing +7. **Be responsive** to feedback on the PR + +## Frontend Development + +The frontend runs as a local resource using webpack dev server instead of being built into a Docker image. This provides: + +1. **Fast Hot Reloading**: Changes appear instantly in the browser without rebuilding Docker images +2. **Better Developer Experience**: Full webpack dev server features (source maps, hot module replacement, etc.) +3. **Faster Iteration**: No Docker build step needed for frontend changes +4. **Proxy Configuration**: Webpack dev server proxies API requests to the backend running in Kubernetes + +The frontend uses the `.env.tilt` file for configuration when running with Tilt, which sets it up for standalone mode with proper proxy settings to connect to the backend. + +## Troubleshooting + +### Build Failures + +If builds fail, check: + +```bash +# Controller build issues +cd workspaces/controller && make build + +# Backend build issues +cd workspaces/backend && make build + +# Frontend dev server issues (for Tilt development) +cd workspaces/frontend && npm ci && npm run start:tilt + +# Frontend production build issues (for testing production builds) +cd workspaces/frontend && npm ci && npm run build:prod +``` + +Also verify: +- All prerequisites are installed +- Makefiles can find their dependencies +- Go modules are downloaded (`go mod download` in controller/backend) +- Node modules are installed (`npm ci` in frontend) + +#### BuildKit + +If you see the following error while `Tilt` is trying to build an image: +``` +Build Failed: failed to dial gRPC: unable to upgrade to h2c, received 404 +``` + +Try disabling Docker BuildKit support in the terminal where you are running `tilt up`: +```bash +export DOCKER_BUILDKIT=0 +``` + + +### Kubernetes Connection Issues + +Ensure: +- `kubectl` is configured correctly (`kubectl cluster-info`) +- Your cluster is running and accessible +- You have permissions to create resources in the `kubeflow-workspaces` namespace + +```bash +# Verify cluster is accessible +kubectl cluster-info + +# Check current context +kubectl config current-context + +# List available contexts +kubectl config get-contexts +``` + +### Port Already in Use + +If ports are already in use, you can: + +1. Stop the conflicting service +2. Or modify port forwards in the Tiltfile + +### CRD Installation Fails + +If CRDs fail to install: + +```bash +# Check if you have permissions +kubectl auth can-i create crds + +# Try installing manually +cd workspaces/controller && make install +``` + +Also check that you have cluster-admin permissions or appropriate RBAC. + +## Best Practices + +1. **Run tests before committing**: + ```bash + # Controller + cd workspaces/controller && make lint && make test + + # Backend + cd workspaces/backend && make lint && make test + + # Frontend + cd workspaces/frontend && npm run test + ``` + +2. **Keep dependencies up to date**: + - Go modules: `go mod tidy` in controller/backend + - Node modules: `npm ci` in frontend + +3. **Clean up resources**: + - Always run `tilt down` when done + - Optionally: `kubectl delete namespace kubeflow-workspaces` diff --git a/workspaces/developing/OWNERS b/workspaces/developing/OWNERS new file mode 100644 index 00000000..d6cf9cc9 --- /dev/null +++ b/workspaces/developing/OWNERS @@ -0,0 +1,5 @@ +labels: + - area/ci + - area/v2 +approvers: + - andyatmiami \ No newline at end of file diff --git a/workspaces/developing/Tiltfile b/workspaces/developing/Tiltfile new file mode 100644 index 00000000..21fd810d --- /dev/null +++ b/workspaces/developing/Tiltfile @@ -0,0 +1,216 @@ +# Tiltfile for Kubeflow Notebooks development +# - Streamlines local development of controller, backend, and frontend components + +# Disable analytics +analytics_settings(False) + +# Enforce minimum Tilt version +version_settings(check_updates=True, constraint=">=0.33.0") + +# Increase timeout for k8s operations +update_settings(k8s_upsert_timeout_secs=120) + + +# Allow skipping frontend via environment variable +enable_frontend = os.getenv("ENABLE_FRONTEND", "true").lower() == "true" + +# Get paths relative to Tiltfile location +# Tilt evaluates paths relative to the Tiltfile directory +tilt_root = os.path.dirname(os.path.abspath(__file__)) +# Go up 2 levels from Tiltfile location: developing -> workspaces -> repo root +workspace_root = os.path.dirname(os.path.dirname(tilt_root)) + +# Define paths relative to workspace root +controller_dir = os.path.join(workspace_root, "workspaces/controller") +backend_dir = os.path.join(workspace_root, "workspaces/backend") +frontend_dir = os.path.join(workspace_root, "workspaces/frontend") + + +# Exclude generated files/directories from file watching to prevent rebuild loops +# This prevents rebuild loops when auto-generated files are updated +# Patterns are scoped to specific directories so they don't affect other components +watch_settings( + ignore=[ + # Controller generated files + os.path.join(controller_dir, "**/zz_generated.*"), + os.path.join(controller_dir, "config/crd/**"), + os.path.join(controller_dir, "config/webhook/manifests.yaml"), + # Backend generated files + os.path.join(backend_dir, "openapi/**"), + # Frontend generated files + os.path.join(frontend_dir, "src/generated/**"), + ], +) + + + +# ============================================================================ +# Setup Resources (must run first) +# ============================================================================ + +# Kind Cluster Setup - must be first, no dependencies +local_resource( + "setup-kind", + cmd=os.path.join(tilt_root, "scripts/setup-kind.sh"), + labels=["setup"], + resource_deps=[], + allow_parallel=False, +) + +# Cert-Manager Installation - depends on Kind cluster +local_resource( + "setup-cert-manager", + cmd=os.path.join(tilt_root, "scripts/setup-cert-manager.sh"), + labels=["setup"], + resource_deps=["setup-kind"], + allow_parallel=False, +) + +# ============================================================================ +# Controller +# ============================================================================ + +# Generate manifests and code (needed before Docker build) +# Note: Skip 'fmt' step to prevent infinite rebuild loops (fmt modifies files in place) +local_resource( + "controller-generate", + cmd="cd {} && make build".format(controller_dir), + deps=[ + os.path.join(controller_dir, "cmd"), + os.path.join(controller_dir, "api"), + os.path.join(controller_dir, "internal"), + os.path.join(controller_dir, "go.mod"), + os.path.join(controller_dir, "go.sum"), + ], + ignore=["bin/", "**/zz_generated.*", "**/config/crd/**"], + labels=["controller"], +) + +# CRDs Installation - depends on cert-manager and controller generate +# Runs after controller-generate to ensure we install the latest generated CRDs +# Note: No deps parameter - we don't want to watch for file changes since both +# controller-generate and install-crds run manifests which would create a loop +local_resource( + "install-crds", + cmd="cd {} && make install".format(controller_dir), + labels=["controller"], + resource_deps=["setup-cert-manager", "controller-generate"], + allow_parallel=False, +) + +# Docker build for controller using production Dockerfile +# The production Dockerfile builds the binary inside Docker, avoiding .dockerignore issues +# Note: kustomize replaces 'workspaces-controller' with 'ghcr.io/kubeflow/notebooks/workspaces-controller' +# so we build with that name to match the final YAML +docker_build( + "ghcr.io/kubeflow/notebooks/workspaces-controller", + dockerfile=os.path.join(controller_dir, "Dockerfile"), + context=controller_dir, +) + +# K8s deployment for controller - use kustomize to build and preprocess YAMLs +# Note: kustomize needs to be available (installed via controller Makefile) +# Note: cert-manager must be installed before deploying (handled by setup-cert-manager) +controller_kustomize_path = "config/default" + +# Build manifests with kustomize +manifests = local("cd {} && make -C {} kustomize && {}/bin/kustomize build {}".format( + controller_dir, controller_dir, controller_dir, controller_kustomize_path)) + +# Decode YAMLs to modify them for development +objects = decode_yaml_stream(manifests) + +# Modify the controller deployment for development +for o in objects: + # Defensive check: skip if missing kind/name + kind = o.get("kind") + name = o.get("metadata", {}).get("name") + if not kind or not name: + continue + + # Modify the controller Deployment for dev + if kind == "Deployment" and name == "workspaces-controller": + containers = o.get("spec", {}).get("template", {}).get("spec", {}).get("containers", []) + for container in containers: + if container.get("name") == "manager": + env = container.setdefault("env", []) + use_istio_found = False + for i, env_var in enumerate(env): + if env_var.get("name") == "USE_ISTIO": + env[i] = {"name": "USE_ISTIO", "value": "false"} + use_istio_found = True + break + if not use_istio_found: + env.append({"name": "USE_ISTIO", "value": "false"}) + +# Encode back to YAML and apply +# This applies all controller manifests: CRDs, RBAC, ConfigMaps, Deployment, etc. +overridden_manifests = encode_yaml_stream(objects) +k8s_yaml(overridden_manifests) + +# Configure k8s resource for the controller deployment +k8s_resource( + "workspaces-controller", + port_forwards=["8080:8080", "8081:8081"], # metrics and health probe + resource_deps=["controller-generate", "setup-cert-manager", "install-crds"], + labels=["controller"], +) + +# ============================================================================ +# Backend +# ============================================================================ + +# Docker build for backend using production Dockerfile +docker_build( + "workspaces-backend", + dockerfile=os.path.join(backend_dir, "Dockerfile"), + context=os.path.dirname(backend_dir), # Production Dockerfile expects workspaces/ as context +) + +# K8s deployment for backend - use kustomize to build +# Note: kustomize needs to be available (installed via backend Makefile) +# allow_duplicates=True because namespace is already defined by controller +backend_kustomize_path = "manifests/kustomize/base" +k8s_yaml( + local("cd {} && make -C {} kustomize && {}/bin/kustomize build {}".format( + backend_dir, backend_dir, backend_dir, backend_kustomize_path)), + allow_duplicates=True, +) + +# Configure k8s resource +# Backend waits for controller to be ready first +# Tilt automatically matches the image based on the image name in the YAML +k8s_resource( + "workspaces-backend", + port_forwards="4000", + resource_deps=["workspaces-controller"], + labels=["backend"], +) + +# ============================================================================ +# Frontend (optional) +# ============================================================================ + +if enable_frontend: + # Run frontend as local resource using webpack dev server + # This enables hot-reloading and faster development iteration + # Uses the start:tilt script which sets TILT_ENV=true to load .env.tilt + # Frontend will be available at http://localhost:9000 + # Note: This is a long-running process, so it will show as "Updating" in Tilt UI + # Other resources don't need to wait for it (frontend proxies to backend) + local_resource( + "workspaces-frontend", + serve_cmd="cd {} && npm run start:tilt".format(frontend_dir), + deps=[ + frontend_dir, + ], + ignore=[ + os.path.join(frontend_dir, "node_modules"), + os.path.join(frontend_dir, "dist"), + os.path.join(frontend_dir, "coverage"), + ], + # No resource_deps - frontend doesn't block other resources + # Frontend will proxy to backend when ready, but doesn't need to wait + labels=["frontend"], + ) + diff --git a/workspaces/developing/scripts/setup-cert-manager.sh b/workspaces/developing/scripts/setup-cert-manager.sh new file mode 100755 index 00000000..d426e1b0 --- /dev/null +++ b/workspaces/developing/scripts/setup-cert-manager.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Setup script for cert-manager +# This script checks if cert-manager is installed and installs it if needed + +set -euo pipefail + +# Check if cert-manager is already installed +if kubectl get crd certificates.cert-manager.io >/dev/null 2>&1; then + echo "Cert-manager is already installed" + exit 0 +fi + +echo "Installing cert-manager..." +kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml + +echo "Waiting for cert-manager to be ready..." +# Wait for cert-manager webhook to be ready (this is the critical component) +kubectl wait --for=condition=ready pod \ + -l app.kubernetes.io/instance=cert-manager \ + -n cert-manager \ + --timeout=120s || { + echo "Warning: cert-manager pods may not be fully ready, but continuing..." +} + +# Also wait for the CRDs to be established +kubectl wait --for=condition=established crd/certificates.cert-manager.io --timeout=60s || true +kubectl wait --for=condition=established crd/issuers.cert-manager.io --timeout=60s || true +kubectl wait --for=condition=established crd/clusterissuers.cert-manager.io --timeout=60s || true + +echo "Cert-manager installation complete" + diff --git a/workspaces/developing/scripts/setup-kind.sh b/workspaces/developing/scripts/setup-kind.sh new file mode 100755 index 00000000..6c178471 --- /dev/null +++ b/workspaces/developing/scripts/setup-kind.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Setup script for Kind cluster +# This script checks if a Kind cluster exists and creates it if needed + +set -euo pipefail + +CLUSTER_NAME="tilt" + +# Check if kind command exists +if ! command -v kind >/dev/null 2>&1; then + echo "ERROR: kind is not installed. Please install kind first:" + echo " brew install kind # macOS" + echo " or visit: https://kind.sigs.k8s.io/docs/user/quick-start/#installation" + exit 1 +fi + +# Check if cluster exists +if ! kind get clusters 2>/dev/null | grep -q "^${CLUSTER_NAME}$"; then + echo "Creating Kind cluster '${CLUSTER_NAME}'..." + kind create cluster --name "${CLUSTER_NAME}" --wait 60s + echo "Kind cluster created successfully" +else + echo "Kind cluster '${CLUSTER_NAME}' already exists" +fi + +# Ensure kubectl context is set to the Kind cluster +kubectl config use-context "kind-${CLUSTER_NAME}" || true + +echo "Kind cluster setup complete" + diff --git a/workspaces/frontend/.env.tilt b/workspaces/frontend/.env.tilt new file mode 100644 index 00000000..af366e86 --- /dev/null +++ b/workspaces/frontend/.env.tilt @@ -0,0 +1,13 @@ +APP_ENV=development + +DEPLOYMENT_MODE=standalone +MOCK_API_ENABLED=false +MANDATORY_NAMESPACE=default +URL_PREFIX= +PUBLIC_PATH=/ + +# Proxy configuration for Tilt development +# Backend is port-forwarded by Tilt to localhost:4000 +PROXY_HOST=localhost +PROXY_PORT=4000 +PROXY_PROTOCOL=http diff --git a/workspaces/frontend/config/webpack.dev.js b/workspaces/frontend/config/webpack.dev.js index 777d55e1..e0936526 100644 --- a/workspaces/frontend/config/webpack.dev.js +++ b/workspaces/frontend/config/webpack.dev.js @@ -8,7 +8,10 @@ const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const smp = new SpeedMeasurePlugin({ disable: !process.env.MEASURE }); -setupDotenvFilesForEnv({ env: 'development' }); +// Use 'tilt' env if TILT_ENV is set, otherwise default to 'development' +// This allows dotenv.js to naturally load .env.tilt without code changes +const env = process.env.TILT_ENV === 'true' ? 'tilt' : 'development'; +setupDotenvFilesForEnv({ env }); const webpackCommon = require('./webpack.common.js'); const RELATIVE_DIRNAME = process.env._RELATIVE_DIRNAME; @@ -64,7 +67,7 @@ module.exports = smp.wrap( plugins: [ ...setupWebpackDotenvFilesForEnv({ directory: RELATIVE_DIRNAME, - env: 'development', + env, isRoot: IS_PROJECT_ROOT_DIR, }), ], diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index 126afe29..e94316e6 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -21,6 +21,7 @@ "build:prod": "webpack --config ./config/webpack.prod.js", "generate:api": "./scripts/generate-api.sh && npm run prettier", "start:dev": "webpack serve --hot --color --config ./config/webpack.dev.js", + "start:tilt": "TILT_ENV=true webpack serve --hot --color --config ./config/webpack.dev.js", "test": "run-s prettier:check test:lint test:type-check test:unit test:cypress-ci", "test:cypress-ci": "npx concurrently -P -k -s first \"CY_MOCK=1 npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ", "test:jest": "jest --passWithNoTests", From 7466c0712086decff902c0ca88e1fd93bd815e88 Mon Sep 17 00:00:00 2001 From: Paulo Rego <832830+paulovmr@users.noreply.github.com> Date: Tue, 18 Nov 2025 12:44:03 -0300 Subject: [PATCH 2/2] chore: avoid double slashes in URL (#80) Signed-off-by: Paulo Rego <832830+paulovmr@users.noreply.github.com> Signed-off-by: Andy Stoneberg --- developing/.gitignore | 7 ++ .../developing => developing}/DEVELOPMENT.md | 95 +++++++++++++------ developing/Makefile | 77 +++++++++++++++ {workspaces/developing => developing}/OWNERS | 0 .../developing => developing}/Tiltfile | 85 ++++------------- developing/scripts/kind.yaml | 18 ++++ .../scripts/setup-cert-manager.sh | 9 +- .../scripts/setup-kind.sh | 6 +- workspaces/backend/.gitignore | 11 +-- workspaces/frontend/config/webpack.dev.js | 6 +- workspaces/frontend/package.json | 2 +- 11 files changed, 203 insertions(+), 113 deletions(-) create mode 100644 developing/.gitignore rename {workspaces/developing => developing}/DEVELOPMENT.md (72%) create mode 100644 developing/Makefile rename {workspaces/developing => developing}/OWNERS (100%) rename {workspaces/developing => developing}/Tiltfile (64%) create mode 100644 developing/scripts/kind.yaml rename {workspaces/developing => developing}/scripts/setup-cert-manager.sh (73%) rename {workspaces/developing => developing}/scripts/setup-kind.sh (74%) diff --git a/developing/.gitignore b/developing/.gitignore new file mode 100644 index 00000000..268f1285 --- /dev/null +++ b/developing/.gitignore @@ -0,0 +1,7 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/* \ No newline at end of file diff --git a/workspaces/developing/DEVELOPMENT.md b/developing/DEVELOPMENT.md similarity index 72% rename from workspaces/developing/DEVELOPMENT.md rename to developing/DEVELOPMENT.md index f74b9cb1..03958286 100644 --- a/workspaces/developing/DEVELOPMENT.md +++ b/developing/DEVELOPMENT.md @@ -43,9 +43,9 @@ node --version npm --version ``` -### Install Kind (Optional) +### Install Kind -**Note**: Tilt will automatically create a Kind cluster named `tilt` if it doesn't exist. However, you still need to have `kind` installed. +**Note**: The Makefile will automatically create a Kind cluster named `tilt` if it doesn't exist. However, you still need to have `kind` installed. ```bash # macOS @@ -54,21 +54,38 @@ brew install kind # Or follow instructions at: https://kind.sigs.k8s.io/docs/user/quick-start/#installation ``` -**Alternative**: If you prefer to use a different Kubernetes cluster (Docker Desktop, Minikube, etc.), you can skip Kind installation. Just ensure your cluster is running and `kubectl` is configured to use it. Tilt will detect and use your current `kubectl` context. + +### Using Kind Provider (Optional) + +You can choose to set the `KIND_EXPERIMENTAL_PROVIDER` environment variable in your shell session: + +```bash +export KIND_EXPERIMENTAL_PROVIDER=podman +``` + +The Makefile will honor this environment variable when creating the Kind cluster. ## Quick Start 1. **Navigate to the developing directory**: ```bash - cd workspaces/developing + cd developing ``` -2. **Start Tilt**: +2. **Start Tilt using the Makefile**: ```bash - tilt up + make tilt ``` + **Important**: Always use `make tilt` instead of running `tilt up` directly. The Makefile ensures: + - The Kind cluster exists and is properly configured + - The Kubernetes context is switched to `kind-tilt` + - Cert-manager is installed (required for webhooks) + - All prerequisites are met before Tilt starts + This will: + - Set up the Kind cluster (if it doesn't exist) + - Install cert-manager - Open the Tilt UI in your browser (usually http://localhost:10350) - Build the controller and backend Docker images - Deploy them to your Kubernetes cluster @@ -80,22 +97,20 @@ brew install kind Watch the Tilt UI in your browser. You should see: - 1. **Setup Resources** (one-time setup): - - `setup-kind` - Creating/verifying Kind cluster (if using Kind) - - `setup-cert-manager` - Installing cert-manager (required for webhooks) - - `install-crds` - Installing CRDs - - 2. **Local Resources**: + 1. **Local Resources**: - `controller-generate` - Generating manifests and code + - `install-crds` - Installing CRDs - `workspaces-frontend` - Running webpack dev server (if enabled) - 3. **Docker Resources** (building images): + 2. **Docker Resources** (building images): - `workspaces-controller` - `workspaces-backend` - 4. **Kubernetes Resources** (deploying): + 3. **Kubernetes Resources** (deploying): - Deployments, Services, etc. + **Note**: The Kind cluster and cert-manager setup are handled by the Makefile before Tilt starts, so you won't see those as Tilt resources. + Wait until all resources show green/healthy status. 5. **Access the componenets**: @@ -107,6 +122,8 @@ brew install kind ```bash # In the terminal where Tilt is running, press Ctrl+C # Or in another terminal: + make tilt-down + # Or: tilt down ``` @@ -128,12 +145,22 @@ kind delete cluster --name tilt ## Configuration +### Makefile Targets + +The Makefile provides several targets for managing your development environment: + +- `make tilt` or `make` - Set up Kind cluster, install cert-manager, and start Tilt +- `make tilt-up` - Alias for `make tilt` +- `make tilt-down` - Stop Tilt +- `make setup-kind` - Set up the Kind cluster only (without starting Tilt) +- `make setup-cert-manager` - Install cert-manager only (requires Kind cluster) + ### Skipping Frontend To run Tilt without the frontend (useful for backend/controller-only development): ```bash -ENABLE_FRONTEND=false tilt up +ENABLE_FRONTEND=false make tilt ``` ### Custom Ports @@ -206,21 +233,32 @@ export DOCKER_BUILDKIT=0 ### Kubernetes Connection Issues -Ensure: -- `kubectl` is configured correctly (`kubectl cluster-info`) -- Your cluster is running and accessible -- You have permissions to create resources in the `kubeflow-workspaces` namespace +The Makefile automatically handles context switching to `kind-tilt`. If you encounter issues: -```bash -# Verify cluster is accessible -kubectl cluster-info +1. **Verify the Kind cluster exists**: + ```bash + kind get clusters + ``` -# Check current context -kubectl config current-context +2. **Check the current context**: + ```bash + kubectl config current-context + # Should be: kind-tilt + ``` -# List available contexts -kubectl config get-contexts -``` +3. **If context is wrong, manually switch**: + ```bash + kubectl config use-context kind-tilt + ``` + +4. **Verify cluster is accessible**: + ```bash + kubectl cluster-info + ``` + +5. **Ensure you have permissions** to create resources in the `kubeflow-workspaces` namespace + +**Note**: If you're running `tilt up` directly (not via `make tilt`), you may be on the wrong Kubernetes context. Always use `make tilt` to ensure the correct context is set. ### Port Already in Use @@ -262,5 +300,6 @@ Also check that you have cluster-admin permissions or appropriate RBAC. - Node modules: `npm ci` in frontend 3. **Clean up resources**: - - Always run `tilt down` when done + - Always run `make tilt-down` (or `tilt down`) when done - Optionally: `kubectl delete namespace kubeflow-workspaces` + - Optionally: `kind delete cluster --name tilt` to remove the Kind cluster diff --git a/developing/Makefile b/developing/Makefile new file mode 100644 index 00000000..920f3545 --- /dev/null +++ b/developing/Makefile @@ -0,0 +1,77 @@ +.PHONY: check-tilt tilt-up tilt-down setup-kind setup-cert-manager kustomize + +# Variables +CLUSTER_NAME := tilt +KIND_CONTEXT := kind-$(CLUSTER_NAME) + +# Location to install dependencies to +LOCALBIN ?= $(shell pwd)/bin +$(LOCALBIN): + mkdir -p $(LOCALBIN) + +# Tool Binaries +KUSTOMIZE ?= $(LOCALBIN)/kustomize + +# Tool Versions +KUSTOMIZE_VERSION ?= v5.5.0 + +# Export KIND_EXPERIMENTAL_PROVIDER to honor it if set in user's environment +# (e.g., KIND_EXPERIMENTAL_PROVIDER=podman for podman support) +export KIND_EXPERIMENTAL_PROVIDER + +# Check if tilt is installed +.PHONY: check-tilt +check-tilt: + @if ! command -v tilt >/dev/null 2>&1; then \ + echo "ERROR: tilt is not installed. Please install tilt first:"; \ + echo " curl -fsSL https://raw.githubusercontent.com/tilt-dev/tilt/master/scripts/install.sh | bash"; \ + echo " or visit: https://docs.tilt.dev/install.html"; \ + exit 1; \ + fi + +# Ensure kind cluster exists and context is set before running tilt +setup-kind: + @echo "Setting up Kind cluster..." + @if [ -n "$$KIND_EXPERIMENTAL_PROVIDER" ]; then \ + echo "Using KIND_EXPERIMENTAL_PROVIDER=$$KIND_EXPERIMENTAL_PROVIDER"; \ + fi + @./scripts/setup-kind.sh + +# Install cert-manager (depends on kind cluster being set up) +setup-cert-manager: setup-kind + @echo "Setting up cert-manager..." + @./scripts/setup-cert-manager.sh + + +# Run tilt up with kind cluster and cert-manager setup +tilt-up: check-tilt setup-cert-manager kustomize + @echo "Starting Tilt..." + @tilt up + + +# Stop Tilt +tilt-down: check-tilt kustomize + @echo "Stopping Tilt..." + @tilt down + +# Install kustomize +kustomize: $(KUSTOMIZE) +$(KUSTOMIZE): $(LOCALBIN) + $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) + +# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist +# $1 - target path with name of binary +# $2 - package url which can be installed +# $3 - specific version of package +define go-install-tool +@[ -f "$(1)-$(3)" ] || { \ +set -e; \ +package=$(2)@$(3) ;\ +echo "Downloading $${package}" ;\ +rm -f $(1) || true ;\ +GOBIN=$(LOCALBIN) go install $${package} ;\ +mv $(1) $(1)-$(3) ;\ +} ;\ +ln -sf $(1)-$(3) $(1) +endef + diff --git a/workspaces/developing/OWNERS b/developing/OWNERS similarity index 100% rename from workspaces/developing/OWNERS rename to developing/OWNERS diff --git a/workspaces/developing/Tiltfile b/developing/Tiltfile similarity index 64% rename from workspaces/developing/Tiltfile rename to developing/Tiltfile index 21fd810d..104173e1 100644 --- a/workspaces/developing/Tiltfile +++ b/developing/Tiltfile @@ -10,6 +10,8 @@ version_settings(check_updates=True, constraint=">=0.33.0") # Increase timeout for k8s operations update_settings(k8s_upsert_timeout_secs=120) +# Allow kind-tilt context (set up by Makefile before tilt runs) +allow_k8s_contexts('kind-tilt') # Allow skipping frontend via environment variable enable_frontend = os.getenv("ENABLE_FRONTEND", "true").lower() == "true" @@ -17,14 +19,17 @@ enable_frontend = os.getenv("ENABLE_FRONTEND", "true").lower() == "true" # Get paths relative to Tiltfile location # Tilt evaluates paths relative to the Tiltfile directory tilt_root = os.path.dirname(os.path.abspath(__file__)) -# Go up 2 levels from Tiltfile location: developing -> workspaces -> repo root -workspace_root = os.path.dirname(os.path.dirname(tilt_root)) +# Go up 1 level from Tiltfile location: developing -> repo root +workspace_root = os.path.dirname(tilt_root) # Define paths relative to workspace root controller_dir = os.path.join(workspace_root, "workspaces/controller") backend_dir = os.path.join(workspace_root, "workspaces/backend") frontend_dir = os.path.join(workspace_root, "workspaces/frontend") +# Kustomize binary path (installed by Makefile) +kustomize_bin = os.path.join(tilt_root, "bin/kustomize") + # Exclude generated files/directories from file watching to prevent rebuild loops # This prevents rebuild loops when auto-generated files are updated @@ -32,71 +37,26 @@ frontend_dir = os.path.join(workspace_root, "workspaces/frontend") watch_settings( ignore=[ # Controller generated files + os.path.join(controller_dir, "bin/**"), os.path.join(controller_dir, "**/zz_generated.*"), os.path.join(controller_dir, "config/crd/**"), os.path.join(controller_dir, "config/webhook/manifests.yaml"), # Backend generated files + os.path.join(backend_dir, "bin/**"), os.path.join(backend_dir, "openapi/**"), # Frontend generated files + os.path.join(frontend_dir, "bin/**"), os.path.join(frontend_dir, "src/generated/**"), ], ) -# ============================================================================ -# Setup Resources (must run first) -# ============================================================================ - -# Kind Cluster Setup - must be first, no dependencies -local_resource( - "setup-kind", - cmd=os.path.join(tilt_root, "scripts/setup-kind.sh"), - labels=["setup"], - resource_deps=[], - allow_parallel=False, -) - -# Cert-Manager Installation - depends on Kind cluster -local_resource( - "setup-cert-manager", - cmd=os.path.join(tilt_root, "scripts/setup-cert-manager.sh"), - labels=["setup"], - resource_deps=["setup-kind"], - allow_parallel=False, -) - # ============================================================================ # Controller # ============================================================================ +# Note: Kind cluster and cert-manager are set up by the Makefile before Tilt starts -# Generate manifests and code (needed before Docker build) -# Note: Skip 'fmt' step to prevent infinite rebuild loops (fmt modifies files in place) -local_resource( - "controller-generate", - cmd="cd {} && make build".format(controller_dir), - deps=[ - os.path.join(controller_dir, "cmd"), - os.path.join(controller_dir, "api"), - os.path.join(controller_dir, "internal"), - os.path.join(controller_dir, "go.mod"), - os.path.join(controller_dir, "go.sum"), - ], - ignore=["bin/", "**/zz_generated.*", "**/config/crd/**"], - labels=["controller"], -) - -# CRDs Installation - depends on cert-manager and controller generate -# Runs after controller-generate to ensure we install the latest generated CRDs -# Note: No deps parameter - we don't want to watch for file changes since both -# controller-generate and install-crds run manifests which would create a loop -local_resource( - "install-crds", - cmd="cd {} && make install".format(controller_dir), - labels=["controller"], - resource_deps=["setup-cert-manager", "controller-generate"], - allow_parallel=False, -) # Docker build for controller using production Dockerfile # The production Dockerfile builds the binary inside Docker, avoiding .dockerignore issues @@ -109,13 +69,10 @@ docker_build( ) # K8s deployment for controller - use kustomize to build and preprocess YAMLs -# Note: kustomize needs to be available (installed via controller Makefile) -# Note: cert-manager must be installed before deploying (handled by setup-cert-manager) -controller_kustomize_path = "config/default" +controller_kustomize_path = os.path.join(controller_dir, "config/default") -# Build manifests with kustomize -manifests = local("cd {} && make -C {} kustomize && {}/bin/kustomize build {}".format( - controller_dir, controller_dir, controller_dir, controller_kustomize_path)) +# Build manifests with kustomize (using kustomize binary from Makefile) +manifests = kustomize(controller_kustomize_path, kustomize_bin=kustomize_bin) # Decode YAMLs to modify them for development objects = decode_yaml_stream(manifests) @@ -152,7 +109,6 @@ k8s_yaml(overridden_manifests) k8s_resource( "workspaces-controller", port_forwards=["8080:8080", "8081:8081"], # metrics and health probe - resource_deps=["controller-generate", "setup-cert-manager", "install-crds"], labels=["controller"], ) @@ -168,12 +124,10 @@ docker_build( ) # K8s deployment for backend - use kustomize to build -# Note: kustomize needs to be available (installed via backend Makefile) # allow_duplicates=True because namespace is already defined by controller -backend_kustomize_path = "manifests/kustomize/base" +backend_kustomize_path = os.path.join(backend_dir, "manifests/kustomize/base") k8s_yaml( - local("cd {} && make -C {} kustomize && {}/bin/kustomize build {}".format( - backend_dir, backend_dir, backend_dir, backend_kustomize_path)), + kustomize(backend_kustomize_path, kustomize_bin=kustomize_bin), allow_duplicates=True, ) @@ -194,10 +148,10 @@ k8s_resource( if enable_frontend: # Run frontend as local resource using webpack dev server # This enables hot-reloading and faster development iteration - # Uses the start:tilt script which sets TILT_ENV=true to load .env.tilt + # Uses the start:tilt script which sets DEV_ENV=tilt to load .env.tilt # Frontend will be available at http://localhost:9000 # Note: This is a long-running process, so it will show as "Updating" in Tilt UI - # Other resources don't need to wait for it (frontend proxies to backend) + # Frontend waits for backend to be ready to avoid 504 errors on initial page load local_resource( "workspaces-frontend", serve_cmd="cd {} && npm run start:tilt".format(frontend_dir), @@ -209,8 +163,7 @@ if enable_frontend: os.path.join(frontend_dir, "dist"), os.path.join(frontend_dir, "coverage"), ], - # No resource_deps - frontend doesn't block other resources - # Frontend will proxy to backend when ready, but doesn't need to wait + resource_deps=["workspaces-backend"], labels=["frontend"], ) diff --git a/developing/scripts/kind.yaml b/developing/scripts/kind.yaml new file mode 100644 index 00000000..da9d0c57 --- /dev/null +++ b/developing/scripts/kind.yaml @@ -0,0 +1,18 @@ +apiVersion: kind.x-k8s.io/v1alpha4 +kind: Cluster +# This is needed in order to support projected volumes with service account tokens. +kubeadmConfigPatches: + - | + apiVersion: kubeadm.k8s.io/v1beta3 + kind: ClusterConfiguration + metadata: + name: config + apiServer: + extraArgs: + "service-account-issuer": "kubernetes.default.svc" + "service-account-signing-key-file": "/etc/kubernetes/pki/sa.key" +nodes: +- role: control-plane + image: kindest/node:v1.33.1@sha256:050072256b9a903bd914c0b2866828150cb229cea0efe5892e2b644d5dd3b34f +- role: worker + image: kindest/node:v1.33.1@sha256:050072256b9a903bd914c0b2866828150cb229cea0efe5892e2b644d5dd3b34f \ No newline at end of file diff --git a/workspaces/developing/scripts/setup-cert-manager.sh b/developing/scripts/setup-cert-manager.sh similarity index 73% rename from workspaces/developing/scripts/setup-cert-manager.sh rename to developing/scripts/setup-cert-manager.sh index d426e1b0..31e28570 100755 --- a/workspaces/developing/scripts/setup-cert-manager.sh +++ b/developing/scripts/setup-cert-manager.sh @@ -1,17 +1,22 @@ #!/usr/bin/env bash # Setup script for cert-manager # This script checks if cert-manager is installed and installs it if needed +# Uses the same version as the e2e tests: v1.12.13 (LTS version) set -euo pipefail +# Use LTS version of cert-manager (matches e2e tests) +CERT_MANAGER_VERSION="v1.12.13" +CERT_MANAGER_URL="https://github.com/jetstack/cert-manager/releases/download/${CERT_MANAGER_VERSION}/cert-manager.yaml" + # Check if cert-manager is already installed if kubectl get crd certificates.cert-manager.io >/dev/null 2>&1; then echo "Cert-manager is already installed" exit 0 fi -echo "Installing cert-manager..." -kubectl apply -f https://github.com/cert-manager/cert-manager/releases/latest/download/cert-manager.yaml +echo "Installing cert-manager ${CERT_MANAGER_VERSION}..." +kubectl apply -f "${CERT_MANAGER_URL}" echo "Waiting for cert-manager to be ready..." # Wait for cert-manager webhook to be ready (this is the critical component) diff --git a/workspaces/developing/scripts/setup-kind.sh b/developing/scripts/setup-kind.sh similarity index 74% rename from workspaces/developing/scripts/setup-kind.sh rename to developing/scripts/setup-kind.sh index 6c178471..476d4ef6 100755 --- a/workspaces/developing/scripts/setup-kind.sh +++ b/developing/scripts/setup-kind.sh @@ -5,6 +5,8 @@ set -euo pipefail CLUSTER_NAME="tilt" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +KIND_CONFIG="${SCRIPT_DIR}/kind.yaml" # Check if kind command exists if ! command -v kind >/dev/null 2>&1; then @@ -16,8 +18,8 @@ fi # Check if cluster exists if ! kind get clusters 2>/dev/null | grep -q "^${CLUSTER_NAME}$"; then - echo "Creating Kind cluster '${CLUSTER_NAME}'..." - kind create cluster --name "${CLUSTER_NAME}" --wait 60s + echo "Creating Kind cluster '${CLUSTER_NAME}' with config from ${KIND_CONFIG}..." + kind create cluster --name "${CLUSTER_NAME}" --config "${KIND_CONFIG}" --wait 60s echo "Kind cluster created successfully" else echo "Kind cluster '${CLUSTER_NAME}' already exists" diff --git a/workspaces/backend/.gitignore b/workspaces/backend/.gitignore index ce6feea7..2e7f7630 100644 --- a/workspaces/backend/.gitignore +++ b/workspaces/backend/.gitignore @@ -5,13 +5,4 @@ *.so *.dylib bin/* -Dockerfile.cross - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Go workspace file -go.work \ No newline at end of file +Dockerfile.cross \ No newline at end of file diff --git a/workspaces/frontend/config/webpack.dev.js b/workspaces/frontend/config/webpack.dev.js index e0936526..1a2ed6b4 100644 --- a/workspaces/frontend/config/webpack.dev.js +++ b/workspaces/frontend/config/webpack.dev.js @@ -8,9 +8,7 @@ const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const smp = new SpeedMeasurePlugin({ disable: !process.env.MEASURE }); -// Use 'tilt' env if TILT_ENV is set, otherwise default to 'development' -// This allows dotenv.js to naturally load .env.tilt without code changes -const env = process.env.TILT_ENV === 'true' ? 'tilt' : 'development'; +const env = process.env.DEV_ENV ?? 'development'; setupDotenvFilesForEnv({ env }); const webpackCommon = require('./webpack.common.js'); @@ -85,7 +83,7 @@ module.exports = smp.wrap( port: PORT, compress: true, historyApiFallback: { - index: `${BASE_PATH}/index.html`, + index: `${BASE_PATH}/index.html`.replace('//', '/'), }, hot: true, open: [BASE_PATH], diff --git a/workspaces/frontend/package.json b/workspaces/frontend/package.json index e94316e6..c0a0afd1 100644 --- a/workspaces/frontend/package.json +++ b/workspaces/frontend/package.json @@ -21,7 +21,7 @@ "build:prod": "webpack --config ./config/webpack.prod.js", "generate:api": "./scripts/generate-api.sh && npm run prettier", "start:dev": "webpack serve --hot --color --config ./config/webpack.dev.js", - "start:tilt": "TILT_ENV=true webpack serve --hot --color --config ./config/webpack.dev.js", + "start:tilt": "DEV_ENV=tilt npm run start:dev", "test": "run-s prettier:check test:lint test:type-check test:unit test:cypress-ci", "test:cypress-ci": "npx concurrently -P -k -s first \"CY_MOCK=1 npm run cypress:server:build && npm run cypress:server\" \"npx wait-on tcp:127.0.0.1:9001 && npm run cypress:run:mock -- {@}\" -- ", "test:jest": "jest --passWithNoTests",