Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions .claude/skills/mecak8s-kind-manual-verify/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ provisioning.
- Working directory is the mecatl repo (or a worktree of it).
- `ko`, `kubectl`, `kind`, and either `docker` or `podman` installed.
- The `mecatl-dev` Kind cluster already exists (`kind get clusters` shows it).
If it doesn't, run `task mecak8s:kind-setup` first — that's provisioning,
not this skill's job.
If it doesn't, run `task mecak8s:kind-setup` (base) or
`task mecak8s:kind-up` (one-shot Keycloak bring-up) first — that's
provisioning, not this skill's job.

## Step 1 — Refresh the mecak8s image with current source

Expand Down Expand Up @@ -93,7 +94,8 @@ the TUI/client code changed.
This skill never tears the cluster down. To destroy the whole fixture:

```sh
task mecak8s:kind-destroy
task mecak8s:kind-down # logout, remove /etc/hosts aliases, destroy
task mecak8s:kind-destroy # cluster and local state only
```

## Reference
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ coverage.*
# Retired generated documentation index; do not re-add without an explicit decision.
/llms.txt

# Disposable ToolHive Kind fixture kubeconfig
# Disposable Kind fixture kubeconfigs
/deploy/mecak8s-kind/kconfig.yaml
/deploy/mecak8s-vmcp/kconfig.yaml

# Task cache (root and per-Taskfile-include, e.g. website/.task/)
Expand Down
24 changes: 23 additions & 1 deletion deploy/mecak8s-kind/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,35 @@ task mecak8s:kind-hosts-remove
`kind-hosts-add` manages two entries in `/etc/hosts`:
`127.0.0.1 keycloak.mecatl.svc.cluster.local` and
`127.0.0.1 mecak8s-mecak8s.mecatl.svc.cluster.local`; `kind-hosts-remove` removes
only those exact entries and leaves an `/etc/hosts.bak` backup. The Keycloak
only those exact entries and leaves an `/etc/hosts.bak` backup. Both tasks read
`/etc/hosts` without privileges first and call `sudo` only when an entry must
change, so a run where nothing needs to change never asks for a password. The Keycloak
NodePort is mapped by Kind only to `127.0.0.1:8443` on the host. Keycloak's
alias preserves its configured issuer and certificate hostname while making the
local browser leg reachable. The mecak8s alias exists for a different, less
obvious reason -- see the footgun note below; it is not merely a second
convenience name.

### One-shot bring-up and teardown

To run the whole journey in one command, use the two composed tasks:

```sh
task mecak8s:kind-up # asks for confirmation; sudo only if an alias is missing
task mecak8s:kind-down # sudo only if an alias is present
```

`kind-up` runs `kind-keycloak-setup`, `task build`, `kind-hosts-add`, and
`kind-keycloak-demo` in order. It is **destructive** because it deletes and
recreates `mecatl-dev`, so it asks for confirmation first. Without a terminal,
Task cancels it unless you pass `--yes`. `kind-down` runs `mecatui logout`
against the fixture target, then `kind-hosts-remove` and `kind-destroy`. It
ignores a missing binary or an absent login, and you can run it again safely.

The aliases are the same for every cluster. After you run
`task mecak8s:kind-hosts-add` once in a terminal, `task --yes mecak8s:kind-up`
runs without any prompt, for example from a script.

### Direct remote-client quickstart

After `kind-keycloak-setup` and the explicit `kind-hosts-add` step above, run:
Expand Down
45 changes: 44 additions & 1 deletion deploy/mecak8s-kind/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,34 @@ tasks:
- task: kind-setup
- task: kind-keycloak-apply

# kind-up and kind-down are one-shot compositions of the targets below. They
# run sequentially through cmds, never deps, which Task runs in parallel.
kind-up:
desc: >-
One-shot, DESTRUCTIVE bring-up: recreate mecatl-dev with the Keycloak
layer, build bin/mecatui, add any missing /etc/hosts aliases (sudo only then),
export the fixture CA, and print the login/connect commands. Pass --yes
to skip the confirmation.
prompt: This deletes and recreates the mecatl-dev Kind cluster and its local state. Continue?
cmds:
- task: kind-keycloak-setup
- task: :build
- task: kind-hosts-add
- task: kind-keycloak-demo

kind-down:
desc: >-
One-shot teardown: log mecatui out of the fixture target, remove any
present /etc/hosts aliases (sudo only then), and delete the cluster and its
local state. Safe to re-run.
cmds:
# Logout precedes cleanup so no stored token outlives the realm that
# issued it; a missing binary or absent login is not a teardown failure.
- cmd: test -x ./bin/mecatui && ./bin/mecatui logout mecak8s-mecak8s.mecatl.svc.cluster.local:18080
ignore_error: true
- task: kind-hosts-remove
- task: kind-destroy

# kind-fixture-ca-refresh waits for the three direct loopback mappings and
# (re)writes .scratch/kind/mecatl-dev/fixture-ca.crt from the cluster's
# CURRENT fixture-ca Secret. cert-manager mints a fresh self-signed CA on
Expand Down Expand Up @@ -161,7 +189,7 @@ tasks:
printf '%s\n' '127.0.0.1 mecak8s-mecak8s.mecatl.svc.cluster.local'

kind-hosts-add:
desc: Add the temporary Keycloak issuer and mecak8s target aliases to /etc/hosts (loopback only)
desc: Add the temporary Keycloak issuer and mecak8s target aliases to /etc/hosts (loopback only; sudo only for a missing entry)
cmds:
- cmd: |
set -eu
Expand All @@ -174,9 +202,17 @@ tasks:
# that check, so the mecak8s alias below -- resolving to the SAME
# 127.0.0.1 the Kind mapping already exposes -- is the fix, not a
# cosmetic alternative to `localhost`.
#
# /etc/hosts is world-readable, so presence is checked unprivileged
# first: sudo (and its password prompt) is reached only for a missing
# entry, which lets kind-up run non-interactively once the aliases
# exist. The privileged re-check keeps the append idempotent.
for entry in \
'127.0.0.1 keycloak.mecatl.svc.cluster.local' \
'127.0.0.1 mecak8s-mecak8s.mecatl.svc.cluster.local'; do
if grep -Fqx "$entry" /etc/hosts; then
continue
fi
sudo sh -c 'grep -Fqx "$1" /etc/hosts || printf "%s\\n" "$1" >> /etc/hosts' sh "$entry"
done
echo 'Added (or already present):'
Expand All @@ -187,6 +223,13 @@ tasks:
cmds:
- cmd: |
set -eu
# Checked unprivileged first, as in kind-hosts-add: with neither entry
# present there is nothing to remove, so sudo is never reached and
# /etc/hosts.bak is not overwritten.
if ! grep -Fqx -e '127.0.0.1 keycloak.mecatl.svc.cluster.local' -e '127.0.0.1 mecak8s-mecak8s.mecatl.svc.cluster.local' /etc/hosts; then
echo 'No temporary entries present; nothing to remove.'
exit 0
fi
sudo sed -i.bak \
-e '\|^127\.0\.0\.1 keycloak\.mecatl\.svc\.cluster\.local$|d' \
-e '\|^127\.0\.0\.1 mecak8s-mecak8s\.mecatl\.svc\.cluster\.local$|d' \
Expand Down
50 changes: 50 additions & 0 deletions deploy/mecak8s-kind/fixture_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,56 @@ func TestMecak8sKindFixture_Scenario3_KeycloakDemoQuickstart(t *testing.T) {
}
}

// TestMecak8sKindFixture_OneShotLifecycle pins the one-shot compositions: the
// destructive bring-up is confirmation-guarded and orders hosts aliases before
// the readiness/CA step, the hosts steps reach sudo only when there is work,
// and teardown logs out before deleting the cluster.
func TestMecak8sKindFixture_OneShotLifecycle(t *testing.T) {
upBlock := fixtureTaskBlock(t, "kind-up")
if !strings.Contains(upBlock, "prompt:") {
t.Fatal("kind-up recreates the cluster and must carry a prompt guard")
}
if strings.Contains(upBlock, "deps:") {
t.Fatal("kind-up must run its steps sequentially through cmds, not parallel deps")
}
assertOrdered(t, upBlock, "task: kind-keycloak-setup", "task: :build", "task: kind-hosts-add", "task: kind-keycloak-demo")

// /etc/hosts is world-readable: both hosts tasks check it unprivileged
// before reaching sudo, so an already-converged run never prompts.
assertOrdered(t, fixtureTaskBlock(t, "kind-hosts-add"), `if grep -Fqx "$entry" /etc/hosts`, "continue", "sudo sh -c")
assertOrdered(t, fixtureTaskBlock(t, "kind-hosts-remove"), "if ! grep -Fqx", "exit 0", "sudo sed -i.bak")

downBlock := fixtureTaskBlock(t, "kind-down")
assertOrdered(t, downBlock, "mecatui logout mecak8s-mecak8s.mecatl.svc.cluster.local:18080", "task: kind-hosts-remove", "task: kind-destroy")
}

// fixtureTaskBlock returns only the named task's own block, after verifying
// that every task it transitively references exists.
func fixtureTaskBlock(t *testing.T, name string) string {
t.Helper()
closure := fixtureTaskClosure(t, name)
headers := fixtureTaskBlockRe.FindAllStringIndex(closure, 2)
if len(headers) < 2 {
return closure
}
return closure[:headers[1][0]]
}

func assertOrdered(t *testing.T, text string, steps ...string) {
t.Helper()
prev := -1
for _, step := range steps {
idx := strings.Index(text, step)
if idx < 0 {
t.Fatalf("missing step %q", step)
}
if idx < prev {
t.Fatalf("step %q is out of order", step)
}
prev = idx
}
}

// TestMecak8sKindFixture_Scenario3_KeycloakOIDCOverlay pins the disposable
// private-HTTPS OIDC shape. The CA is narrowly mounted for the validator and
// no process-wide or deprecated insecure escape hatch is admitted.
Expand Down
Loading