redkite 0.1.6
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/LICENSE +21 -0
- package/README.md +819 -0
- package/bin/redkite.mjs +12 -0
- package/dist/build.d.ts +27 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +132 -0
- package/dist/build.js.map +1 -0
- package/dist/checks.d.ts +4 -0
- package/dist/checks.d.ts.map +1 -0
- package/dist/checks.js +49 -0
- package/dist/checks.js.map +1 -0
- package/dist/cli/agent.d.ts +2 -0
- package/dist/cli/agent.d.ts.map +1 -0
- package/dist/cli/agent.js +24 -0
- package/dist/cli/agent.js.map +1 -0
- package/dist/cli/config.d.ts +5 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +217 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +438 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/log.d.ts +9 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +128 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/screen.d.ts +34 -0
- package/dist/cli/screen.d.ts.map +1 -0
- package/dist/cli/screen.js +298 -0
- package/dist/cli/screen.js.map +1 -0
- package/dist/cli/viewer.d.ts +9 -0
- package/dist/cli/viewer.d.ts.map +1 -0
- package/dist/cli/viewer.js +139 -0
- package/dist/cli/viewer.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/deploy.d.ts +19 -0
- package/dist/deploy.d.ts.map +1 -0
- package/dist/deploy.js +275 -0
- package/dist/deploy.js.map +1 -0
- package/dist/docker.d.ts +96 -0
- package/dist/docker.d.ts.map +1 -0
- package/dist/docker.js +325 -0
- package/dist/docker.js.map +1 -0
- package/dist/dockerfile.d.ts +14 -0
- package/dist/dockerfile.d.ts.map +1 -0
- package/dist/dockerfile.js +160 -0
- package/dist/dockerfile.js.map +1 -0
- package/dist/health.d.ts +13 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +54 -0
- package/dist/health.js.map +1 -0
- package/dist/host.d.ts +21 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +27 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/layout.d.ts +5 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +45 -0
- package/dist/layout.js.map +1 -0
- package/dist/localHost.d.ts +7 -0
- package/dist/localHost.d.ts.map +1 -0
- package/dist/localHost.js +32 -0
- package/dist/localHost.js.map +1 -0
- package/dist/log.d.ts +14 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +15 -0
- package/dist/log.js.map +1 -0
- package/dist/nginx.d.ts +4 -0
- package/dist/nginx.d.ts.map +1 -0
- package/dist/nginx.js +53 -0
- package/dist/nginx.js.map +1 -0
- package/dist/pipeline.d.ts +103 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +142 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/presets/index.d.ts +3 -0
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +3 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/presets/nextApp.d.ts +15 -0
- package/dist/presets/nextApp.d.ts.map +1 -0
- package/dist/presets/nextApp.js +53 -0
- package/dist/presets/nextApp.js.map +1 -0
- package/dist/presets/nodeApp.d.ts +19 -0
- package/dist/presets/nodeApp.d.ts.map +1 -0
- package/dist/presets/nodeApp.js +29 -0
- package/dist/presets/nodeApp.js.map +1 -0
- package/dist/secrets/bitwarden.d.ts +3 -0
- package/dist/secrets/bitwarden.d.ts.map +1 -0
- package/dist/secrets/bitwarden.js +6 -0
- package/dist/secrets/bitwarden.js.map +1 -0
- package/dist/secrets/refs.d.ts +9 -0
- package/dist/secrets/refs.d.ts.map +1 -0
- package/dist/secrets/refs.js +20 -0
- package/dist/secrets/refs.js.map +1 -0
- package/dist/secrets/store.d.ts +9 -0
- package/dist/secrets/store.d.ts.map +1 -0
- package/dist/secrets/store.js +104 -0
- package/dist/secrets/store.js.map +1 -0
- package/dist/services/ensure.d.ts +18 -0
- package/dist/services/ensure.d.ts.map +1 -0
- package/dist/services/ensure.js +93 -0
- package/dist/services/ensure.js.map +1 -0
- package/dist/services/index.d.ts +19 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +27 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/planned.d.ts +20 -0
- package/dist/services/planned.d.ts.map +1 -0
- package/dist/services/planned.js +77 -0
- package/dist/services/planned.js.map +1 -0
- package/dist/shell.d.ts +11 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +65 -0
- package/dist/shell.js.map +1 -0
- package/dist/source.d.ts +16 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +108 -0
- package/dist/source.js.map +1 -0
- package/dist/sshHost.d.ts +15 -0
- package/dist/sshHost.d.ts.map +1 -0
- package/dist/sshHost.js +91 -0
- package/dist/sshHost.js.map +1 -0
- package/dist/steps.d.ts +27 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +69 -0
- package/dist/steps.js.map +1 -0
- package/dist/topology.d.ts +40 -0
- package/dist/topology.d.ts.map +1 -0
- package/dist/topology.js +113 -0
- package/dist/topology.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +64 -0
- package/src/build.ts +236 -0
- package/src/checks.ts +65 -0
- package/src/cli/agent.ts +28 -0
- package/src/cli/config.ts +275 -0
- package/src/cli/index.ts +559 -0
- package/src/cli/log.ts +168 -0
- package/src/cli/screen.ts +409 -0
- package/src/cli/viewer.ts +192 -0
- package/src/config.ts +90 -0
- package/src/deploy.ts +409 -0
- package/src/docker.ts +433 -0
- package/src/dockerfile.ts +228 -0
- package/src/health.ts +83 -0
- package/src/host.ts +54 -0
- package/src/index.ts +25 -0
- package/src/layout.ts +41 -0
- package/src/localHost.ts +50 -0
- package/src/log.ts +38 -0
- package/src/nginx.ts +63 -0
- package/src/pipeline.ts +335 -0
- package/src/presets/index.ts +2 -0
- package/src/presets/nextApp.ts +70 -0
- package/src/presets/nodeApp.ts +46 -0
- package/src/secrets/bitwarden.ts +7 -0
- package/src/secrets/refs.ts +32 -0
- package/src/secrets/store.ts +147 -0
- package/src/services/ensure.ts +160 -0
- package/src/services/index.ts +48 -0
- package/src/services/planned.ts +119 -0
- package/src/shell.ts +95 -0
- package/src/source.ts +171 -0
- package/src/sshHost.ts +145 -0
- package/src/steps.ts +101 -0
- package/src/topology.ts +199 -0
- package/src/types.ts +243 -0
package/dist/shell.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { lineReader, tail } from "./host.js";
|
|
3
|
+
// Every child still running. A detached child outlives this process, so a stop
|
|
4
|
+
// that does not go through here leaves a build running
|
|
5
|
+
const live = new Set();
|
|
6
|
+
// Signals every group and answers with how many are still there. The count is
|
|
7
|
+
// the whole point: nothing may exit while it is above zero
|
|
8
|
+
export function signalEverything(name) {
|
|
9
|
+
for (const child of live) {
|
|
10
|
+
try {
|
|
11
|
+
if (child.pid)
|
|
12
|
+
process.kill(-child.pid, name);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// Already gone, which is the outcome being asked for
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return live.size;
|
|
19
|
+
}
|
|
20
|
+
export function stillRunning() {
|
|
21
|
+
return live.size;
|
|
22
|
+
}
|
|
23
|
+
export function spawnCollect(command, args, options = {}) {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
if (options.signal?.aborted)
|
|
26
|
+
return reject(new Error("Stopped"));
|
|
27
|
+
// Its own process group, so one signal reaches everything it started. A
|
|
28
|
+
// shell that ran docker dies on its own otherwise, and docker keeps going
|
|
29
|
+
const child = spawn(command, args, {
|
|
30
|
+
stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"],
|
|
31
|
+
detached: true,
|
|
32
|
+
});
|
|
33
|
+
live.add(child);
|
|
34
|
+
const out = [];
|
|
35
|
+
const err = [];
|
|
36
|
+
const collect = (into) => lineReader((line) => {
|
|
37
|
+
into.push(line);
|
|
38
|
+
options.onLine?.(line);
|
|
39
|
+
});
|
|
40
|
+
const stdout = collect(out);
|
|
41
|
+
const stderr = collect(err);
|
|
42
|
+
// Only a streamed command is truncated. The host snapshot is one command
|
|
43
|
+
// listing every container and image, and a busy host overruns any tail
|
|
44
|
+
const keep = (lines) => (options.onLine ? tail(lines) : lines.join("\n"));
|
|
45
|
+
child.stdout?.on("data", (chunk) => stdout.push(chunk.toString()));
|
|
46
|
+
child.stderr?.on("data", (chunk) => stderr.push(chunk.toString()));
|
|
47
|
+
child.on("error", reject);
|
|
48
|
+
// The only thing that settles this. A signalled command answers here once
|
|
49
|
+
// it has actually gone, which is what lets a caller wait for exactly that
|
|
50
|
+
child.on("close", (code) => {
|
|
51
|
+
live.delete(child);
|
|
52
|
+
stdout.flush();
|
|
53
|
+
stderr.flush();
|
|
54
|
+
resolve({ code: code ?? 1, stdout: keep(out), stderr: keep(err) });
|
|
55
|
+
});
|
|
56
|
+
if (options.stdin !== undefined)
|
|
57
|
+
child.stdin?.end(options.stdin);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Single quotes are what a shell strips, so a value containing double quotes
|
|
61
|
+
// survives being handed to one
|
|
62
|
+
export function quote(value) {
|
|
63
|
+
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.js","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"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"}
|
package/dist/source.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Host } from "./host.js";
|
|
2
|
+
export type Source = {
|
|
3
|
+
release: string;
|
|
4
|
+
tree: string;
|
|
5
|
+
};
|
|
6
|
+
export type SourceRequest = {
|
|
7
|
+
name: string;
|
|
8
|
+
repo?: string;
|
|
9
|
+
path?: string;
|
|
10
|
+
include?: string[];
|
|
11
|
+
branch: string;
|
|
12
|
+
submodules: boolean;
|
|
13
|
+
detail?: (message: string) => void;
|
|
14
|
+
};
|
|
15
|
+
export declare function prepareSource(host: Host, request: SourceRequest): Promise<Source>;
|
|
16
|
+
//# sourceMappingURL=source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAOtC,MAAM,MAAM,MAAM,GAAG;IAGnB,OAAO,EAAE,MAAM,CAAC;IAEhB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAG1B,IAAI,EAAE,MAAM,CAAC;IAEb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IAGd,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC,CAAC;AAKF,wBAAsB,aAAa,CACjC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,MAAM,CAAC,CAKjB"}
|
package/dist/source.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// accept-new rather than no: a host key that changes is still worth refusing
|
|
2
|
+
const GIT_SSH = "ssh -o StrictHostKeyChecking=accept-new";
|
|
3
|
+
export async function prepareSource(host, request) {
|
|
4
|
+
if (request.path)
|
|
5
|
+
return await localSource(host, request.path, request);
|
|
6
|
+
if (request.repo)
|
|
7
|
+
return await clonedSource(host, request.repo, request);
|
|
8
|
+
throw new Error(`${request.name} names neither a repo nor a path to build from`);
|
|
9
|
+
}
|
|
10
|
+
// Built where it sits. Nothing is cloned, checked out or cleaned: what the
|
|
11
|
+
// build reads is the tree as the person running this left it
|
|
12
|
+
async function localSource(host, path, request) {
|
|
13
|
+
const detail = request.detail ?? (() => { });
|
|
14
|
+
detail(`reading ${path}`);
|
|
15
|
+
await assertKnowable(host, path, request.include);
|
|
16
|
+
const release = await treeOf(host, path, request.include);
|
|
17
|
+
detail(`built from ${release.slice(0, 7)}`);
|
|
18
|
+
return { release, tree: path };
|
|
19
|
+
}
|
|
20
|
+
// Something has to say what belongs in the build. A work tree's .gitignore
|
|
21
|
+
// does; anywhere else the deployment has to say it, or the release would cover
|
|
22
|
+
// whatever happened to be lying in the directory
|
|
23
|
+
async function assertKnowable(host, path, include) {
|
|
24
|
+
if (include)
|
|
25
|
+
return;
|
|
26
|
+
const inside = await host.sh(`git -C '${path}' rev-parse --is-inside-work-tree`);
|
|
27
|
+
if (inside.code === 0 && inside.stdout.trim() === "true")
|
|
28
|
+
return;
|
|
29
|
+
throw new Error(`${path} is not a git work tree, so nothing there says what belongs in the ` +
|
|
30
|
+
'build. Say it: include: ["src", "package.json"] on the app names what ' +
|
|
31
|
+
"ships, and git init there would let .gitignore name it instead");
|
|
32
|
+
}
|
|
33
|
+
// git's own content addressing, over a scratch repository so nothing about the
|
|
34
|
+
// source changes and no object lands in it. The tree covers what is committed,
|
|
35
|
+
// what is modified and what is untracked, so an edit that is never committed is
|
|
36
|
+
// a new release. Without an include it honours .gitignore, which is the same
|
|
37
|
+
// set the build reads
|
|
38
|
+
async function treeOf(host, path, include) {
|
|
39
|
+
// Everything the work tree does not ignore, or exactly what was named. The
|
|
40
|
+
// -- is only for the second: it would make -A a path rather than a flag
|
|
41
|
+
const added = include ? `-- ${include.map((item) => `'${item}'`).join(" ")}` : "-A";
|
|
42
|
+
const written = await host.sh([
|
|
43
|
+
"scratch=$(mktemp -d)",
|
|
44
|
+
'git init -q --bare "$scratch/git"',
|
|
45
|
+
`export GIT_DIR="$scratch/git" GIT_WORK_TREE='${path}' GIT_INDEX_FILE="$scratch/index"`,
|
|
46
|
+
`git add ${added}`,
|
|
47
|
+
"git write-tree",
|
|
48
|
+
'rm -rf "$scratch"',
|
|
49
|
+
].join("\n"));
|
|
50
|
+
const release = written.stdout.trim().split("\n").at(-1) ?? "";
|
|
51
|
+
if (/^[0-9a-f]{40}$/.test(release))
|
|
52
|
+
return release;
|
|
53
|
+
throw new Error(`Could not read the state of ${path}: ${written.stderr || written.stdout}`);
|
|
54
|
+
}
|
|
55
|
+
async function clonedSource(host, repo, request) {
|
|
56
|
+
const detail = request.detail ?? (() => { });
|
|
57
|
+
const mirror = `${host.cache}/mirrors/${request.name}.git`;
|
|
58
|
+
const path = `${host.cache}/source/${request.name}`;
|
|
59
|
+
detail(`fetching ${request.branch}`);
|
|
60
|
+
await run(host, `updating the mirror of ${repo}`, [
|
|
61
|
+
`if [ -d '${mirror}' ]; then`,
|
|
62
|
+
` git -C '${mirror}' remote set-url origin '${repo}'`,
|
|
63
|
+
` git -C '${mirror}' remote update --prune`,
|
|
64
|
+
"else",
|
|
65
|
+
` mkdir -p '${host.cache}/mirrors'`,
|
|
66
|
+
` git clone --mirror '${repo}' '${mirror}'`,
|
|
67
|
+
"fi",
|
|
68
|
+
]);
|
|
69
|
+
const resolved = await run(host, `resolving ${request.branch}`, [
|
|
70
|
+
`git -C '${mirror}' rev-parse 'refs/heads/${request.branch}'`,
|
|
71
|
+
]);
|
|
72
|
+
const release = resolved.stdout.trim();
|
|
73
|
+
if (!/^[0-9a-f]{40}$/.test(release)) {
|
|
74
|
+
throw new Error(`${repo} has no branch ${request.branch}`);
|
|
75
|
+
}
|
|
76
|
+
detail(`checking out ${release.slice(0, 7)}`);
|
|
77
|
+
await run(host, `checking out ${release.slice(0, 7)}`, [
|
|
78
|
+
// The checkout shares the mirror's object store, so it costs the working
|
|
79
|
+
// tree and nothing else. Reused between deploys, hence the reset and clean
|
|
80
|
+
`if [ ! -d '${path}/.git' ]; then`,
|
|
81
|
+
` rm -rf '${path}'`,
|
|
82
|
+
` mkdir -p '${host.cache}/source'`,
|
|
83
|
+
` git clone --shared --no-checkout '${mirror}' '${path}'`,
|
|
84
|
+
"fi",
|
|
85
|
+
`git -C '${path}' fetch --prune origin`,
|
|
86
|
+
`git -C '${path}' checkout --detach --force '${release}'`,
|
|
87
|
+
// Leaves the submodules alone: they are tracked, and clean only removes
|
|
88
|
+
// what is not
|
|
89
|
+
`git -C '${path}' clean -ffdx`,
|
|
90
|
+
]);
|
|
91
|
+
if (request.submodules) {
|
|
92
|
+
// --remote follows the branch named in .gitmodules rather than the commit
|
|
93
|
+
// the parent recorded, which is what the pipeline has always done
|
|
94
|
+
detail("updating submodules");
|
|
95
|
+
await run(host, "updating submodules", [
|
|
96
|
+
`git -C '${path}' submodule sync --recursive`,
|
|
97
|
+
`git -C '${path}' submodule update --init --remote --recursive`,
|
|
98
|
+
]);
|
|
99
|
+
}
|
|
100
|
+
return { release, tree: path };
|
|
101
|
+
}
|
|
102
|
+
async function run(host, what, script) {
|
|
103
|
+
const result = await host.sh([`export GIT_SSH_COMMAND='${GIT_SSH}'`, "set -e", ...script].join("\n"));
|
|
104
|
+
if (result.code === 0)
|
|
105
|
+
return result;
|
|
106
|
+
throw new Error(`Failed ${what}: ${result.stderr || result.stdout}`);
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Host, OnLine, Result } from "./host.js";
|
|
2
|
+
export type Ssh = (args: string[], options?: {
|
|
3
|
+
stdin?: string;
|
|
4
|
+
onLine?: OnLine;
|
|
5
|
+
}) => Promise<Result>;
|
|
6
|
+
export type Shell = (command: string) => Promise<Result>;
|
|
7
|
+
export type SshOptions = {
|
|
8
|
+
run?: Ssh;
|
|
9
|
+
shell?: Shell;
|
|
10
|
+
directory?: string;
|
|
11
|
+
cache?: string;
|
|
12
|
+
signal?: AbortSignal;
|
|
13
|
+
};
|
|
14
|
+
export declare function sshHost(bastion: string, options?: SshOptions): Promise<Host>;
|
|
15
|
+
//# sourceMappingURL=sshHost.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sshHost.d.ts","sourceRoot":"","sources":["../src/sshHost.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAQtD,MAAM,MAAM,GAAG,GAAG,CAChB,IAAI,EAAE,MAAM,EAAE,EACd,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,KAC1C,OAAO,CAAC,MAAM,CAAC,CAAC;AAIrB,MAAM,MAAM,KAAK,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,MAAM,UAAU,GAAG;IACvB,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IAGf,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB,CAAC;AAeF,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAkFtF"}
|
package/dist/sshHost.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { quote, spawnCollect } from "./shell.js";
|
|
4
|
+
// One TCP connection and one authentication for the whole deploy. Without it
|
|
5
|
+
// every command pays a handshake, which is most of what a command costs
|
|
6
|
+
const MULTIPLEX = [
|
|
7
|
+
"-o",
|
|
8
|
+
"ControlMaster=auto",
|
|
9
|
+
"-o",
|
|
10
|
+
"ControlPersist=60s",
|
|
11
|
+
"-o",
|
|
12
|
+
"StrictHostKeyChecking=no",
|
|
13
|
+
// The host clones from GitHub on our behalf rather than us shipping a tree
|
|
14
|
+
"-A",
|
|
15
|
+
];
|
|
16
|
+
export async function sshHost(bastion, options = {}) {
|
|
17
|
+
const control = `/tmp/redkite-${randomUUID().slice(0, 8)}.control`;
|
|
18
|
+
const directory = options.directory ?? `/tmp/redkite-${randomUUID().slice(0, 8)}`;
|
|
19
|
+
const signal = options.signal;
|
|
20
|
+
const run = options.run ?? ((args, extra) => spawnCollect("ssh", args, { ...extra, signal }));
|
|
21
|
+
// Cleanup has to survive the abort that made it necessary, so it is the one
|
|
22
|
+
// thing here that is not killed along with everything else
|
|
23
|
+
const final = options.run ?? ((args, extra) => spawnCollect("ssh", args, extra));
|
|
24
|
+
const shell = options.shell ??
|
|
25
|
+
((command) => spawnCollect("sh", ["-c", command], { signal }));
|
|
26
|
+
const argv = (command) => [
|
|
27
|
+
...MULTIPLEX,
|
|
28
|
+
"-o",
|
|
29
|
+
`ControlPath=${control}`,
|
|
30
|
+
bastion,
|
|
31
|
+
command,
|
|
32
|
+
];
|
|
33
|
+
const ssh = (command, extra) => run(argv(command), extra);
|
|
34
|
+
// Each command records the process group job control gave it, because killing
|
|
35
|
+
// the ssh client here leaves what it was carrying running there
|
|
36
|
+
let issued = 0;
|
|
37
|
+
// One round trip for all of it, including the home directory the cache hangs
|
|
38
|
+
// off. A literal ~ would only survive as long as every caller used a shell
|
|
39
|
+
const opened = await ssh(`mkdir -p -m 700 '${directory}' && mkdir -p "$HOME/.cache/redkite" && printf %s "$HOME"`);
|
|
40
|
+
if (opened.code !== 0) {
|
|
41
|
+
throw new Error(`Could not reach ${bastion}: ${opened.stderr || opened.stdout}`);
|
|
42
|
+
}
|
|
43
|
+
const cache = options.cache ?? `${opened.stdout.trim()}/.cache/redkite`;
|
|
44
|
+
return {
|
|
45
|
+
directory,
|
|
46
|
+
cache,
|
|
47
|
+
sh: async (command, onLine) => {
|
|
48
|
+
issued += 1;
|
|
49
|
+
return await ssh(supervised(command, `${directory}/run.${issued}.pid`), { onLine });
|
|
50
|
+
},
|
|
51
|
+
write: async (name, contents) => {
|
|
52
|
+
const path = `${directory}/${name}`;
|
|
53
|
+
const result = await ssh(`mkdir -p '${dirname(path)}' && cat > '${path}'`, {
|
|
54
|
+
stdin: contents,
|
|
55
|
+
});
|
|
56
|
+
if (result.code !== 0) {
|
|
57
|
+
throw new Error(`Could not write ${name}: ${result.stderr}`);
|
|
58
|
+
}
|
|
59
|
+
return path;
|
|
60
|
+
},
|
|
61
|
+
// Through the connection that is already open, so an image is streamed
|
|
62
|
+
// rather than written to a disk at each end and copied between them
|
|
63
|
+
pipe: async (local, remote) => await shell(`${local} | ssh ${argv(remote).map(quote).join(" ")}`),
|
|
64
|
+
// Signalled where they are running, not here. Killing the client would
|
|
65
|
+
// leave the build going with nothing left able to reach it
|
|
66
|
+
stop: async (name) => {
|
|
67
|
+
const result = await final(argv(sweep(directory, name)));
|
|
68
|
+
return Number(result.stdout.trim()) || 0;
|
|
69
|
+
},
|
|
70
|
+
close: async () => {
|
|
71
|
+
await final(argv(`rm -rf '${directory}'`));
|
|
72
|
+
await final(["-o", `ControlPath=${control}`, "-O", "exit", bastion]);
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// Job control puts a background job in a process group of its own, which is the
|
|
77
|
+
// only handle a second connection has on what the first one started. Nothing is
|
|
78
|
+
// written to stdout by this: an announcement would corrupt the host snapshot
|
|
79
|
+
function supervised(command, pidfile) {
|
|
80
|
+
return `set -m; { ${command}; } & __rk=$!; printf %s "$__rk" > '${pidfile}'; wait "$__rk"`;
|
|
81
|
+
}
|
|
82
|
+
// Signals every group this deploy started there, then counts the ones still
|
|
83
|
+
// answering. The count is what the caller waits on: a build that ignores the
|
|
84
|
+
// first signal keeps it above zero until a harder one is sent
|
|
85
|
+
function sweep(directory, name) {
|
|
86
|
+
return (`left=0; for f in '${directory}'/*.pid; do [ -f "$f" ] || continue; ` +
|
|
87
|
+
`p=$(cat "$f" 2>/dev/null) || continue; ` +
|
|
88
|
+
`kill -${name} -"$p" 2>/dev/null; ` +
|
|
89
|
+
`kill -0 -"$p" 2>/dev/null && left=$((left+1)); done; printf %s "$left"`);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=sshHost.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sshHost.js","sourceRoot":"","sources":["../src/sshHost.ts"],"names":[],"mappings":"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"}
|
package/dist/steps.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Built, Step } from "./pipeline.js";
|
|
2
|
+
import type { Topology } from "./topology.js";
|
|
3
|
+
import type { AppSpec, StepNetwork } from "./types.js";
|
|
4
|
+
export declare function attachment(network: StepNetwork, topology: Topology): string[];
|
|
5
|
+
type MigrateOptions = {
|
|
6
|
+
app: string;
|
|
7
|
+
command: string;
|
|
8
|
+
network?: StepNetwork;
|
|
9
|
+
tunnel?: {
|
|
10
|
+
bastion: string;
|
|
11
|
+
from: string;
|
|
12
|
+
alias?: string;
|
|
13
|
+
port?: number;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export declare function migrate(options: MigrateOptions): Step<`swap:before:${string}`>;
|
|
17
|
+
export declare function builderOf(input: Built, name: string): string;
|
|
18
|
+
type SentryOptions = {
|
|
19
|
+
stripFromImage: string;
|
|
20
|
+
};
|
|
21
|
+
export declare function sentry(options: SentryOptions): {
|
|
22
|
+
provider: "sentry";
|
|
23
|
+
stripFromImage: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function routeOf(app: AppSpec): string;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=steps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAQ,IAAI,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAKvD,wBAAgB,UAAU,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAY7E;AAED,KAAK,cAAc,GAAG;IAGpB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAIhB,OAAO,CAAC,EAAE,WAAW,CAAC;IAItB,MAAM,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC3E,CAAC;AAIF,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC,eAAe,MAAM,EAAE,CAAC,CAyB9E;AAqBD,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,UAKnD;AAED,KAAK,aAAa,GAAG;IAAE,cAAc,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhD,wBAAgB,MAAM,CAAC,OAAO,EAAE,aAAa;;;EAE5C;AAED,wBAAgB,OAAO,CAAC,GAAG,EAAE,OAAO,UAEnC"}
|
package/dist/steps.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { environmentOf } from "./config.js";
|
|
2
|
+
// The flags that attach one. The deployment network carries the aliases too,
|
|
3
|
+
// because a service answers to its alias rather than its container name, and a
|
|
4
|
+
// step reaching postgres:5432 is the whole reason to be on that network
|
|
5
|
+
export function attachment(network, topology) {
|
|
6
|
+
if (network === "host")
|
|
7
|
+
return ["--network host"];
|
|
8
|
+
if (network === "none")
|
|
9
|
+
return ["--network none"];
|
|
10
|
+
if (network !== "deployment")
|
|
11
|
+
return [`--network ${network.named}`];
|
|
12
|
+
return [
|
|
13
|
+
// No address is asked for. Docker allocates from the bottom of the subnet
|
|
14
|
+
// and every derived address starts at .20, so an ephemeral container never
|
|
15
|
+
// takes one an app is about to be created on
|
|
16
|
+
`--network ${topology.network}`,
|
|
17
|
+
...Object.entries(topology.extraHosts).map(([name, ip]) => `--add-host ${name}:${ip}`),
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
// An ordinary step at an ordinary point. Hung before the swap, it runs while
|
|
21
|
+
// the old containers still serve, and it throws, so nothing retires
|
|
22
|
+
export function migrate(options) {
|
|
23
|
+
const command = options.command.split(" ");
|
|
24
|
+
return {
|
|
25
|
+
point: `swap:before:migrate-${options.app}`,
|
|
26
|
+
check: (plan) => assertReachable(plan, options),
|
|
27
|
+
run: async (input, context) => {
|
|
28
|
+
const image = builderOf(input, options.app);
|
|
29
|
+
context.task.detail(`${options.app}: ${options.command}`);
|
|
30
|
+
await context.docker.runOrThrow([
|
|
31
|
+
"run --rm",
|
|
32
|
+
...attachment(options.network ?? "host", context.topology),
|
|
33
|
+
"--workdir /app",
|
|
34
|
+
image,
|
|
35
|
+
...command,
|
|
36
|
+
].join(" "), `${options.app} failed to migrate`);
|
|
37
|
+
return input;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// Checked before the run starts, so a config naming an app that never kept a
|
|
42
|
+
// builder fails without having touched the host
|
|
43
|
+
function assertReachable(plan, options) {
|
|
44
|
+
const app = plan.config.apps.find((item) => item.name === options.app);
|
|
45
|
+
if (!app)
|
|
46
|
+
throw new Error(`${options.app} names no app in this deployment`);
|
|
47
|
+
const bastion = environmentOf(plan.config, plan.environment)?.host?.bastion;
|
|
48
|
+
const tunnel = options.tunnel;
|
|
49
|
+
if (!tunnel || tunnel.bastion === bastion)
|
|
50
|
+
return;
|
|
51
|
+
throw new Error(`${options.app} tunnels its migration through ${tunnel.bastion}, ` +
|
|
52
|
+
`but ${plan.environment} deploys to ${bastion ?? "this machine"}. ` +
|
|
53
|
+
"The step runs on the deploy host, so they have to be the same");
|
|
54
|
+
}
|
|
55
|
+
// A deployment may replace the build step, and a command hung on the pipeline
|
|
56
|
+
// has to run in an image something actually produced
|
|
57
|
+
export function builderOf(input, name) {
|
|
58
|
+
const app = input.apps.find((item) => item.name === name);
|
|
59
|
+
if (app?.builderTag)
|
|
60
|
+
return app.builderTag;
|
|
61
|
+
throw new Error(`${name} has no builder image, so nothing can be run in it`);
|
|
62
|
+
}
|
|
63
|
+
export function sentry(options) {
|
|
64
|
+
return { provider: "sentry", stripFromImage: options.stripFromImage };
|
|
65
|
+
}
|
|
66
|
+
export function routeOf(app) {
|
|
67
|
+
return app.route;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=steps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.js","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Deployment } from "./types.js";
|
|
2
|
+
export type AppTopology = {
|
|
3
|
+
name: string;
|
|
4
|
+
container: string;
|
|
5
|
+
retired: string;
|
|
6
|
+
failed: string;
|
|
7
|
+
currentAddress: string;
|
|
8
|
+
retiredAddress: string;
|
|
9
|
+
port: number;
|
|
10
|
+
route: string;
|
|
11
|
+
volumes: {
|
|
12
|
+
volume: string;
|
|
13
|
+
mountPath: string;
|
|
14
|
+
}[];
|
|
15
|
+
caches: Record<string, string>;
|
|
16
|
+
};
|
|
17
|
+
export type ServiceTopology = {
|
|
18
|
+
name: string;
|
|
19
|
+
container: string;
|
|
20
|
+
address: string;
|
|
21
|
+
alias?: string;
|
|
22
|
+
volumes: {
|
|
23
|
+
volume: string;
|
|
24
|
+
mountPath: string;
|
|
25
|
+
}[];
|
|
26
|
+
};
|
|
27
|
+
export type Topology = {
|
|
28
|
+
environment: string;
|
|
29
|
+
branch: string;
|
|
30
|
+
network: string;
|
|
31
|
+
subnet: string;
|
|
32
|
+
cidr: string;
|
|
33
|
+
publicPort?: number;
|
|
34
|
+
router: ServiceTopology;
|
|
35
|
+
apps: AppTopology[];
|
|
36
|
+
services: ServiceTopology[];
|
|
37
|
+
extraHosts: Record<string, string>;
|
|
38
|
+
};
|
|
39
|
+
export declare function topologyFor(config: Deployment, environment: string): Topology;
|
|
40
|
+
//# sourceMappingURL=topology.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topology.d.ts","sourceRoot":"","sources":["../src/topology.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,UAAU,EAAe,MAAM,YAAY,CAAC;AAcnE,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACjD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAClD,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IAEb,UAAU,CAAC,EAAE,MAAM,CAAC;IAGpB,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,WAAW,EAAE,CAAC;IACpB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAE5B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC,CAAC;AAEF,wBAAgB,WAAW,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,GAAG,QAAQ,CAgD7E"}
|
package/dist/topology.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { environmentOf } from "./config.js";
|
|
2
|
+
import { mountFor } from "./layout.js";
|
|
3
|
+
// Every name and address the deployment uses, derived from the app list. This
|
|
4
|
+
// is the file that replaces the constants block: nothing is chosen by hand, so
|
|
5
|
+
// adding an app cannot collide with an address somebody already picked.
|
|
6
|
+
const NGINX_OCTET = 20;
|
|
7
|
+
const APP_BLOCK_START = 21;
|
|
8
|
+
// Services sit in their own block, so adding one never moves an app
|
|
9
|
+
const SERVICE_BLOCK_START = 200;
|
|
10
|
+
export function topologyFor(config, environment) {
|
|
11
|
+
const env = environmentOf(config, environment);
|
|
12
|
+
if (!env) {
|
|
13
|
+
const known = Object.keys(config.environments ?? {});
|
|
14
|
+
// An environment is a file, so having none is a different mistake from
|
|
15
|
+
// asking for one that is not there, and reads as one
|
|
16
|
+
if (known.length === 0) {
|
|
17
|
+
throw new Error(`No environments. Each one is a redkite.<name>.config.ts beside the ` +
|
|
18
|
+
`deployment, so ${environment} wants a redkite.${environment}.config.ts`);
|
|
19
|
+
}
|
|
20
|
+
throw new Error(`Unknown environment ${environment}, expected one of ${known.join(", ")}`);
|
|
21
|
+
}
|
|
22
|
+
const prefix = `${config.project}-${environment}`;
|
|
23
|
+
const address = (octet) => `${env.subnet}.${octet}`;
|
|
24
|
+
const apps = config.apps.map((app, index) => appTopology(app, prefix, environment, address, index));
|
|
25
|
+
const services = config.services.map((service, index) => serviceTopology(service, prefix, address, index));
|
|
26
|
+
return {
|
|
27
|
+
environment,
|
|
28
|
+
branch: env.branch,
|
|
29
|
+
network: `${prefix}-network`,
|
|
30
|
+
subnet: env.subnet,
|
|
31
|
+
cidr: `${env.subnet}.0/16`,
|
|
32
|
+
publicPort: env.publicPort,
|
|
33
|
+
router: {
|
|
34
|
+
name: "nginx",
|
|
35
|
+
container: `${prefix}-nginx`,
|
|
36
|
+
address: address(NGINX_OCTET),
|
|
37
|
+
volumes: [],
|
|
38
|
+
},
|
|
39
|
+
apps,
|
|
40
|
+
services,
|
|
41
|
+
extraHosts: extraHosts(apps, services, env.extraHosts),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function appTopology(app, prefix, environment, address, index) {
|
|
45
|
+
const container = `${prefix}-${app.name}`;
|
|
46
|
+
const base = APP_BLOCK_START + index * 2;
|
|
47
|
+
const volumes = Object.entries(app.volumes ?? {}).map(([name, mountPath]) => ({
|
|
48
|
+
volume: `${container}-${name}`,
|
|
49
|
+
mountPath,
|
|
50
|
+
}));
|
|
51
|
+
// Deduplicated by where each one mounts: an app that is the whole repository
|
|
52
|
+
// resolves the root and its own node_modules to one path, and two ids for one
|
|
53
|
+
// target would be a second cache nothing ever writes to
|
|
54
|
+
const targets = new Set();
|
|
55
|
+
const caches = {};
|
|
56
|
+
for (const cache of app.build.caches) {
|
|
57
|
+
const target = mountFor(cache, app.dir);
|
|
58
|
+
if (targets.has(target))
|
|
59
|
+
continue;
|
|
60
|
+
targets.add(target);
|
|
61
|
+
caches[cache] = `${app.name}-${environment}-${cache}-cache`;
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
name: app.name,
|
|
65
|
+
container,
|
|
66
|
+
retired: `retired-${container}`,
|
|
67
|
+
failed: `failed-${container}`,
|
|
68
|
+
// Retired takes the lower slot, so a rollback never renumbers the live one
|
|
69
|
+
retiredAddress: address(base),
|
|
70
|
+
currentAddress: address(base + 1),
|
|
71
|
+
port: app.port,
|
|
72
|
+
route: app.route,
|
|
73
|
+
volumes,
|
|
74
|
+
caches,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function serviceTopology(service, prefix, address, index) {
|
|
78
|
+
const container = `${prefix}-${service.name}`;
|
|
79
|
+
return {
|
|
80
|
+
name: service.name,
|
|
81
|
+
container,
|
|
82
|
+
// Pinned when a service already exists at a known address
|
|
83
|
+
address: address(service.address ?? SERVICE_BLOCK_START + index),
|
|
84
|
+
alias: service.alias,
|
|
85
|
+
volumes: Object.entries(service.volumes ?? {}).map(([name, mountPath]) => ({
|
|
86
|
+
volume: `${container}-${name}`,
|
|
87
|
+
mountPath,
|
|
88
|
+
})),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Nginx resolves upstreams by container name, apps reach services by alias
|
|
92
|
+
function extraHosts(apps, services, declared = {}) {
|
|
93
|
+
const hosts = {};
|
|
94
|
+
for (const app of apps) {
|
|
95
|
+
hosts[app.container] = app.currentAddress;
|
|
96
|
+
hosts[app.retired] = app.retiredAddress;
|
|
97
|
+
}
|
|
98
|
+
for (const service of services) {
|
|
99
|
+
if (service.alias)
|
|
100
|
+
hosts[service.alias] = service.address;
|
|
101
|
+
}
|
|
102
|
+
// Declared last, but never over a derived one: a name that already resolves
|
|
103
|
+
// to a container in this deployment would send its traffic somewhere else,
|
|
104
|
+
// and the deploy would look like it worked
|
|
105
|
+
for (const [name, address] of Object.entries(declared)) {
|
|
106
|
+
if (name in hosts) {
|
|
107
|
+
throw new Error(`extraHosts names ${name}, which this deployment already resolves to ${hosts[name]}`);
|
|
108
|
+
}
|
|
109
|
+
hosts[name] = address;
|
|
110
|
+
}
|
|
111
|
+
return hosts;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=topology.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topology.js","sourceRoot":"","sources":["../src/topology.ts"],"names":[],"mappings":"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"}
|