7ddfd51139
Create legion-config.jsonc beside local state and route its DNS TTL, recursive resolver, and ACME directory settings through runtime services.
140 lines
3.8 KiB
TypeScript
140 lines
3.8 KiB
TypeScript
import assert from "node:assert/strict"
|
|
import test from "node:test"
|
|
|
|
import { watchMaterialization } from "../../src/main/ops/materialization-watcher"
|
|
import type {
|
|
ResourceReconcileOutcome,
|
|
ResourceSupervisor
|
|
} from "../../src/main/resources/base/supervisor"
|
|
import type {
|
|
ResourceActorSnapshot,
|
|
ResourceActorType
|
|
} from "../../src/main/resources/base/snapshots"
|
|
import { defaultLegionOperatorConfig } from "../../src/main/operator-config"
|
|
import type { StoredState } from "../../src/shared/app"
|
|
|
|
const testOperatorConfig = defaultLegionOperatorConfig()
|
|
|
|
function createState(): StoredState {
|
|
return {
|
|
scheme: {
|
|
servers: [{ id: "node-a", provider: "hetzner", flavor: "cluster" }],
|
|
domains: [],
|
|
dnsZones: [{ id: "zone-1", zoneName: "example.test", provider: "ovh", records: [] }],
|
|
dnsHosts: []
|
|
},
|
|
trackedServers: [],
|
|
clusterMembership: []
|
|
} as unknown as StoredState
|
|
}
|
|
|
|
function resourceSnapshot(input: {
|
|
actorType: ResourceActorType
|
|
kind: ResourceActorSnapshot["kind"]
|
|
id: string
|
|
healthy: boolean
|
|
}): ResourceActorSnapshot {
|
|
return {
|
|
key: `${input.actorType}:${input.id}`,
|
|
actorType: input.actorType,
|
|
kind: input.kind,
|
|
id: input.id,
|
|
label: input.id,
|
|
state: input.healthy ? "healthy" : "absent",
|
|
health: input.healthy ? "healthy" : "planned",
|
|
desired: { present: true },
|
|
runtime: { present: input.healthy },
|
|
actual: { present: input.healthy }
|
|
}
|
|
}
|
|
|
|
test("watchMaterialization converges servers before touching DNS zones", async () => {
|
|
const calls: Array<ResourceActorType[] | undefined> = []
|
|
const deferred: ResourceActorType[][] = []
|
|
let serverHealthy = false
|
|
let dnsZoneHealthy = false
|
|
|
|
const supervisor = {
|
|
syncFromState() {
|
|
return undefined
|
|
},
|
|
async withReactiveEntityReconciliationDeferred(
|
|
types: ResourceActorType[],
|
|
callback: () => Promise<void>
|
|
) {
|
|
deferred.push(types)
|
|
await callback()
|
|
},
|
|
async reconcileAll(
|
|
_runContext: unknown,
|
|
actorTypes?: ResourceActorType[]
|
|
): Promise<ResourceReconcileOutcome[]> {
|
|
calls.push(actorTypes)
|
|
if (!actorTypes || actorTypes.includes("node")) {
|
|
serverHealthy = true
|
|
}
|
|
if (!actorTypes || actorTypes.includes("dns-zone")) {
|
|
dnsZoneHealthy = true
|
|
}
|
|
|
|
return [
|
|
...(actorTypes?.includes("node") || !actorTypes
|
|
? [
|
|
{
|
|
type: "node" as const,
|
|
id: "node-a",
|
|
label: "node-a",
|
|
status: "healthy" as const
|
|
}
|
|
]
|
|
: []),
|
|
...(actorTypes?.includes("dns-zone") || !actorTypes
|
|
? [
|
|
{
|
|
type: "dns-zone" as const,
|
|
id: "zone-1",
|
|
label: "zone-1",
|
|
status: "healthy" as const
|
|
}
|
|
]
|
|
: [])
|
|
]
|
|
},
|
|
getSnapshot() {
|
|
return {
|
|
servers: [
|
|
resourceSnapshot({
|
|
actorType: "node",
|
|
kind: "server",
|
|
id: "node-a",
|
|
healthy: serverHealthy
|
|
})
|
|
],
|
|
domains: [],
|
|
dnsZones: [
|
|
resourceSnapshot({
|
|
actorType: "dns-zone",
|
|
kind: "dns-zone",
|
|
id: "zone-1",
|
|
healthy: dnsZoneHealthy
|
|
})
|
|
],
|
|
firewalls: []
|
|
}
|
|
}
|
|
} as unknown as ResourceSupervisor
|
|
|
|
const result = await watchMaterialization({
|
|
previousState: createState(),
|
|
getState: createState,
|
|
resourceSupervisor: supervisor,
|
|
logger: {} as never,
|
|
appendLog: async () => undefined,
|
|
dnsRecordTtlSeconds: testOperatorConfig.dns.recordTtlSeconds
|
|
})
|
|
|
|
assert.deepEqual(deferred, [["dns-zone"]])
|
|
assert.deepEqual(calls, [["node", "firewall"], undefined])
|
|
assert.deepEqual(result.failures, [])
|
|
})
|