@intentius/behold 0.4.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/demos.json +39 -0
- package/dist/cli.js +147 -40
- package/example-flux-estate/README.md +41 -0
- package/example-flux-estate/app-a/chant.config.ts +18 -0
- package/example-flux-estate/app-a/manifests/app.yaml +56 -0
- package/example-flux-estate/app-a/package.json +13 -0
- package/example-flux-estate/app-a/src/app.ts +35 -0
- package/example-flux-estate/app-a/tsconfig.json +1 -0
- package/example-flux-estate/app-b/chant.config.ts +20 -0
- package/example-flux-estate/app-b/manifests/app.yaml +54 -0
- package/example-flux-estate/app-b/package.json +13 -0
- package/example-flux-estate/app-b/src/app.ts +37 -0
- package/example-flux-estate/app-b/tsconfig.json +1 -0
- package/example-flux-estate/control-plane/chant.config.ts +19 -0
- package/example-flux-estate/control-plane/package.json +13 -0
- package/example-flux-estate/control-plane/src/flux.ts +51 -0
- package/example-flux-estate/control-plane/tsconfig.json +1 -0
- package/example-flux-estate/package-lock.json +1949 -0
- package/example-flux-estate/package.json +10 -0
- package/example-flux-estate/scripts/estate-down.sh +19 -0
- package/example-flux-estate/scripts/estate-up.sh +61 -0
- package/example-k8s/README.md +120 -0
- package/example-k8s/chant.config.ts +21 -0
- package/example-k8s/ops/k3d-apply.op.ts +18 -0
- package/example-k8s/package-lock.json +2071 -0
- package/example-k8s/package.json +14 -0
- package/example-k8s/scripts/local/local-down.sh +23 -0
- package/example-k8s/scripts/local/local-up.sh +46 -0
- package/example-k8s/src/config.ts +5 -0
- package/example-k8s/src/web.ts +29 -0
- package/example-k8s/tsconfig.json +1 -0
- package/package.json +4 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Tear the flux-estate demo down: delete the uniquely-named cluster and
|
|
3
|
+
# restore whatever kubectl context was current before estate-up.sh switched it.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
cd "$(dirname "$0")/.."
|
|
6
|
+
|
|
7
|
+
CLUSTER=behold-flux-demo
|
|
8
|
+
STATE="scripts/.prev-context"
|
|
9
|
+
|
|
10
|
+
k3d cluster delete "$CLUSTER" 2>/dev/null || echo "flux-estate demo: no ${CLUSTER} cluster to remove."
|
|
11
|
+
if [ -f "$STATE" ]; then
|
|
12
|
+
prev="$(cat "$STATE")"
|
|
13
|
+
if [ -n "$prev" ] && kubectl config get-contexts -o name 2>/dev/null | grep -qx "$prev"; then
|
|
14
|
+
kubectl config use-context "$prev" >/dev/null
|
|
15
|
+
echo "flux-estate demo: restored kubectl context \"$prev\"."
|
|
16
|
+
fi
|
|
17
|
+
rm -f "$STATE"
|
|
18
|
+
fi
|
|
19
|
+
echo "flux-estate demo: cluster removed."
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Stand up the flux-estate demo (#211): a throwaway single-node k3d cluster,
|
|
3
|
+
# Flux's controllers (applied straight from the pinned release manifest — no
|
|
4
|
+
# flux CLI needed), and the control plane's CRs. From there the reconcilers
|
|
5
|
+
# deploy the app projects out of behold's public repo.
|
|
6
|
+
#
|
|
7
|
+
# Safety, same contract as example-k8s: never touches any OTHER kubeconfig
|
|
8
|
+
# context. Whatever context is current is saved to .prev-context and restored
|
|
9
|
+
# by estate-down.sh; the cluster and its context are uniquely named
|
|
10
|
+
# (behold-flux-demo / k3d-behold-flux-demo).
|
|
11
|
+
#
|
|
12
|
+
# BEHOLD_FLUX_REF (optional): patch the LIVE GitRepository to sync a branch
|
|
13
|
+
# other than main — how a not-yet-merged manifests change is verified. The
|
|
14
|
+
# declared source always says main.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
cd "$(dirname "$0")/.."
|
|
17
|
+
|
|
18
|
+
CLUSTER=behold-flux-demo
|
|
19
|
+
CONTEXT="k3d-${CLUSTER}"
|
|
20
|
+
STATE="scripts/.prev-context"
|
|
21
|
+
FLUX_MANIFEST="https://github.com/fluxcd/flux2/releases/download/v2.4.0/install.yaml"
|
|
22
|
+
|
|
23
|
+
if ! docker info >/dev/null 2>&1; then
|
|
24
|
+
echo "flux-estate demo: Docker is not running — start Docker and re-run." >&2
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
for bin in k3d kubectl; do
|
|
28
|
+
if ! command -v "$bin" >/dev/null 2>&1; then
|
|
29
|
+
echo "flux-estate demo: $bin is not installed." >&2
|
|
30
|
+
exit 1
|
|
31
|
+
fi
|
|
32
|
+
done
|
|
33
|
+
|
|
34
|
+
if ! k3d cluster list 2>/dev/null | grep -q "^${CLUSTER} "; then
|
|
35
|
+
kubectl config current-context > "$STATE" 2>/dev/null || true
|
|
36
|
+
k3d cluster create "$CLUSTER" --wait
|
|
37
|
+
else
|
|
38
|
+
echo "flux-estate demo: cluster ${CLUSTER} already up — reusing."
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
echo "flux-estate demo: installing Flux controllers (${FLUX_MANIFEST##*/download/})…"
|
|
42
|
+
kubectl --context "$CONTEXT" apply -f "$FLUX_MANIFEST" > /dev/null
|
|
43
|
+
kubectl --context "$CONTEXT" -n flux-system rollout status deploy/source-controller --timeout=180s
|
|
44
|
+
kubectl --context "$CONTEXT" -n flux-system rollout status deploy/kustomize-controller --timeout=180s
|
|
45
|
+
|
|
46
|
+
echo "flux-estate demo: building + applying the control plane's CRs…"
|
|
47
|
+
(cd control-plane && npx chant build src -o dist/control-plane.yaml --format yaml)
|
|
48
|
+
kubectl --context "$CONTEXT" apply -f control-plane/dist/control-plane.yaml
|
|
49
|
+
|
|
50
|
+
if [ -n "${BEHOLD_FLUX_REF:-}" ]; then
|
|
51
|
+
echo "flux-estate demo: syncing branch ${BEHOLD_FLUX_REF} instead of main (BEHOLD_FLUX_REF)"
|
|
52
|
+
kubectl --context "$CONTEXT" -n flux-system patch gitrepository behold --type merge \
|
|
53
|
+
-p "{\"spec\":{\"ref\":{\"branch\":\"${BEHOLD_FLUX_REF}\"}}}"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "flux-estate demo: waiting for the reconcilers to deploy the apps…"
|
|
57
|
+
kubectl --context "$CONTEXT" -n flux-system wait --for=condition=Ready --timeout=180s gitrepository/behold
|
|
58
|
+
kubectl --context "$CONTEXT" -n flux-system wait --for=condition=Ready --timeout=180s kustomization/app-a kustomization/app-b
|
|
59
|
+
kubectl --context "$CONTEXT" -n app-a rollout status deploy/app-a --timeout=180s
|
|
60
|
+
kubectl --context "$CONTEXT" -n app-b rollout status deploy/app-b --timeout=180s
|
|
61
|
+
echo "flux-estate demo: up — Flux deployed both apps from the repo."
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# The k3d turnkey demo — behold's Kubernetes counterpart to Loom-on-Floci
|
|
2
|
+
|
|
3
|
+
The smallest real thing on Kubernetes: one `nginx` Deployment + Service (+ a
|
|
4
|
+
PodDisruptionBudget), deployed from the browser with the **▶ Deploy
|
|
5
|
+
(k3d-apply)** header button, against a local, single-node
|
|
6
|
+
[k3d](https://k3d.io) cluster — no cloud account, no credentials.
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
src/config.ts static config — app name, pinned image tag
|
|
10
|
+
src/web.ts WebApp composite → Deployment + Service + PodDisruptionBudget
|
|
11
|
+
ops/k3d-apply.op.ts ApplyOp "k3d-apply" — code → local k3d, server-side apply
|
|
12
|
+
chant.config.ts lexicons [k8s, temporal], k8s.profiles.local bound to k3d-behold-k3d-demo
|
|
13
|
+
scripts/local/ local-up.sh / local-down.sh — the k3d cluster's own lifecycle
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Run it
|
|
17
|
+
|
|
18
|
+
From the **behold** repo root:
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
npm run demo:k8s
|
|
22
|
+
# → brings up a single-node k3d cluster ("behold-k3d-demo"),
|
|
23
|
+
# then serves this project at http://localhost:4600
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That one command does two things: `scripts/local/local-up.sh` creates the
|
|
27
|
+
cluster (idempotent — reuses one already up), then `behold serve --local`
|
|
28
|
+
points its live overlay at it. `--local`'s usual boot step (`chant emulator
|
|
29
|
+
up`) is a no-op here — chant's k8s lexicon has no local-emulator capability
|
|
30
|
+
the way Floci does for aws — so the cluster comes up via the same generic
|
|
31
|
+
substrate **Bring up** mechanism the Floci demo's `scripts/local/local-up.sh`
|
|
32
|
+
convention already uses (see `src/substrates.ts`), just run up front instead
|
|
33
|
+
of from a click. Ctrl-C stops `behold serve` and tears the cluster back down;
|
|
34
|
+
Docker or k3d missing degrades to the source graph with a clear message
|
|
35
|
+
instead of crashing.
|
|
36
|
+
|
|
37
|
+
1. Open **http://localhost:4600**. The graph shows the Deployment, Service and
|
|
38
|
+
PodDisruptionBudget — **blue** (declared, not yet deployed).
|
|
39
|
+
2. Click **▶ Deploy (k3d-apply)** in the header (or ⌘K → "Deploy: Sync"). The
|
|
40
|
+
now-line streams Build → Plan (a live
|
|
41
|
+
diff) → Apply: a Kubernetes **server-side apply**, field manager
|
|
42
|
+
`chant:behold-k3d-demo` (chant#1074/#1075) — deletes are **owned-only**, a
|
|
43
|
+
marker-scoped prune that only ever touches what chant itself applied.
|
|
44
|
+
3. The three nodes flip **blue → green (managed)**. Zoom in on the Deployment
|
|
45
|
+
and its two Pods appear **nested underneath it** — the runtime tier
|
|
46
|
+
(chant#1077/behold#86): live children a Deployment's controller created,
|
|
47
|
+
never declared here, never classified as drift or an orphan.
|
|
48
|
+
|
|
49
|
+
## The four things this proves live (epic #84)
|
|
50
|
+
|
|
51
|
+
behold's k8s parity (#85–#87) shipped against fixtures; this is it against a
|
|
52
|
+
real cluster.
|
|
53
|
+
|
|
54
|
+
**Declared, not yet deployed.** Before step 2, `/api/overlay` reports all
|
|
55
|
+
three nodes `_status: accent` (pending) — chant knows about them, the cluster
|
|
56
|
+
doesn't yet.
|
|
57
|
+
|
|
58
|
+
**Runtime children.** After apply, `/api/overlay`'s `ir.groups.byContainer`
|
|
59
|
+
nests the Deployment's Pods under it, each `_status: runtime` — a tier below
|
|
60
|
+
what's declared, sourced from the cluster's own `ownerReferences`, that
|
|
61
|
+
behold's zoom dial can descend into.
|
|
62
|
+
|
|
63
|
+
**Managed-fields drift.** Scale or label the Deployment out of band —
|
|
64
|
+
`kubectl scale deployment/web --replicas=3`, bypassing chant entirely — and
|
|
65
|
+
`/api/diff`'s `fieldDrift` reports `spec.replicas: { kind: "changed", declared:
|
|
66
|
+
2, live: 3 }`: chant's own SSA-tracked field, now diverged from a competing
|
|
67
|
+
field manager (`kubectl`, visible in `kubectl get deploy/web -o json`'s
|
|
68
|
+
`metadata.managedFields`). Re-running `k3d-apply` at that point doesn't
|
|
69
|
+
silently overwrite it: chant's server-side apply refuses —
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
k8s: server-side apply of apps/v1 Deployment web was refused — 1 field is owned by another field manager.
|
|
73
|
+
|
|
74
|
+
"kubectl" owns:
|
|
75
|
+
.spec.replicas
|
|
76
|
+
|
|
77
|
+
chant applied as field manager "chant:behold-k3d-demo". Taking these fields means the managers above
|
|
78
|
+
stop owning them, and will contest them again on their next apply.
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
— naming the contested path and the competing manager, and leaving the field
|
|
82
|
+
alone rather than force-resolving it. (One honest gap found running this
|
|
83
|
+
live: a **new** out-of-band key with no chant-declared counterpart at all —
|
|
84
|
+
`kubectl label deployment/web team=platform`, which has no corresponding
|
|
85
|
+
`web.spec` field in `src/web.ts` — doesn't currently surface in `fieldDrift`,
|
|
86
|
+
only a value change to a field chant *does* declare does. Worth a follow-up
|
|
87
|
+
issue on the chant side; noted here rather than papered over.)
|
|
88
|
+
|
|
89
|
+
**Unobserved.** `chant.config.ts` binds environment `local` to kubectl context
|
|
90
|
+
`k3d-behold-k3d-demo` (chant#1100) — every read/apply checks that binding
|
|
91
|
+
against whatever context is actually ambient, and refuses rather than reading
|
|
92
|
+
the wrong cluster. Switch away from it for a moment —
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
kubectl config use-context <anything-else>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
— and `/api/overlay` flips every node to `_status: neutral` with
|
|
99
|
+
`unobservedReason: "read-failed"`, Pods and all: an honest "did not look",
|
|
100
|
+
never a false "these are all gone." Switch back
|
|
101
|
+
(`kubectl config use-context k3d-behold-k3d-demo`) and the next refresh
|
|
102
|
+
recovers cleanly.
|
|
103
|
+
|
|
104
|
+
## Cleanup
|
|
105
|
+
|
|
106
|
+
`Ctrl-C` on `npm run demo:k8s` runs `scripts/local/local-down.sh`
|
|
107
|
+
automatically: deletes the `behold-k3d-demo` cluster and restores whatever
|
|
108
|
+
kubectl context was current before `local-up.sh` ran. Safe to run by hand too:
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
bash scripts/local/local-down.sh
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Note for chant#1179
|
|
115
|
+
|
|
116
|
+
This Op's `delete: "owned-only"` exercises the same typed, marker-scoped
|
|
117
|
+
prune path chant#1179 wants a live k3d E2E for. This demo isn't that test —
|
|
118
|
+
it's a manual walkthrough, not an automated assertion of "exactly the orphan
|
|
119
|
+
and nothing else was deleted" — but it does confirm the path runs against a
|
|
120
|
+
real cluster, not just `fakeCluster`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ChantConfig } from "@intentius/chant";
|
|
2
|
+
import type { K8sChantConfig } from "@intentius/chant-lexicon-k8s";
|
|
3
|
+
|
|
4
|
+
// Bound to the demo's own cluster (scripts/local/local-up.sh creates it, k3d
|
|
5
|
+
// names the context "k3d-behold-k3d-demo") rather than whatever kubectl
|
|
6
|
+
// context happens to be ambient — chant#1100. A declared binding is checked
|
|
7
|
+
// against the ambient context on every live read/apply; a mismatch refuses
|
|
8
|
+
// loudly instead of silently reading the wrong cluster. The walkthrough's
|
|
9
|
+
// "unobserved" step (README.md) is exactly that refusal, produced on purpose
|
|
10
|
+
// by switching away from this context for a moment.
|
|
11
|
+
export default {
|
|
12
|
+
lexicons: ["k8s", "temporal"],
|
|
13
|
+
sourceDir: "src",
|
|
14
|
+
environments: ["local"],
|
|
15
|
+
ownership: { stack: "behold-k3d-demo", env: "local" },
|
|
16
|
+
k8s: {
|
|
17
|
+
profiles: {
|
|
18
|
+
local: { context: "k3d-behold-k3d-demo" },
|
|
19
|
+
},
|
|
20
|
+
} satisfies K8sChantConfig,
|
|
21
|
+
} satisfies ChantConfig;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ApplyOp } from "@intentius/chant-lexicon-temporal";
|
|
2
|
+
|
|
3
|
+
// code → local k3d (chant#704), no cloud account. scripts/local/local-up.sh
|
|
4
|
+
// (run by `npm run demo:k8s` before behold starts serving) brings the cluster
|
|
5
|
+
// up; behold's Run button on this Op deploys the declared app to it —
|
|
6
|
+
// build → plan (live diff) → server-side apply, field manager
|
|
7
|
+
// "chant:behold-k3d-demo" (chant#1074/#1075). Deletes are owned-only: a
|
|
8
|
+
// marker-scoped prune that never touches anything chant didn't declare — the
|
|
9
|
+
// same path chant#1179 wants a live E2E for; this Op exercises it, but isn't
|
|
10
|
+
// that test.
|
|
11
|
+
const { op } = ApplyOp({
|
|
12
|
+
name: "k3d-apply",
|
|
13
|
+
env: "local",
|
|
14
|
+
target: "kubectl",
|
|
15
|
+
output: "app.yaml",
|
|
16
|
+
delete: "owned-only",
|
|
17
|
+
});
|
|
18
|
+
export default op;
|