diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2d09249..9e8c435 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -86,6 +86,36 @@ jobs: --set image.tag="${{ github.sha }}" \ $values + reconcile: + needs: detect + # Removing a toolset always touches shared paths (uv.lock, root + # pyproject.toml), so any removal lands in a run with a non-empty matrix; + # docs-only pushes skip, keeping kubeconfig out of no-op runs. + if: needs.detect.outputs.toolsets != '[]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Configure kubeconfig + run: | + mkdir -p ~/.kube + printf '%s' "${KUBE_CONFIG}" > ~/.kube/config + chmod 600 ~/.kube/config + env: + KUBE_CONFIG: ${{ secrets.KUBE_CONFIG }} + - name: Uninstall releases whose toolset directory is gone + run: | + for release in $(helm list --namespace mcp-toolsets --short); do + case "$release" in + mcp-index) continue ;; + mcp-*) toolset="${release#mcp-}" ;; + *) continue ;; + esac + if [ ! -d "toolsets/$toolset" ]; then + echo "Uninstalling $release: toolsets/$toolset no longer exists" + helm uninstall "$release" --namespace mcp-toolsets + fi + done + index: needs: detect # Skip no-op pushes (e.g. docs): the index image is built from shared code, diff --git a/README.md b/README.md index 80b8420..15d0fbf 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,22 @@ tools and merge. Conventions: directory `toolsets/` (kebab-case) → module `.tools` → service `mcp-`. +## Removing a toolset + +```sh +./scripts/remove-toolset my-toolset +``` + +Merge to `main`. Removal is GitOps like everything else: the deploy +workflow reconciles the cluster against `toolsets/`, uninstalling any +`mcp-` release whose directory no longer exists — Deployment, Service +and Ingress with it; the index drops the entry automatically. Mind that +this means merging a deleted directory tears down the live service. + +Not removed automatically: out-of-band Secrets the toolset listed in its +`toolset.yaml` (`kubectl -n mcp-toolsets delete secret `) and its +images in GHCR (delete the package from the repo settings if you care). + ## Deployment - **ci.yml** (PRs + main): lint, tests, `helm lint`, and a no-push Docker @@ -102,6 +118,8 @@ Conventions: directory `toolsets/` (kebab-case) → module root `pyproject.toml`) rebuild *all* toolsets — then per toolset: build and push `ghcr.io///mcp-:` and `helm upgrade --install mcp- charts/mcp-toolset -n mcp-toolsets`. + A reconcile job also uninstalls releases whose `toolsets/` directory + is gone — see [Removing a toolset](#removing-a-toolset). - **Required secret**: `KUBE_CONFIG` — a kubeconfig with rights to manage the `mcp-toolsets` namespace. Images push to GHCR with the built-in `GITHUB_TOKEN`. diff --git a/scripts/remove-toolset b/scripts/remove-toolset new file mode 100755 index 0000000..18d5d20 --- /dev/null +++ b/scripts/remove-toolset @@ -0,0 +1,31 @@ +#!/usr/bin/env sh +# Remove a toolset: scripts/remove-toolset +# +# Deletes toolsets/ and deregisters it from the uv workspace. Merging +# the removal to main uninstalls the deployed mcp- release: the deploy +# workflow reconciles the cluster against the toolsets/ directory. + +set -e + +name="$1" +if [ -z "$name" ]; then + echo "usage: scripts/remove-toolset " >&2 + exit 1 +fi +dir="toolsets/$name" +if [ ! -d "$dir" ]; then + echo "error: $dir does not exist" >&2 + exit 1 +fi + +uv remove "$name" +rm -rf "$dir" +# Re-lock: the removal above ran while the directory still existed, so the +# lockfile still listed it as a workspace member. +uv sync + +echo +echo "Removed $dir and deregistered it from the workspace." +echo "Merge to main: the deploy workflow uninstalls the mcp-$name release." +echo "Not removed automatically: out-of-band Secrets the toolset listed in" +echo "toolset.yaml, and its images in GHCR."