diff --git a/app/server/config/integration/docker.ts b/app/server/config/integration/docker.ts index c02cc0f..7e5f1ea 100644 --- a/app/server/config/integration/docker.ts +++ b/app/server/config/integration/docker.ts @@ -16,9 +16,11 @@ interface DockerContainer { interface DockerVersionInfo { ApiVersion?: string; + MinAPIVersion?: string; } -const REQUIRED_DOCKER_API_VERSION = "1.44"; +const TARGET_DOCKER_API_VERSION = "1.44"; +const MIN_DOCKER_API_VERSION = "1.24"; function compareApiVersions(current: string, required: string) { const currentParts = current.split(".").map(Number); @@ -50,7 +52,19 @@ function compareApiVersions(current: string, required: string) { } function isSupportedDockerApiVersion(apiVersion: string) { - return compareApiVersions(apiVersion, REQUIRED_DOCKER_API_VERSION) >= 0; + return compareApiVersions(apiVersion, MIN_DOCKER_API_VERSION) >= 0; +} + +function clampApiVersion(target: string, min: string, max: string) { + if (compareApiVersions(target, max) > 0) { + return max; + } + + if (compareApiVersions(target, min) < 0) { + return min; + } + + return target; } const configSchema = { @@ -73,6 +87,7 @@ export default class DockerIntegration extends Integration { - if (!this.client) { - throw new Error("Docker client is not initialized"); - } - - const filters = encodeURIComponent( - JSON.stringify({ - label: [`${label}=${value}`], - }), - ); - const { body } = await this.client.request({ - method: "GET", - path: `/containers/json?filters=${filters}`, - }); - const containers: DockerContainer[] = (await body.json()) as DockerContainer[]; - if (containers.length > 1) { - throw new Error( - `Found multiple Docker containers matching label ${label}=${value}. Please specify a container name.`, - ); - } - if (containers.length === 0) { - throw new Error(`No Docker containers found matching label: ${label}=${value}`); - } - log.info("config", "Found Docker container matching label: %s=%s", label, value); - return containers[0].Id; - } - async isAvailable() { - log.info("config", "Requiring Docker API version %s or newer", REQUIRED_DOCKER_API_VERSION); + log.info("config", "Requiring Docker API version %s or newer", MIN_DOCKER_API_VERSION); // Basic configuration check, the name overrides the container_label // selector because of legacy support. @@ -198,10 +186,18 @@ export default class DockerIntegration extends Integration { let env: HeadscaleEnv; let dockerSocket: string; @@ -77,16 +86,21 @@ describe("DockerIntegration", () => { // Health check goes through the Docker socket to avoid stale port // mappings after container restart. const containerId = env.container.getId(); - const dockerClient = new Client("http://localhost", { - socketPath: "/var/run/docker.sock", + const dockerClient = connect(dockerSocket); + + const versionRes = await dockerClient.request({ + method: "GET", + path: "/version", }); + const versionInfo = (await versionRes.body.json()) as { ApiVersion?: string }; + const apiVersion = versionInfo.ApiVersion ?? "1.24"; const mockHeadscale = { health: async () => { try { const res = await dockerClient.request({ method: "GET", - path: `/v1.44/containers/${containerId}/json`, + path: `/v${apiVersion}/containers/${containerId}/json`, }); const info = (await res.body.json()) as any; return info.State?.Running === true;