@spunto/build 0.1.0 → 0.2.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 +22 -0
- package/package.json +5 -1
- package/src/index.ts +1 -0
- package/src/naming/index.ts +105 -0
package/README.md
CHANGED
|
@@ -53,6 +53,28 @@ const forCodeServer = codeServerGallery(rawBlob) // the same choice, handed to t
|
|
|
53
53
|
environment variable — and every other function takes its result. Nothing is memoized at import
|
|
54
54
|
time, so one process can serve several tenants on several registries.
|
|
55
55
|
|
|
56
|
+
### `@spunto/build/naming` — the names both sides have to pronounce the same way
|
|
57
|
+
|
|
58
|
+
A worker is a container called `mp-worker-<id>`, with volumes and a network named after it, running
|
|
59
|
+
an image called `mp-proj-<slug>:v<n>`. None of that is an implementation detail — each name is a
|
|
60
|
+
contract between the script that writes it, the Docker client that looks it up and the UI that
|
|
61
|
+
shows it:
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { workerContainerName, workerVolumePrefix, projectImageRef } from "@spunto/build/naming"
|
|
65
|
+
|
|
66
|
+
docker.getContainer(workerContainerName(workerId))
|
|
67
|
+
const ref = projectImageRef(projectId, version) // mp-proj-my-app:v7
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Also here: `workerNetworkName`, `workerVolumeName`, `isProjectImageRef`, `SHARED_NETWORK`, and the
|
|
71
|
+
paths a worker and its control plane agree on — `WORKER_STATUS_FILE` / `workerStatusPath`,
|
|
72
|
+
`EXTENSIONS_DIR`, `FEATURE_ENTRYPOINTS_DIR`, `EXTENSION_FAILURES_FILE`.
|
|
73
|
+
|
|
74
|
+
**These are formats, not preferences.** Changing a value renames things that already exist on
|
|
75
|
+
running machines: a live worker's container and volumes keep the name they were created with. Read
|
|
76
|
+
a diff in that file as a migration, not a rename.
|
|
77
|
+
|
|
56
78
|
## The rule
|
|
57
79
|
|
|
58
80
|
A module belongs in this package only if it imports **no** ORM schema, **no** HTTP framework, **no**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spunto/build",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Spunto's shared Build engine \u2014 the devcontainer image protocol and VS Code extension registry clients, with no database, no HTTP framework and no UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"./extensions": {
|
|
35
35
|
"types": "./src/extensions/index.ts",
|
|
36
36
|
"import": "./src/extensions/index.ts"
|
|
37
|
+
},
|
|
38
|
+
"./naming": {
|
|
39
|
+
"types": "./src/naming/index.ts",
|
|
40
|
+
"import": "./src/naming/index.ts"
|
|
37
41
|
}
|
|
38
42
|
},
|
|
39
43
|
"files": [
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// The names both control planes have to pronounce the same way.
|
|
2
|
+
//
|
|
3
|
+
// A worker is a container called `mp-worker-<id>`, with volumes named after it, on a network named
|
|
4
|
+
// after it, running an image called `mp-proj-<slug>:v<n>`, writing its progress to a file at a
|
|
5
|
+
// fixed path. None of that is an implementation detail: each name is a contract between the shell
|
|
6
|
+
// script that writes it, the Docker client that looks it up, and the UI that shows it.
|
|
7
|
+
//
|
|
8
|
+
// Until now none of them had a single definition. `mp-worker-<id>` was rebuilt by hand in fourteen
|
|
9
|
+
// places across two apps — and a typo in one of them doesn't fail to compile, it produces a
|
|
10
|
+
// container nobody can find at runtime. Spunto Lite rebuilds the same names again, on its side.
|
|
11
|
+
//
|
|
12
|
+
// **These are formats, not preferences.** Changing a value here renames things that already exist
|
|
13
|
+
// on real machines: a running worker's container and volumes keep the name they were created with.
|
|
14
|
+
// Treat every constant below as versioned protocol, the same way `../steps` treats its markers.
|
|
15
|
+
//
|
|
16
|
+
// The `mp-` prefix is historical (the product was called something else) and is deliberately left
|
|
17
|
+
// alone: it is baked into containers, volumes and images that are running right now.
|
|
18
|
+
|
|
19
|
+
/** Container that *is* the worker. */
|
|
20
|
+
export function workerContainerName(workerId: string): string {
|
|
21
|
+
return `mp-worker-${workerId}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Per-worker bridge network. Isolates each worker at L3 so no worker can reach another worker's
|
|
26
|
+
* container IP directly, bypassing the proxy.
|
|
27
|
+
*/
|
|
28
|
+
export function workerNetworkName(workerId: string): string {
|
|
29
|
+
return `mp-worker-${workerId}-net`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The named volumes a worker owns, and what each holds. */
|
|
33
|
+
export const WORKER_VOLUME_KINDS = ["workspace", "home", "docker", "containerd"] as const
|
|
34
|
+
export type WorkerVolumeKind = (typeof WORKER_VOLUME_KINDS)[number]
|
|
35
|
+
|
|
36
|
+
/** One of a worker's named volumes — `workspace` for the code, `docker`/`containerd` for DinD. */
|
|
37
|
+
export function workerVolumeName(workerId: string, kind: WorkerVolumeKind): string {
|
|
38
|
+
return `mp-worker-${workerId}-${kind}`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Prefix every volume of one worker shares. Teardown and disk accounting both scan by prefix rather
|
|
43
|
+
* than by listing kinds, so that a volume added later is still found by code written before it.
|
|
44
|
+
*/
|
|
45
|
+
export function workerVolumePrefix(workerId: string): string {
|
|
46
|
+
return `mp-worker-${workerId}-`
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Image a project's workers boot from, keyed by the project's *version* — see the image cache in
|
|
51
|
+
* docs/architecture-deep-dive.md. The slug is derived, not stored: an image ref may only hold
|
|
52
|
+
* lowercase alphanumerics and separators, and a project id is not guaranteed to.
|
|
53
|
+
*/
|
|
54
|
+
export function projectImageRef(projectId: string, version: number): string {
|
|
55
|
+
const slug = projectId
|
|
56
|
+
.toLowerCase()
|
|
57
|
+
.replace(/[^a-z0-9]/g, "-")
|
|
58
|
+
.replace(/-+/g, "-")
|
|
59
|
+
.replace(/^-|-$/g, "")
|
|
60
|
+
return `mp-proj-${slug}:v${version}`
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** True for an image this platform built, as opposed to a base image it pulled. */
|
|
64
|
+
export function isProjectImageRef(ref: string): boolean {
|
|
65
|
+
return /^mp-proj-[a-z0-9-]+:v\d+$/.test(ref)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Network every shared service joins under its slug, and every worker joins at spawn, so a
|
|
70
|
+
* workspace reaches `http://<slug>:<port>` by DNS. Spunto Lite's "shared services"; the local
|
|
71
|
+
* counterpart of the Ship pillar.
|
|
72
|
+
*/
|
|
73
|
+
export const SHARED_NETWORK = "mp-shared-net"
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Where a worker reports its own setup progress, read back by the control plane.
|
|
77
|
+
*
|
|
78
|
+
* Relative to the user's home rather than absolute: the remote user is a project setting, so the
|
|
79
|
+
* absolute path is only known once you know who the worker runs as — hence `workerStatusPath`.
|
|
80
|
+
*/
|
|
81
|
+
export const WORKER_STATUS_FILE = ".mp-status.json"
|
|
82
|
+
|
|
83
|
+
/** Absolute path of the status file for a given home directory, e.g. `/home/vscode`. */
|
|
84
|
+
export function workerStatusPath(home: string): string {
|
|
85
|
+
return `${home.replace(/\/+$/, "")}/${WORKER_STATUS_FILE}`
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* VS Code extensions are installed into the *image*, at a system path — not into a home, which is
|
|
90
|
+
* per worker and would make every worker re-download them.
|
|
91
|
+
*/
|
|
92
|
+
export const EXTENSIONS_DIR = "/opt/mp-extensions"
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Directory of executables a devcontainer feature can drop to have them run at container start.
|
|
96
|
+
* The devcontainer `entrypoint` contract, implemented on our side.
|
|
97
|
+
*/
|
|
98
|
+
export const FEATURE_ENTRYPOINTS_DIR = "/etc/mp-feature-entrypoints"
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Extensions the image build could not install, one id per line. Left in the image so a worker can
|
|
102
|
+
* tell what is missing without anyone re-reading the build log — see `EXTENSION_FAILED_MARKER` in
|
|
103
|
+
* `../steps`, which is the same information on its way out.
|
|
104
|
+
*/
|
|
105
|
+
export const EXTENSION_FAILURES_FILE = "/etc/mp-extension-failures"
|