mirror of
https://github.com/tale/headplane.git
synced 2026-08-05 19:57:42 +00:00
0512565f8e
Apparently I didn't use my brain cells and rely on the /version endpoint that Headscale has exposed since 0.26 (our lowest supported version). Switching to that significantly simplifies the API surface.
83 lines
2.3 KiB
TypeScript
83 lines
2.3 KiB
TypeScript
import { createInterface } from "node:readline";
|
|
|
|
import tc from "testcontainers";
|
|
|
|
// Imported by `env.ts` which owns the canonical version list; keep this
|
|
// loose to avoid an import cycle.
|
|
export type Version = string;
|
|
|
|
export interface TailscaleNodeEnv {
|
|
container: tc.StartedTestContainer;
|
|
authCode: string;
|
|
nodeName: string;
|
|
}
|
|
|
|
export async function startTailscaleNode(
|
|
version: Version,
|
|
headscalePort: number,
|
|
): Promise<TailscaleNodeEnv> {
|
|
let resolveAuthCode!: (code: string) => void;
|
|
let rejectAuthCode!: (err: Error) => void;
|
|
let authCodeResolved = false;
|
|
|
|
const authCodePromise = new Promise<string>((resolve, reject) => {
|
|
resolveAuthCode = resolve;
|
|
rejectAuthCode = reject;
|
|
});
|
|
|
|
const nodeName = `test-node-${version.replace(/\./g, "-")}`;
|
|
const prefix = `http://localhost:8080/register/`;
|
|
const container = await new tc.GenericContainer("tailscale/tailscale:latest")
|
|
.withNetworkMode("host")
|
|
.withEnvironment({
|
|
TS_STATE_DIR: "/tailscale-state",
|
|
TS_EXTRA_ARGS: [
|
|
`--login-server=http://localhost:${headscalePort}`,
|
|
`--hostname=${nodeName}`,
|
|
"--accept-dns=false",
|
|
"--accept-routes=false",
|
|
].join(" "),
|
|
})
|
|
.withLogConsumer((stream) => {
|
|
const rl = createInterface({ input: stream });
|
|
|
|
rl.on("line", (line: string) => {
|
|
if (authCodeResolved) return;
|
|
const idx = line.indexOf(prefix);
|
|
if (idx === -1) return;
|
|
|
|
const after = line.slice(idx + prefix.length).trim();
|
|
if (!after) return;
|
|
|
|
const token = after.split(/\s+/)[0];
|
|
if (!token) return;
|
|
|
|
authCodeResolved = true;
|
|
resolveAuthCode(token);
|
|
rl.close();
|
|
});
|
|
|
|
rl.on("close", () => {
|
|
if (!authCodeResolved) {
|
|
rejectAuthCode(
|
|
new Error("Tailscale container log stream closed before auth code was found"),
|
|
);
|
|
}
|
|
});
|
|
})
|
|
.withWaitStrategy(tc.Wait.forLogMessage(prefix).withStartupTimeout(30_000))
|
|
.start();
|
|
|
|
const authCode = await Promise.race<string>([
|
|
authCodePromise,
|
|
new Promise((_, reject) =>
|
|
setTimeout(
|
|
() => reject(new Error(`Timed out waiting for Tailscale auth URL on ${prefix}`)),
|
|
25_000,
|
|
),
|
|
),
|
|
]);
|
|
|
|
return { container, authCode, nodeName };
|
|
}
|