@jr2/orchestrator 0.3.0 → 0.4.1
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/bin/server.ts +7 -4
- package/console/canvas.ts +27 -14
- package/console/components/app.ts +10 -1
- package/console/components/fleet.ts +39 -6
- package/console/components/machine-pane.ts +89 -2
- package/console/components/nav.ts +1 -1
- package/console/components/schema-form.ts +12 -4
- package/console/json-field.ts +23 -0
- package/console/main.ts +110 -39
- package/console/store.ts +221 -17
- package/console/style.css +90 -0
- package/package.json +4 -4
- package/src/actor.ts +502 -49
- package/src/ambient.ts +2 -0
- package/src/assembly-checks.ts +165 -0
- package/src/assembly-tar.ts +494 -0
- package/src/assembly.ts +721 -0
- package/src/chart.ts +2050 -0
- package/src/charts.ts +720 -0
- package/src/config.ts +152 -46
- package/src/content-hash.ts +105 -0
- package/src/custodian.lua +6 -5
- package/src/custodian.ts +71 -19
- package/src/customize.ts +216 -25
- package/src/effect-checks.ts +557 -0
- package/src/effect.ts +905 -0
- package/src/effector-wire.ts +525 -0
- package/src/fingerprint.ts +4 -3
- package/src/gate.ts +8 -0
- package/src/git-clone.ts +65 -0
- package/src/git-push.ts +336 -0
- package/src/harness-client.ts +245 -23
- package/src/held-secrets.ts +82 -26
- package/src/holds.ts +156 -0
- package/src/http-effect.ts +981 -0
- package/src/http.ts +496 -104
- package/src/images.ts +47 -21
- package/src/index.ts +43 -2
- package/src/instance.ts +444 -58
- package/src/kube-client.ts +333 -0
- package/src/machine-doc.ts +88 -6
- package/src/mount-digest.ts +53 -0
- package/src/names.ts +66 -8
- package/src/notices.ts +88 -0
- package/src/open-body.ts +1444 -0
- package/src/open-effect.ts +144 -0
- package/src/open.ts +4 -4
- package/src/parts.ts +530 -33
- package/src/party.ts +468 -0
- package/src/pick.ts +309 -0
- package/src/pool.ts +1 -1
- package/src/registration.ts +301 -17
- package/src/repo-fetch.ts +60 -85
- package/src/repos.ts +63 -74
- package/src/rest.ts +133 -0
- package/src/run-host.ts +1832 -98
- package/src/sandbox-effect.ts +891 -0
- package/src/sandbox-effector.ts +111 -0
- package/src/sandbox-kube.ts +1719 -0
- package/src/sandbox-watch.ts +260 -0
- package/src/secret-placement.ts +494 -0
- package/src/server.ts +281 -84
- package/src/setup.ts +589 -70
- package/src/size.ts +342 -0
- package/src/snapshot-store.ts +64 -13
- package/src/stub-harness.ts +5 -0
- package/src/sync-client.ts +54 -0
- package/src/timers.ts +178 -0
- package/src/tokens.ts +35 -4
- package/src/vocabulary.ts +66 -1
- package/src/wire.ts +156 -2
- package/src/workspace.ts +529 -194
- package/src/sandbox-kubectl.ts +0 -1108
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// A Workspace's Sandbox Effector, as `jr2 up` declares it and the provision makes it (ADR-0068,
|
|
2
|
+
// ADR-0059): the same split as the Custodian's. `jr2 up` reads the Machines, places each Effect
|
|
3
|
+
// secret, and writes two objects per Workspace that places an Effect there:
|
|
4
|
+
//
|
|
5
|
+
// - the Secret `jr2-effector-<pod>`: each literal Effect secret, and `effector.json` (the targets
|
|
6
|
+
// its file-body https call, and whether it holds a credentialed command);
|
|
7
|
+
// - the manifest `effector-<pod>.json` in `jr2-held`: names only, never a value — the projected
|
|
8
|
+
// sources that make the Effector's `/run/jr2/secrets` (that Secret's keys, and each `valueFrom`
|
|
9
|
+
// key of the user's own Secret by reference), and its pushes by Repo Slot.
|
|
10
|
+
//
|
|
11
|
+
// The provision reads the pod's manifest, as it reads its Held manifest, and adds the `effector`
|
|
12
|
+
// container only when one is there: a Workspace with no such Effect gets no Sandbox Effector and no Secret.
|
|
13
|
+
// For each push it matches the slot's url to a `git.credentials` entry and names that entry's push
|
|
14
|
+
// Secret on the Sandbox CR (git-push.ts, ADR-0051); the operator projects it at `@git/<name>/` in the
|
|
15
|
+
// same mount — a directory no secret's file can take, since `@` is no character of a secret's name
|
|
16
|
+
// (SECRET_NAME). The per-pod key is derived from the Sandbox's name at provision (`effectorKey`),
|
|
17
|
+
// as the Sandbox token is, and written into a Secret the Sandbox CR owns.
|
|
18
|
+
|
|
19
|
+
import { readFile } from "node:fs/promises";
|
|
20
|
+
import type { SandboxEffectorPlan } from "./secret-placement.ts";
|
|
21
|
+
|
|
22
|
+
/** The Secret `jr2 up` writes per Workspace for its Sandbox Effector. */
|
|
23
|
+
export const sandboxEffectorSecret = (workspacePod: string): string => `jr2-effector-${workspacePod}`;
|
|
24
|
+
/** The `jr2-held` key of one Workspace's Sandbox Effector manifest. */
|
|
25
|
+
export const effectorManifestKey = (workspacePod: string): string => `effector-${workspacePod}.json`;
|
|
26
|
+
/** The Secret the provision writes per Sandbox with its Effector's key, owned by the CR. */
|
|
27
|
+
export const sandboxEffectorKeySecret = (sandbox: string): string => `${sandbox}-effector`;
|
|
28
|
+
/**
|
|
29
|
+
* The headless Service the provision writes per Sandbox for its Effector, owned by the CR: how the
|
|
30
|
+
* Orchestrator reaches it. Its name resolves to the pod's own address, which a request is signed
|
|
31
|
+
* for (`POD_IP:port`, ADR-0068); the operator's Service names the Harness's port only. A Sandbox's
|
|
32
|
+
* name always starts `ws-` and is at most 63 characters (`workspaceName`), so `fx-` in its place
|
|
33
|
+
* keeps the name unique, as long, and apart from the operator's Service.
|
|
34
|
+
*/
|
|
35
|
+
export const sandboxEffectorService = (sandbox: string): string =>
|
|
36
|
+
sandbox.startsWith("ws-") ? `fx-${sandbox.slice(3)}` : `fx-${sandbox}`.slice(0, 63);
|
|
37
|
+
/** The label every `jr2-effector-<pod>` Secret carries: its pod key, so a stale one is found. */
|
|
38
|
+
export const LABEL_EFFECTOR_POD = "jr2.dev/effector-pod";
|
|
39
|
+
|
|
40
|
+
/** One projected-volume source: a Secret's keys at paths. Kubernetes' own shape. */
|
|
41
|
+
export type ProjectedSecret = { secret: { name: string; items: Array<{ key: string; path: string }> } };
|
|
42
|
+
|
|
43
|
+
/** What the provision reads for one Workspace's Sandbox Effector. Names only. */
|
|
44
|
+
export type SandboxEffectorManifest = {
|
|
45
|
+
version: 1;
|
|
46
|
+
/** The Secret that holds `effector.json` (and the literals). */
|
|
47
|
+
secret: string;
|
|
48
|
+
/** The sources of `/run/jr2/secrets`. */
|
|
49
|
+
sources: ProjectedSecret[];
|
|
50
|
+
/** Its pushes, by Repo Slot: the provision names each one's entry Secret on the Sandbox CR.
|
|
51
|
+
* `squash`: an unsigned squash, which needs the entry's committer (ADR-0067). */
|
|
52
|
+
pushes?: Array<{ slot: string; sign: boolean; squash?: true }>;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const secretKey = (name: string): string => `secret.${name}`;
|
|
56
|
+
/**
|
|
57
|
+
* Where the push credentials sit in `/run/jr2/secrets`, which the operator projects from each
|
|
58
|
+
* entry Secret the Sandbox CR names: a secret's file is its name, and a name holds only
|
|
59
|
+
* `[A-Za-z0-9._-]`, so no secret — one named `git` included — takes this directory.
|
|
60
|
+
*/
|
|
61
|
+
export const SANDBOX_EFFECTOR_GIT_DIR = "@git";
|
|
62
|
+
|
|
63
|
+
/** The two objects `jr2 up` writes for one plan: the Secret's data and the manifest. */
|
|
64
|
+
export function sandboxEffectorObjectsOf(plan: SandboxEffectorPlan): {
|
|
65
|
+
secret: { name: string; stringData: Record<string, string> };
|
|
66
|
+
manifest: SandboxEffectorManifest;
|
|
67
|
+
} {
|
|
68
|
+
const name = sandboxEffectorSecret(plan.pod);
|
|
69
|
+
const config = {
|
|
70
|
+
version: 1,
|
|
71
|
+
placement: "sandbox",
|
|
72
|
+
targets: plan.targets,
|
|
73
|
+
...(plan.commands ? { commands: true } : {}),
|
|
74
|
+
};
|
|
75
|
+
const stringData: Record<string, string> = { "effector.json": JSON.stringify(config, null, 2) };
|
|
76
|
+
const own: Array<{ key: string; path: string }> = [];
|
|
77
|
+
for (const s of plan.secrets) {
|
|
78
|
+
if (s.source.kind !== "literal") continue;
|
|
79
|
+
stringData[secretKey(s.name)] = plan.values[s.name] ?? "";
|
|
80
|
+
own.push({ key: secretKey(s.name), path: s.name });
|
|
81
|
+
}
|
|
82
|
+
const byRef = new Map<string, Array<{ key: string; path: string }>>();
|
|
83
|
+
const add = (secret: string, key: string, path: string) =>
|
|
84
|
+
byRef.set(secret, [...(byRef.get(secret) ?? []), { key, path }]);
|
|
85
|
+
for (const s of plan.secrets) if (s.source.kind === "secret") add(s.source.secret, s.source.key, s.name);
|
|
86
|
+
const sources: ProjectedSecret[] = [
|
|
87
|
+
...(own.length ? [{ secret: { name, items: own } }] : []),
|
|
88
|
+
...[...byRef].map(([secret, items]) => ({ secret: { name: secret, items } })),
|
|
89
|
+
];
|
|
90
|
+
const pushes = plan.pushes.map(({ slot, sign, squash }) => ({ slot, sign, ...(squash ? { squash } : {}) }));
|
|
91
|
+
return {
|
|
92
|
+
secret: { name, stringData },
|
|
93
|
+
manifest: { version: 1, secret: name, sources, ...(pushes.length ? { pushes } : {}) },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The pod's manifest, or undefined when `jr2 up` placed no Effect in its Sandbox Effector. */
|
|
98
|
+
export async function readSandboxEffectorManifest(path: string): Promise<SandboxEffectorManifest | undefined> {
|
|
99
|
+
let raw: string;
|
|
100
|
+
try {
|
|
101
|
+
raw = await readFile(path, "utf8");
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") return undefined;
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
const parsed = JSON.parse(raw) as SandboxEffectorManifest;
|
|
107
|
+
if (parsed.version !== 1 || typeof parsed.secret !== "string" || !Array.isArray(parsed.sources)) {
|
|
108
|
+
throw new Error(`${path} is not a Sandbox Effector manifest jr2 reads (version 1)`);
|
|
109
|
+
}
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|