@intentius/chant 0.121.0 → 0.121.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/dist/op/emulator-lifecycle.d.ts +47 -0
- package/dist/op/emulator-lifecycle.d.ts.map +1 -1
- package/dist/op/index.d.ts +2 -2
- package/dist/op/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/op/emulator-lifecycle.test.ts +140 -2
- package/src/op/emulator-lifecycle.ts +142 -7
- package/src/op/index.ts +2 -2
|
@@ -79,6 +79,53 @@ export interface EmulatorIdentity {
|
|
|
79
79
|
* `localhost` connection meant for the emulator.
|
|
80
80
|
*/
|
|
81
81
|
export declare function hostPortInUse(port: number, timeoutMs?: number): Promise<boolean>;
|
|
82
|
+
/** One host address Docker publishes a container port on, as `docker port` prints it. */
|
|
83
|
+
export interface PublishedAddress {
|
|
84
|
+
/** The bound host IP: `0.0.0.0`, `::`, `127.0.0.1`, ... */
|
|
85
|
+
ip: string;
|
|
86
|
+
port: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Parse `docker port <name> <port>/tcp` output (`0.0.0.0:5432`, `[::]:5432`,
|
|
90
|
+
* one per line). Lines that do not parse are dropped.
|
|
91
|
+
*/
|
|
92
|
+
export declare function parsePublishedAddresses(stdout: string): PublishedAddress[];
|
|
93
|
+
/**
|
|
94
|
+
* The hosts a client on this machine dials to reach a published address,
|
|
95
|
+
* `localhost` first: a wildcard bind (`0.0.0.0`, `::`) answers on the
|
|
96
|
+
* loopback of its family, and a specific IP only on itself.
|
|
97
|
+
*/
|
|
98
|
+
export declare function clientHosts(addresses: readonly PublishedAddress[]): {
|
|
99
|
+
host: string;
|
|
100
|
+
port: number;
|
|
101
|
+
}[];
|
|
102
|
+
/** `endpoint` with its host replaced by `host` (`localhost` stays as written). */
|
|
103
|
+
export declare function endpointOnHost(endpoint: string, host: string): string;
|
|
104
|
+
/**
|
|
105
|
+
* Pick the endpoint of a reused container that reaches it (the identity read
|
|
106
|
+
* inside the container matches the one read through the endpoint), trying
|
|
107
|
+
* {@link clientHosts} in order. `notes` says when `localhost` reaches another
|
|
108
|
+
* server and a different address had to be used; `error` says when none of
|
|
109
|
+
* them reaches the emulator.
|
|
110
|
+
*/
|
|
111
|
+
export declare function pickReachableEndpoint(opts: {
|
|
112
|
+
name: string;
|
|
113
|
+
hosts: readonly {
|
|
114
|
+
host: string;
|
|
115
|
+
port: number;
|
|
116
|
+
}[];
|
|
117
|
+
endpoint: (port: number) => string;
|
|
118
|
+
identity: EmulatorIdentity;
|
|
119
|
+
/** The identity read inside the container. */
|
|
120
|
+
inside: string;
|
|
121
|
+
/** A hint appended to an `error`, e.g. how to republish on a free port. */
|
|
122
|
+
hint: string;
|
|
123
|
+
}): Promise<{
|
|
124
|
+
endpoint: string;
|
|
125
|
+
notes: string[];
|
|
126
|
+
} | {
|
|
127
|
+
error: string;
|
|
128
|
+
}>;
|
|
82
129
|
/**
|
|
83
130
|
* A lexicon's local-emulator capability (#920) — what `chant emulator` needs to
|
|
84
131
|
* boot it and point tooling at it. The `spec` drives the Docker lifecycle; `env`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emulator-lifecycle.d.ts","sourceRoot":"","sources":["../../src/op/emulator-lifecycle.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"emulator-lifecycle.d.ts","sourceRoot":"","sources":["../../src/op/emulator-lifecycle.ts"],"names":[],"mappings":"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"}
|
package/dist/op/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { Op, phase, activity, gate, effect, build, kubectlApply, helmInstall, helmInstallPinned, waitForStack, waitForReady, gitlabPipeline, lifecycleSnapshot, shell, sourceArchive, releasePlan, releaseRecord, composeChangeSet, lifecyclePlanChangeSet, readChangeSetPart, releaseRollbackPlan, releaseRollbackRecord, decide, ensureSecret, teardown, envTeardown, k3dUp, k3dDown, k3sInstall, k3sUninstall, flociUp, flociDown, flociAzUp, flociAzDown, flociGcpUp, flociGcpDown, httpCheck, azGroupEnsure, azGroupDelete, azApply, azDelete, awsApply, awsDelete, gcpApply, gcpDelete, policyGate, guardValidate, workEvidence, spriteCreate, spriteExec, spriteCheckpoint, spriteRestore, listCheckpoints, spriteDestroy, spriteWriteFile, spriteReadFile, spriteListDir, spriteRemove, spriteApplyNetworkPolicy, spriteApplyServices, spriteTaskCreate, spriteTaskRefresh, spriteTaskRelease, spritesUp, spritesDown } from "./builders.js";
|
|
2
2
|
export { OpResource } from "./resource.js";
|
|
3
3
|
export { sleep } from "./activity-runtime.js";
|
|
4
|
-
export { emulatorLifecycle, emulatorsOf, endpointEnvVars, hostPortInUse } from "./emulator-lifecycle.js";
|
|
5
|
-
export type { EmulatorSpec, EmulatorCapability, EmulatorDeclaration, EmulatorIdentity, EmulatorUpArgs, EmulatorLifecycle } from "./emulator-lifecycle.js";
|
|
4
|
+
export { clientHosts, emulatorLifecycle, emulatorsOf, endpointEnvVars, endpointOnHost, hostPortInUse, parsePublishedAddresses, pickReachableEndpoint } from "./emulator-lifecycle.js";
|
|
5
|
+
export type { EmulatorSpec, EmulatorCapability, EmulatorDeclaration, EmulatorIdentity, EmulatorUpArgs, EmulatorLifecycle, PublishedAddress } from "./emulator-lifecycle.js";
|
|
6
6
|
export { checkFreshness, compare, formatResult, latestRelease, parseVersion, unpinned } from "./emulator-freshness.js";
|
|
7
7
|
export type { FreshnessResult } from "./emulator-freshness.js";
|
|
8
8
|
export type { OpConfig, OpSchedule, OpWorkLease, PhaseDefinition, StepDefinition, ActivityStep, GateStep, EffectStep, OutcomeAttribute } from "./types.js";
|
package/dist/op/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/op/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAClH,cAAc,EAAE,iBAAiB,EAAE,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EACzP,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAC5C,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,SAAS,EAC3D,aAAa,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EACrG,aAAa,EAAE,YAAY,EAC3B,YAAY,EAAE,UAAU,EAAE,gBAAgB,EAAE,aAAa,EAAE,eAAe,EAAE,aAAa,EACzF,eAAe,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAC5D,wBAAwB,EAAE,mBAAmB,EAC7C,gBAAgB,EAAE,iBAAiB,EAAE,iBAAiB,EACtD,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/op/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,33 @@
|
|
|
1
|
-
import { describe, test, expect } from "vitest";
|
|
1
|
+
import { describe, test, expect, vi, beforeEach } from "vitest";
|
|
2
2
|
import { createServer } from "node:net";
|
|
3
|
-
import {
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import type { EmulatorIdentity } from "./emulator-lifecycle";
|
|
5
|
+
|
|
6
|
+
// `up` reads `docker ps` through exec and `docker port` / `docker exec` through
|
|
7
|
+
// execFile; drive their output so the reuse path runs without Docker.
|
|
8
|
+
let dockerPs = "";
|
|
9
|
+
let dockerPort = "";
|
|
10
|
+
let insideId = "";
|
|
11
|
+
const execCalls: string[] = [];
|
|
12
|
+
const execStub = Object.assign(() => {}, {
|
|
13
|
+
[promisify.custom]: (cmd: string) => {
|
|
14
|
+
execCalls.push(cmd);
|
|
15
|
+
return Promise.resolve({ stdout: cmd.startsWith("docker ps") ? dockerPs : "" });
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
const execFileStub = Object.assign(() => {}, {
|
|
19
|
+
[promisify.custom]: (_file: string, argv: string[]) =>
|
|
20
|
+
argv[0] === "port"
|
|
21
|
+
? (dockerPort ? Promise.resolve({ stdout: dockerPort }) : Promise.reject(new Error("no public port")))
|
|
22
|
+
: Promise.resolve({ stdout: `${insideId}\n` }),
|
|
23
|
+
});
|
|
24
|
+
vi.mock("node:child_process", async () => {
|
|
25
|
+
const actual = await vi.importActual<typeof import("node:child_process")>("node:child_process");
|
|
26
|
+
return { ...actual, exec: execStub, execFile: execFileStub };
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const { clientHosts, emulatorLifecycle, endpointOnHost, hostPortInUse, parsePublishedAddresses, pickReachableEndpoint } =
|
|
30
|
+
await import("./emulator-lifecycle");
|
|
4
31
|
|
|
5
32
|
describe("emulatorLifecycle command builders", () => {
|
|
6
33
|
const emu = emulatorLifecycle({
|
|
@@ -74,3 +101,114 @@ describe("hostPortInUse (#3673)", () => {
|
|
|
74
101
|
expect(await hostPortInUse(port)).toBe(false);
|
|
75
102
|
});
|
|
76
103
|
});
|
|
104
|
+
|
|
105
|
+
describe("a reused container's real address", () => {
|
|
106
|
+
test("parsePublishedAddresses reads docker port output, IPv4 and IPv6", () => {
|
|
107
|
+
expect(parsePublishedAddresses("0.0.0.0:15432\n[::]:15432\n")).toEqual([
|
|
108
|
+
{ ip: "0.0.0.0", port: 15432 },
|
|
109
|
+
{ ip: "::", port: 15432 },
|
|
110
|
+
]);
|
|
111
|
+
expect(parsePublishedAddresses("")).toEqual([]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("clientHosts dials localhost first, then the IPv6 loopback and specific IPs", () => {
|
|
115
|
+
expect(clientHosts([{ ip: "::", port: 5432 }, { ip: "0.0.0.0", port: 5432 }])).toEqual([
|
|
116
|
+
{ host: "localhost", port: 5432 },
|
|
117
|
+
{ host: "[::1]", port: 5432 },
|
|
118
|
+
]);
|
|
119
|
+
expect(clientHosts([{ ip: "192.168.5.2", port: 5433 }])).toEqual([{ host: "192.168.5.2", port: 5433 }]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("endpointOnHost swaps only the host", () => {
|
|
123
|
+
expect(endpointOnHost("postgres://localhost:5432/postgres", "[::1]")).toBe("postgres://[::1]:5432/postgres");
|
|
124
|
+
expect(endpointOnHost("http://localhost:8123", "10.0.0.4")).toBe("http://10.0.0.4:8123");
|
|
125
|
+
expect(endpointOnHost("http://localhost:8123", "localhost")).toBe("http://localhost:8123");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const identity = (answers: Record<string, string | Error>): EmulatorIdentity => ({
|
|
129
|
+
server: "Postgres",
|
|
130
|
+
command: ["id"],
|
|
131
|
+
async probe(endpoint) {
|
|
132
|
+
const answer = answers[new URL(endpoint).host];
|
|
133
|
+
if (answer === undefined || answer instanceof Error) throw answer ?? new Error("connection refused");
|
|
134
|
+
return { id: answer, label: answer === "emu" ? "PostgreSQL 18.1" : "PostgreSQL 15.4" };
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
const endpoint = (port: number) => `postgres://localhost:${port}/postgres`;
|
|
138
|
+
|
|
139
|
+
test("pickReachableEndpoint names the address that works when localhost reaches another server", async () => {
|
|
140
|
+
const picked = await pickReachableEndpoint({
|
|
141
|
+
name: "chant-postgres",
|
|
142
|
+
hosts: [{ host: "localhost", port: 5432 }, { host: "[::1]", port: 5432 }],
|
|
143
|
+
endpoint,
|
|
144
|
+
identity: identity({ "localhost:5432": "native", "[::1]:5432": "emu" }),
|
|
145
|
+
inside: "emu",
|
|
146
|
+
hint: "",
|
|
147
|
+
});
|
|
148
|
+
expect(picked).toEqual({
|
|
149
|
+
endpoint: "postgres://[::1]:5432/postgres",
|
|
150
|
+
notes: [
|
|
151
|
+
'emulator "chant-postgres": localhost:5432 reaches another Postgres (PostgreSQL 15.4), not the emulator. ' +
|
|
152
|
+
"The emulator answers on postgres://[::1]:5432/postgres; use that address.",
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("pickReachableEndpoint fails plainly when no published address reaches the emulator", async () => {
|
|
158
|
+
const picked = await pickReachableEndpoint({
|
|
159
|
+
name: "chant-postgres",
|
|
160
|
+
hosts: [{ host: "localhost", port: 5432 }],
|
|
161
|
+
endpoint,
|
|
162
|
+
identity: identity({ "localhost:5432": "native" }),
|
|
163
|
+
inside: "emu",
|
|
164
|
+
hint: "Republish it.",
|
|
165
|
+
});
|
|
166
|
+
expect(picked).toEqual({
|
|
167
|
+
error: 'emulator "chant-postgres" runs, but no address it publishes reaches it: localhost:5432 reaches another Postgres (PostgreSQL 15.4), not the emulator. Republish it.',
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe("up, reusing a running container", () => {
|
|
172
|
+
beforeEach(() => {
|
|
173
|
+
dockerPs = "abc123\n";
|
|
174
|
+
dockerPort = "";
|
|
175
|
+
insideId = "emu";
|
|
176
|
+
execCalls.length = 0;
|
|
177
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
178
|
+
});
|
|
179
|
+
const spec = (answers: Record<string, string | Error>) => ({
|
|
180
|
+
name: "chant-postgres",
|
|
181
|
+
image: "postgres:18",
|
|
182
|
+
containerPort: 5432,
|
|
183
|
+
readyCommand: ["pg_isready"],
|
|
184
|
+
endpoint,
|
|
185
|
+
identity: identity(answers),
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("reports the port Docker publishes, not the default", async () => {
|
|
189
|
+
dockerPort = "0.0.0.0:15432\n";
|
|
190
|
+
const lc = emulatorLifecycle(spec({ "localhost:15432": "emu" }));
|
|
191
|
+
expect(await lc.up({ intervalMs: 1 })).toEqual({ endpoint: "postgres://localhost:15432/postgres" });
|
|
192
|
+
expect(execCalls.some((c) => c.startsWith("docker run"))).toBe(false);
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
test("prints the working address when another server holds localhost", async () => {
|
|
196
|
+
dockerPort = "0.0.0.0:5432\n[::]:5432\n";
|
|
197
|
+
const err: string[] = [];
|
|
198
|
+
vi.spyOn(console, "error").mockImplementation((s: string) => { err.push(s); });
|
|
199
|
+
const lc = emulatorLifecycle(spec({ "localhost:5432": "native", "[::1]:5432": "emu" }));
|
|
200
|
+
expect(await lc.up({ intervalMs: 1 })).toEqual({ endpoint: "postgres://[::1]:5432/postgres" });
|
|
201
|
+
expect(err.join("\n")).toContain("localhost:5432 reaches another Postgres (PostgreSQL 15.4), not the emulator");
|
|
202
|
+
expect(err.at(-1)).toBe('emulator "chant-postgres" ready on postgres://[::1]:5432/postgres');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test("refuses when no address reaches the emulator, or nothing is published", async () => {
|
|
206
|
+
dockerPort = "0.0.0.0:5432\n";
|
|
207
|
+
await expect(emulatorLifecycle(spec({ "localhost:5432": "native" })).up({ intervalMs: 1 }))
|
|
208
|
+
.rejects.toThrow(/no address it publishes reaches it: localhost:5432 reaches another Postgres/);
|
|
209
|
+
dockerPort = "";
|
|
210
|
+
await expect(emulatorLifecycle(spec({})).up({ intervalMs: 1 }))
|
|
211
|
+
.rejects.toThrow(/publishes no host port for 5432/);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
1
|
+
import { exec, execFile } from "node:child_process";
|
|
2
2
|
import { connect } from "node:net";
|
|
3
3
|
import { promisify } from "node:util";
|
|
4
4
|
import { sleep } from "./activity-runtime";
|
|
5
5
|
|
|
6
6
|
const execAsync = promisify(exec);
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* A local emulator's fixed identity — everything that differs between the
|
|
@@ -97,6 +98,97 @@ export function hostPortInUse(port: number, timeoutMs = 1_000): Promise<boolean>
|
|
|
97
98
|
});
|
|
98
99
|
}
|
|
99
100
|
|
|
101
|
+
/** One host address Docker publishes a container port on, as `docker port` prints it. */
|
|
102
|
+
export interface PublishedAddress {
|
|
103
|
+
/** The bound host IP: `0.0.0.0`, `::`, `127.0.0.1`, ... */
|
|
104
|
+
ip: string;
|
|
105
|
+
port: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Parse `docker port <name> <port>/tcp` output (`0.0.0.0:5432`, `[::]:5432`,
|
|
110
|
+
* one per line). Lines that do not parse are dropped.
|
|
111
|
+
*/
|
|
112
|
+
export function parsePublishedAddresses(stdout: string): PublishedAddress[] {
|
|
113
|
+
const out: PublishedAddress[] = [];
|
|
114
|
+
for (const line of stdout.split("\n")) {
|
|
115
|
+
const match = /^\s*(?:\[([^\]]+)\]|([^\s:]+)):(\d+)\s*$/.exec(line);
|
|
116
|
+
if (match) out.push({ ip: (match[1] ?? match[2])!, port: Number(match[3]) });
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* The hosts a client on this machine dials to reach a published address,
|
|
123
|
+
* `localhost` first: a wildcard bind (`0.0.0.0`, `::`) answers on the
|
|
124
|
+
* loopback of its family, and a specific IP only on itself.
|
|
125
|
+
*/
|
|
126
|
+
export function clientHosts(addresses: readonly PublishedAddress[]): { host: string; port: number }[] {
|
|
127
|
+
const seen = new Set<string>();
|
|
128
|
+
const hosts: { host: string; port: number }[] = [];
|
|
129
|
+
const add = (host: string, port: number): void => {
|
|
130
|
+
const key = `${host}:${port}`;
|
|
131
|
+
if (seen.has(key)) return;
|
|
132
|
+
seen.add(key);
|
|
133
|
+
hosts.push({ host, port });
|
|
134
|
+
};
|
|
135
|
+
for (const { ip, port } of addresses) {
|
|
136
|
+
if (ip === "0.0.0.0" || ip === "127.0.0.1") add("localhost", port);
|
|
137
|
+
}
|
|
138
|
+
for (const { ip, port } of addresses) {
|
|
139
|
+
if (ip === "::" || ip === "::1") add("[::1]", port);
|
|
140
|
+
else if (ip !== "0.0.0.0" && ip !== "127.0.0.1") add(ip.includes(":") ? `[${ip}]` : ip, port);
|
|
141
|
+
}
|
|
142
|
+
return hosts;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** `endpoint` with its host replaced by `host` (`localhost` stays as written). */
|
|
146
|
+
export function endpointOnHost(endpoint: string, host: string): string {
|
|
147
|
+
if (host === "localhost") return endpoint;
|
|
148
|
+
const url = new URL(endpoint);
|
|
149
|
+
url.hostname = host;
|
|
150
|
+
return url.toString().replace(/\/$/, endpoint.endsWith("/") ? "/" : "");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Pick the endpoint of a reused container that reaches it (the identity read
|
|
155
|
+
* inside the container matches the one read through the endpoint), trying
|
|
156
|
+
* {@link clientHosts} in order. `notes` says when `localhost` reaches another
|
|
157
|
+
* server and a different address had to be used; `error` says when none of
|
|
158
|
+
* them reaches the emulator.
|
|
159
|
+
*/
|
|
160
|
+
export async function pickReachableEndpoint(opts: {
|
|
161
|
+
name: string;
|
|
162
|
+
hosts: readonly { host: string; port: number }[];
|
|
163
|
+
endpoint: (port: number) => string;
|
|
164
|
+
identity: EmulatorIdentity;
|
|
165
|
+
/** The identity read inside the container. */
|
|
166
|
+
inside: string;
|
|
167
|
+
/** A hint appended to an `error`, e.g. how to republish on a free port. */
|
|
168
|
+
hint: string;
|
|
169
|
+
}): Promise<{ endpoint: string; notes: string[] } | { error: string }> {
|
|
170
|
+
const { name, hosts, identity, inside } = opts;
|
|
171
|
+
const misses: string[] = [];
|
|
172
|
+
for (const { host, port } of hosts) {
|
|
173
|
+
const ep = endpointOnHost(opts.endpoint(port), host);
|
|
174
|
+
try {
|
|
175
|
+
const answered = await identity.probe(ep);
|
|
176
|
+
if (answered.id.trim() === inside) {
|
|
177
|
+
const notes = misses.length
|
|
178
|
+
? [`emulator "${name}": ${misses.join("; ")}. The emulator answers on ${ep}; use that address.`]
|
|
179
|
+
: [];
|
|
180
|
+
return { endpoint: ep, notes };
|
|
181
|
+
}
|
|
182
|
+
misses.push(`${host}:${port} reaches another ${identity.server} (${answered.label}), not the emulator`);
|
|
183
|
+
} catch (err) {
|
|
184
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
185
|
+
misses.push(`${host}:${port} does not answer as the emulator (${why})`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const tried = misses.length ? `: ${misses.join("; ")}` : "";
|
|
189
|
+
return { error: `emulator "${name}" runs, but no address it publishes reaches it${tried}. ${opts.hint}` };
|
|
190
|
+
}
|
|
191
|
+
|
|
100
192
|
/**
|
|
101
193
|
* A lexicon's local-emulator capability (#920) — what `chant emulator` needs to
|
|
102
194
|
* boot it and point tooling at it. The `spec` drives the Docker lifecycle; `env`
|
|
@@ -198,7 +290,7 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
|
198
290
|
spec.readyCommand?.length ? ["docker", "exec", name, ...spec.readyCommand].join(" ") : undefined;
|
|
199
291
|
|
|
200
292
|
/** One readiness probe: the in-container command when declared, else the HTTP health path. */
|
|
201
|
-
async function probe(name: string, port: number, signal?: AbortSignal): Promise<boolean> {
|
|
293
|
+
async function probe(name: string, port: number, signal?: AbortSignal, host = "localhost"): Promise<boolean> {
|
|
202
294
|
const command = readyExecCommand(name);
|
|
203
295
|
if (command) {
|
|
204
296
|
try {
|
|
@@ -209,7 +301,7 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
|
209
301
|
}
|
|
210
302
|
}
|
|
211
303
|
try {
|
|
212
|
-
const res = await fetch(healthUrl(port), { signal });
|
|
304
|
+
const res = await fetch(endpointOnHost(healthUrl(port), host), { signal });
|
|
213
305
|
return res.ok && ready(await res.text());
|
|
214
306
|
} catch {
|
|
215
307
|
// Not up yet (connection refused / non-2xx) — retry.
|
|
@@ -231,10 +323,33 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
|
231
323
|
// `docker ps` failed — assume not running and try to start it.
|
|
232
324
|
}
|
|
233
325
|
|
|
326
|
+
// A reused container publishes what it was started with, which need not
|
|
327
|
+
// be `port` on localhost: read it from Docker rather than assume it.
|
|
328
|
+
let hosts: { host: string; port: number }[] = [{ host: "localhost", port }];
|
|
234
329
|
if (running) {
|
|
235
330
|
// Progress → stderr, so a `--json` consumer (chant emulator, behold) reads
|
|
236
331
|
// clean JSON on stdout. runOp/Op activities capture both streams regardless.
|
|
237
332
|
console.error(`emulator container "${name}" already running — reusing`);
|
|
333
|
+
let published: PublishedAddress[] = [];
|
|
334
|
+
try {
|
|
335
|
+
const { stdout } = await execFileAsync("docker", ["port", name, `${spec.containerPort}/tcp`], { signal });
|
|
336
|
+
published = parsePublishedAddresses(stdout);
|
|
337
|
+
} catch {
|
|
338
|
+
// `docker port` fails when nothing is published.
|
|
339
|
+
}
|
|
340
|
+
if (published.length === 0) {
|
|
341
|
+
throw new Error(
|
|
342
|
+
`emulator "${name}" runs, but publishes no host port for ${spec.containerPort}, so nothing on this machine reaches it. ` +
|
|
343
|
+
`Run \`chant emulator down\`, then \`chant emulator up\`.`,
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
hosts = clientHosts(published);
|
|
347
|
+
if (args.port !== undefined && !hosts.some((h) => h.port === args.port)) {
|
|
348
|
+
console.error(
|
|
349
|
+
`emulator "${name}" was started on host port ${hosts[0]!.port}, not ${args.port}; reusing it there. ` +
|
|
350
|
+
`Run \`chant emulator down\` first to move it.`,
|
|
351
|
+
);
|
|
352
|
+
}
|
|
238
353
|
} else {
|
|
239
354
|
if (await hostPortInUse(port)) {
|
|
240
355
|
throw new Error(
|
|
@@ -249,17 +364,37 @@ export function emulatorLifecycle(spec: EmulatorSpec): EmulatorLifecycle {
|
|
|
249
364
|
let ok = false;
|
|
250
365
|
while (Date.now() < deadline) {
|
|
251
366
|
if (signal?.aborted) throw new Error(`emulator "${name}" wait aborted`);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
367
|
+
for (const h of hosts) {
|
|
368
|
+
if (await probe(name, h.port, signal, h.host)) {
|
|
369
|
+
ok = true;
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
255
372
|
}
|
|
373
|
+
if (ok) break;
|
|
256
374
|
await sleep(intervalMs, signal);
|
|
257
375
|
}
|
|
258
376
|
if (!ok) {
|
|
259
377
|
throw new Error(`emulator "${name}" did not become ready within ${timeoutMs}ms`);
|
|
260
378
|
}
|
|
261
379
|
|
|
262
|
-
|
|
380
|
+
let ep = endpointOnHost(endpoint(hosts[0]!.port), hosts[0]!.host);
|
|
381
|
+
if (running && spec.identity) {
|
|
382
|
+
let inside: string | undefined;
|
|
383
|
+
try {
|
|
384
|
+
inside = (await execFileAsync("docker", ["exec", name, ...spec.identity.command], { signal })).stdout.trim();
|
|
385
|
+
} catch {
|
|
386
|
+
// The container cannot answer for itself; nothing to compare against.
|
|
387
|
+
}
|
|
388
|
+
if (inside) {
|
|
389
|
+
const picked = await pickReachableEndpoint({
|
|
390
|
+
name, hosts, endpoint, identity: spec.identity, inside,
|
|
391
|
+
hint: `Stop the other server, or run \`chant emulator down\`, then \`chant emulator up --port ${spec.containerPort}=<free port>\`.`,
|
|
392
|
+
});
|
|
393
|
+
if ("error" in picked) throw new Error(picked.error);
|
|
394
|
+
for (const note of picked.notes) console.error(note);
|
|
395
|
+
ep = picked.endpoint;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
263
398
|
console.error(`emulator "${name}" ready on ${ep}`);
|
|
264
399
|
return { endpoint: ep };
|
|
265
400
|
}
|
package/src/op/index.ts
CHANGED
|
@@ -11,8 +11,8 @@ export { Op, phase, activity, gate, effect, build, kubectlApply, helmInstall, he
|
|
|
11
11
|
spritesUp, spritesDown } from "./builders";
|
|
12
12
|
export { OpResource } from "./resource";
|
|
13
13
|
export { sleep } from "./activity-runtime";
|
|
14
|
-
export { emulatorLifecycle, emulatorsOf, endpointEnvVars, hostPortInUse } from "./emulator-lifecycle";
|
|
15
|
-
export type { EmulatorSpec, EmulatorCapability, EmulatorDeclaration, EmulatorIdentity, EmulatorUpArgs, EmulatorLifecycle } from "./emulator-lifecycle";
|
|
14
|
+
export { clientHosts, emulatorLifecycle, emulatorsOf, endpointEnvVars, endpointOnHost, hostPortInUse, parsePublishedAddresses, pickReachableEndpoint } from "./emulator-lifecycle";
|
|
15
|
+
export type { EmulatorSpec, EmulatorCapability, EmulatorDeclaration, EmulatorIdentity, EmulatorUpArgs, EmulatorLifecycle, PublishedAddress } from "./emulator-lifecycle";
|
|
16
16
|
export { checkFreshness, compare, formatResult, latestRelease, parseVersion, unpinned } from "./emulator-freshness";
|
|
17
17
|
export type { FreshnessResult } from "./emulator-freshness";
|
|
18
18
|
export type { OpConfig, OpSchedule, OpWorkLease, PhaseDefinition, StepDefinition, ActivityStep, GateStep, EffectStep, OutcomeAttribute } from "./types";
|