Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/lib/onboard/preflight-podman-compat.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,31 @@ describe("assessHost Podman docker-compat detection (#7320)", () => {
expect(result.isUnsupportedRuntime).toBe(true);
});

it("keeps real Docker classified as supported (no Podman false positive)", () => {
it.each([
["an empty JSON object", "{}"],
["unexpected plain text", "unexpected version output"],
])("rejects a Podman-compatible runtime when version output is %s (#7320)", (_case, versionOutput) => {
const result = assessHost({
platform: "darwin",
env: {},
dockerInfoOutput: PODMAN_COMPAT_DOCKER_INFO,
dockerVersionOutput: versionOutput,
commandExistsImpl: (name: string) => name === "docker",
});

expect(result.dockerReachable).toBe(true);
expect(result.runtime).toBe("podman");
expect(result.isUnsupportedRuntime).toBe(true);
});

it("keeps Docker Engine supported when ProductLicense reports Apache-2.0", () => {
const realDockerInfo = JSON.stringify({
ServerVersion: "29.6.2",
OperatingSystem: "Ubuntu 24.04.3 LTS",
OSType: "linux",
Architecture: "x86_64",
DefaultRuntime: "runc",
ProductLicense: "Apache-2.0",
DockerRootDir: "/var/lib/docker",
DefaultAddressPools: [{ Base: "192.168.240.0/20", Size: 24 }],
ClientInfo: { Platform: { Name: "Docker Engine - Community" } },
Expand Down
66 changes: 38 additions & 28 deletions src/lib/onboard/preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,9 +216,11 @@ function inferContainerRuntime(info = ""): ContainerRuntime {
return "unknown";
}

type DockerVersionIdentity = "docker" | "podman" | "unknown";

/**
* Detect Podman fronting the Docker CLI compatibility socket from the explicit
* `docker version --format '{{json .}}'` engine banner.
* Classify the engine identity from the explicit
* `docker version --format '{{json .}}'` banner.
*
* Podman's docker-compat `/info` endpoint mimics Docker so closely that
* `docker info` carries no "podman" marker (observed on Apple Silicon macOS:
Expand All @@ -228,43 +230,51 @@ function inferContainerRuntime(info = ""): ContainerRuntime {
* `Server.Components[].Name` of "Podman Engine" and a `Server.Platform.Name`
* like "linux/arm64/fedora-42". Real Docker reports
* `Server.Platform.Name: "Docker Engine - Community"` and components
* `Engine`/`containerd`/`runc`, so this stays false on Docker Engine,
* Docker Desktop, and Colima (#7320).
* `Engine`/`containerd`/`runc`, which provide positive Docker identity on
* Docker Engine, Docker Desktop, and Colima (#7320).
*/
function dockerVersionReportsPodman(versionOutput = ""): boolean {
function classifyDockerVersionIdentity(versionOutput = ""): DockerVersionIdentity {
const text = String(versionOutput || "").trim();
if (!text) return false;
if (!text) return "unknown";
let parsed: unknown;
try {
parsed = JSON.parse(text);
} catch {
// Plain-text `docker version` still prints the "Podman Engine" server banner.
return /podman/i.test(text);
if (/podman/i.test(text)) return "podman";
return /docker engine/i.test(text) ? "docker" : "unknown";
}
const server = (parsed as Record<string, unknown> | null)?.Server;
if (!server || typeof server !== "object") return false;
if (!server || typeof server !== "object") return "unknown";
const s = server as Record<string, unknown>;
const platformName = (s.Platform as Record<string, unknown> | undefined)?.Name;
if (typeof platformName === "string" && /podman/i.test(platformName)) return true;
if (typeof platformName === "string" && /podman/i.test(platformName)) return "podman";
const components = s.Components;
return (
Array.isArray(components) &&
components.some((component) => {
const name =
component && typeof component === "object"
? (component as Record<string, unknown>).Name
: undefined;
return typeof name === "string" && /podman/i.test(name);
})
);
const componentNames = Array.isArray(components)
? components.flatMap((component) => {
const name =
component && typeof component === "object"
? (component as Record<string, unknown>).Name
: undefined;
return typeof name === "string" ? [name] : [];
})
: [];
if (componentNames.some((name) => /podman/i.test(name))) return "podman";
if (
(typeof platformName === "string" && /^docker (?:engine|desktop)\b/i.test(platformName)) ||
componentNames.some((name) => name.trim().toLowerCase() === "engine")
) {
return "docker";
}
return "unknown";
}

/**
* Backstop Podman signal from `docker info --format '{{json .}}'` when the
* `docker version` probe is unavailable: Podman's docker-compat `/info` reports
* `ProductLicense: "Apache-2.0"` (Podman is Apache-2.0 licensed), whereas Docker
* Engine omits `ProductLicense` (Docker CE) or reports a Docker license string.
* Observed Apache-2.0 on the reporter-equivalent Podman machine (#7320).
* Use `ProductLicense: "Apache-2.0"` as a Podman backstop only when the
* authoritative `docker version` probe is unavailable. Podman's docker-compat
* `/info` reports this value, but Docker Engine can also report it. Without
* version evidence, preflight conservatively rejects that ambiguous runtime
* before the Docker-driver path (#7320).
*/
function dockerInfoReportsPodmanCompat(infoOutput = ""): boolean {
const text = String(infoOutput || "").trim();
Expand All @@ -287,10 +297,10 @@ function dockerInfoReportsPodmanCompat(infoOutput = ""): boolean {
* `EADDRNOTAVAIL` (#7320).
*/
function isDockerCompatPodman(dockerInfoOutput = "", dockerVersionOutput = ""): boolean {
return (
dockerVersionReportsPodman(dockerVersionOutput) ||
dockerInfoReportsPodmanCompat(dockerInfoOutput)
);
const versionIdentity = classifyDockerVersionIdentity(dockerVersionOutput);
if (versionIdentity === "podman") return true;
if (versionIdentity === "docker") return false;
return dockerInfoReportsPodmanCompat(dockerInfoOutput);
}

function parseDockerCgroupVersion(info = ""): "v1" | "v2" | "unknown" {
Expand Down
Loading