feat: bump headscale minimum to 0.27

This commit is contained in:
Aarnav Tale
2026-05-30 17:32:00 -04:00
parent 7901f37002
commit d7f1d665a4
11 changed files with 101 additions and 112 deletions
+3 -4
View File
@@ -24,10 +24,9 @@ describe.for(HS_VERSIONS)("Headscale %s: Runtime Client", (version) => {
expect(bootstrapper.capabilities.preAuthKeysHaveStableIds).toBe(gte(v, "0.28.0"));
expect(bootstrapper.capabilities.nodeTagsAreFlat).toBe(gte(v, "0.28.0"));
expect(bootstrapper.capabilities.nodeOwnerIsImmutable).toBe(gte(v, "0.28.0"));
expect(bootstrapper.capabilities.policyErrorsUseModernFormat).toBe(gte(v, "0.27.0"));
// The known version table only has 0.26+; if a future version is added
// before this test is updated, surface that explicitly rather than passing.
const known: Version[] = ["0.26.1", "0.27.0", "0.27.1", "0.28.0"];
// If a future version is added to HS_VERSIONS before this test is
// updated, surface that explicitly rather than passing silently.
const known: Version[] = ["0.27.0", "0.27.1", "0.28.0"];
if (!known.includes(version)) {
context.skip();
}
+1 -1
View File
@@ -6,7 +6,7 @@ import { startTailscaleNode, TailscaleNodeEnv } from "./start-tailscale";
// The set of Headscale versions integration tests run against. Listed
// explicitly (rather than derived from a generated manifest) so the
// supported version matrix lives next to the code that uses it.
export const HS_VERSIONS = ["0.26.1", "0.27.0", "0.27.1", "0.28.0"] as const;
export const HS_VERSIONS = ["0.27.0", "0.27.1", "0.28.0"] as const;
export type Version = (typeof HS_VERSIONS)[number];
interface VersionStateEntry {
@@ -77,7 +77,29 @@ describe("createHeadscale boot-time resilience", () => {
// Should not throw, and should default to "unknown" with permissive caps.
expect(headscale.version.unknown).toBe(true);
expect(headscale.capabilities.preAuthKeysHaveStableIds).toBe(true);
expect(headscale.capabilities.policyErrorsUseModernFormat).toBe(true);
await headscale.dispose();
});
test("a 404 from /version is treated as below the supported floor and keeps retrying", async () => {
const probe = await startVersionServer(() => new Response("not found", { status: 404 }));
const headscale = await createHeadscale({ url: probe.url, retryIntervalMs: 25 });
// 404 = pre-0.27 Headscale. We do NOT settle on an inferred version;
// capabilities stay permissive and the background retry keeps probing
// so an in-place upgrade is picked up without a Headplane restart.
expect(headscale.version.unknown).toBe(true);
probe.setResponse(() =>
Response.json({ version: "v0.28.0", commit: "x", buildTime: "", go: "", dirty: false }),
);
const deadline = Date.now() + 2000;
while (Date.now() < deadline && headscale.version.unknown) {
await new Promise((r) => setTimeout(r, 25));
}
expect(headscale.version.unknown).toBe(false);
expect(headscale.version.raw).toBe("v0.28.0");
await headscale.dispose();
});
@@ -100,7 +122,6 @@ describe("createHeadscale boot-time resilience", () => {
expect(headscale.version.unknown).toBe(false);
expect(headscale.version.raw).toBe("v0.27.1");
expect(headscale.capabilities.preAuthKeysHaveStableIds).toBe(false);
expect(headscale.capabilities.policyErrorsUseModernFormat).toBe(true);
await headscale.dispose();
});
+1 -11
View File
@@ -82,7 +82,6 @@ describe("capabilitiesFor", () => {
preAuthKeysHaveStableIds: true,
nodeTagsAreFlat: true,
nodeOwnerIsImmutable: true,
policyErrorsUseModernFormat: true,
});
});
@@ -92,19 +91,10 @@ describe("capabilitiesFor", () => {
);
});
test("0.27.1 only has the policy-error format flag", () => {
test("0.27.1 lacks every 0.28-gated capability", () => {
const caps = capabilitiesFor(parseServerVersion("0.27.1"));
expect(caps.preAuthKeysHaveStableIds).toBe(false);
expect(caps.nodeTagsAreFlat).toBe(false);
expect(caps.nodeOwnerIsImmutable).toBe(false);
expect(caps.policyErrorsUseModernFormat).toBe(true);
});
test("0.26.1 has none of the modern capabilities", () => {
const caps = capabilitiesFor(parseServerVersion("0.26.1"));
expect(caps.preAuthKeysHaveStableIds).toBe(false);
expect(caps.nodeTagsAreFlat).toBe(false);
expect(caps.nodeOwnerIsImmutable).toBe(false);
expect(caps.policyErrorsUseModernFormat).toBe(false);
});
});