Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't use docker shim if only using a mounted docker.sock instead of docker-in-docker #67

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
89 changes: 89 additions & 0 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

114 changes: 113 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as actions_core from "@actions/core";
import * as github from "@actions/github";
import * as actions_tool_cache from "@actions/tool-cache";
import * as actions_exec from "@actions/exec";
import { chmod, access, writeFile } from "node:fs/promises";
import { chmod, access, writeFile, readFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
import { join } from "node:path";
import fs from "node:fs";
Expand Down Expand Up @@ -164,6 +164,16 @@ class NixInstallerAction {
}
}

if (
!this.force_docker_shim &&
(await this.detectDockerWithMountedDockerSocket())
) {
actions_core.debug(
"Detected a Docker container with a Docker socket mounted, not enabling docker shim.",
);
return;
}

actions_core.startGroup(
"Enabling the Docker shim for running Nix on Linux in CI without Systemd.",
);
Expand All @@ -182,6 +192,108 @@ class NixInstallerAction {
actions_core.endGroup();
}

// Detect if we are running under `act` or some other system which is not using docker-in-docker,
// and instead using a mounted docker socket.
// In the case of the socket mount solution, the shim will cause issues since the given mount paths will
// equate to mount paths on the host, not mount paths to the docker container in question.
async detectDockerWithMountedDockerSocket(): Promise<boolean> {
let cgroups_buffer;
try {
// If we are inside a docker container, the last line of `/proc/self/cgroup` should be
// 0::/docker/$SOME_ID
//
// If we are not, the line will likely be `0::/`
cgroups_buffer = await readFile("/proc/self/cgroup", {
encoding: "utf-8",
});
} catch (e) {
actions_core.debug(
`Did not detect \`/proc/self/cgroup\` existence, bailing on docker container ID detection:\n${e}`,
);
return false;
}

const cgroups = cgroups_buffer.trim().split("\n");
const last_cgroup = cgroups[cgroups.length - 1];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be worth it to check every cgroup line to see if there is /docker/ in it, or if it's in an earlier cgroup, is that a weird configuration we wouldn't want to support anyways?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have access to every system and setup, only a few, but I think in this case we're only looking for the 0:: prefixed line which is always last.

const last_cgroup_parts = last_cgroup.split(":");
const last_cgroup_path = last_cgroup_parts[last_cgroup_parts.length - 1];
if (!last_cgroup_path.includes("/docker/")) {
actions_core.debug(
"Did not detect a container ID, bailing on docker.sock detection",
);
return false;
}
// We are in a docker container, now to determine if this container is visible from
// the `docker` command, and if so, if there is a `docker.socket` mounted.
const last_cgroup_path_parts = last_cgroup_path.split("/");
const container_id =
last_cgroup_path_parts[last_cgroup_path_parts.length - 1];

// If we cannot `docker inspect` this discovered container ID, we'll fall through to the `catch` below.
let stdout_buffer = "";
let stderr_buffer = "";
let exit_code;
try {
exit_code = await actions_exec.exec("docker", ["inspect", container_id], {
silent: true,
listeners: {
stdout: (data: Buffer) => {
stdout_buffer += data.toString("utf-8");
},
stderr: (data: Buffer) => {
stderr_buffer += data.toString("utf-8");
},
},
});
} catch (e) {
actions_core.debug(
`Could not execute \`docker inspect ${container_id}\`, bailing on docker container inspection:\n${e}`,
);
return false;
}

if (exit_code !== 0) {
actions_core.debug(
`Unable to inspect detected docker container with id \`${container_id}\`, bailing on container inspection (exit ${exit_code}):\n${stderr_buffer}`,
);
return false;
}

const output = JSON.parse(stdout_buffer);
// `docker inspect $ID` prints an array containing objects.
// In our use case, we should only see 1 item in the array.
if (output.length !== 1) {
actions_core.debug(
`Got \`docker inspect ${container_id}\` output which was not one item (was ${output.length}), bailing on docker.sock detection.`,
);
return false;
}
const item = output[0];
// On this array item we want the `Mounts` field, which is an array
// containing `{ Type, Source, Destination, Mode}`.
// We are looking for a `Destination` ending with `docker.sock`.
const mounts = item["Mounts"];
if (typeof mounts !== "object") {
actions_core.debug(
`Got non-object in \`Mounts\` field of \`docker inspect ${container_id}\` output, bailing on docker.sock detection.`,
);
return false;
}

let found_docker_sock_mount = false;
for (const mount of mounts) {
const destination = mount["Destination"];
if (typeof destination === "string") {
if (destination.endsWith("docker.sock")) {
found_docker_sock_mount = true;
break;
}
}
}

return found_docker_sock_mount;
}

private async executionEnvironment(): Promise<ExecuteEnvironment> {
const execution_env: ExecuteEnvironment = {};

Expand Down