Archived
fix(docker): refuse unsafe development updates
This commit is contained in:
@@ -259,6 +259,84 @@ describe("Docker command assets", () => {
|
||||
expect(await readFile(helperLog, "utf8")).toBe("allow=1 args=ps\n");
|
||||
});
|
||||
|
||||
dockerCommandIt("refuses development updates when the checkout has uncommitted files", async () => {
|
||||
const helperLog = join(tempDir, "dev-helper.log");
|
||||
const devRoot = await createCleanDevGitRepoWithFakeHelper(helperLog);
|
||||
const fakeDocker = await installFakeDocker();
|
||||
await installFakeId(fakeDocker.binDir, 1234, 2345);
|
||||
await writeFile(join(devRoot, "staged.txt"), "changed\n", "utf8");
|
||||
await execUtf8("git", ["-C", devRoot, "add", "staged.txt"], cleanProcessEnv());
|
||||
await writeFile(join(devRoot, "modified.txt"), "changed\n", "utf8");
|
||||
await writeFile(join(devRoot, "untracked.txt"), "untracked\n", "utf8");
|
||||
|
||||
const result = await runDockerCommandAllowFailure(
|
||||
["--dev", "update"],
|
||||
devHostEnv(fakeDocker, devRoot, join(tempDir, "home")),
|
||||
);
|
||||
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
expect(result.stderr).toContain("refusing to update the Docker development stack because the checkout has uncommitted changes");
|
||||
expect(result.stderr).toContain("staged.txt");
|
||||
expect(result.stderr).toContain("modified.txt");
|
||||
expect(result.stderr).toContain("?? untracked.txt");
|
||||
expect(result.stderr).toContain("commit, stash, or remove these changes");
|
||||
await expect(readFile(helperLog, "utf8")).rejects.toThrow();
|
||||
});
|
||||
|
||||
dockerCommandIt("refuses dirty development updates before scheduling a detached helper", async () => {
|
||||
const helperLog = join(tempDir, "dev-helper.log");
|
||||
const devRoot = await createCleanDevGitRepoWithFakeHelper(helperLog);
|
||||
const fakeDocker = await installFakeDocker();
|
||||
await installFakeId(fakeDocker.binDir, 1234, 2345);
|
||||
await writeFile(join(devRoot, "untracked.txt"), "untracked\n", "utf8");
|
||||
|
||||
const result = await runDockerCommandAllowFailure(["--dev", "update"], devRuntimeEnv(fakeDocker, devRoot));
|
||||
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
expect(result.stderr).toContain("checkout has uncommitted changes");
|
||||
expect(result.stdout).not.toContain("Started detached PI WEB Docker helper");
|
||||
await expect(readFile(fakeDocker.logPath, "utf8")).rejects.toThrow();
|
||||
await expect(readFile(helperLog, "utf8")).rejects.toThrow();
|
||||
});
|
||||
|
||||
dockerCommandIt("refuses development updates while a Git operation is in progress", async () => {
|
||||
const helperLog = join(tempDir, "dev-helper.log");
|
||||
const devRoot = await createCleanDevGitRepoWithFakeHelper(helperLog);
|
||||
const fakeDocker = await installFakeDocker();
|
||||
await installFakeId(fakeDocker.binDir, 1234, 2345);
|
||||
const head = (await execUtf8("git", ["-C", devRoot, "rev-parse", "HEAD"], cleanProcessEnv())).stdout.trim();
|
||||
await writeFile(join(devRoot, ".git", "MERGE_HEAD"), `${head}\n`, "utf8");
|
||||
|
||||
const result = await runDockerCommandAllowFailure(
|
||||
["--dev", "update"],
|
||||
devHostEnv(fakeDocker, devRoot, join(tempDir, "home")),
|
||||
);
|
||||
|
||||
expect(result.exitCode).not.toBe(0);
|
||||
expect(result.stderr).toContain("while a Git merge is in progress");
|
||||
expect(result.stderr).toContain("resolve or abort the Git merge");
|
||||
await expect(readFile(helperLog, "utf8")).rejects.toThrow();
|
||||
});
|
||||
|
||||
dockerCommandIt("allows clean development updates and dirty development starts", async () => {
|
||||
const helperLog = join(tempDir, "dev-helper.log");
|
||||
const devRoot = await createCleanDevGitRepoWithFakeHelper(helperLog);
|
||||
const fakeDocker = await installFakeDocker();
|
||||
await installFakeId(fakeDocker.binDir, 1234, 2345);
|
||||
const env = devHostEnv(fakeDocker, devRoot, join(tempDir, "home"));
|
||||
|
||||
await runDockerCommand(["--dev", "update"], env);
|
||||
await writeFile(join(devRoot, "in-progress-work.txt"), "dirty by design\n", "utf8");
|
||||
await runDockerCommand(["--dev", "start"], env);
|
||||
|
||||
expect(await readFile(helperLog, "utf8")).toBe([
|
||||
"allow=0 args=build --pull",
|
||||
"allow=0 args=up -d --force-recreate --remove-orphans",
|
||||
"allow=0 args=up -d --build",
|
||||
"",
|
||||
].join("\n"));
|
||||
});
|
||||
|
||||
dockerCommandIt("starts development detached helpers as the generated dev user", async () => {
|
||||
const devRoot = await createDevGeneratedEnv({ uid: 1234, gid: 2345, dockerGid: 3456 });
|
||||
const fakeDocker = await installFakeDocker();
|
||||
@@ -443,12 +521,31 @@ async function createDevRepoFixtureWithFakeHelper(logPath: string): Promise<stri
|
||||
await mkdir(dirname(helperPath), { recursive: true });
|
||||
await writeFile(helperPath, `#!/usr/bin/env sh
|
||||
set -eu
|
||||
printf 'allow=%s args=%s\n' "\${PI_WEB_DOCKER_ALLOW_ROOT:-}" "$*" >${shellSingleQuote(logPath)}
|
||||
printf 'allow=%s args=%s\n' "\${PI_WEB_DOCKER_ALLOW_ROOT:-}" "$*" >>${shellSingleQuote(logPath)}
|
||||
`, "utf8");
|
||||
await chmod(helperPath, 0o755);
|
||||
return devRoot;
|
||||
}
|
||||
|
||||
async function createCleanDevGitRepoWithFakeHelper(logPath: string): Promise<string> {
|
||||
const devRoot = await createDevRepoFixtureWithFakeHelper(logPath);
|
||||
await Promise.all([
|
||||
writeFile(join(devRoot, "staged.txt"), "clean\n", "utf8"),
|
||||
writeFile(join(devRoot, "modified.txt"), "clean\n", "utf8"),
|
||||
]);
|
||||
const env = cleanProcessEnv();
|
||||
await execUtf8("git", ["init", "--quiet", devRoot], env);
|
||||
await execUtf8("git", ["-C", devRoot, "add", "."], env);
|
||||
await execUtf8("git", [
|
||||
"-C", devRoot,
|
||||
"-c", "user.name=PI WEB Test",
|
||||
"-c", "[email protected]",
|
||||
"-c", "core.hooksPath=/dev/null",
|
||||
"commit", "--quiet", "--no-gpg-sign", "-m", "test fixture",
|
||||
], env);
|
||||
return devRoot;
|
||||
}
|
||||
|
||||
async function createDevGeneratedEnv(ids: { uid: number; gid: number; dockerGid: number }): Promise<string> {
|
||||
const devRoot = join(tempDir, "dev-runtime");
|
||||
await mkdir(join(devRoot, ".pi-web"), { recursive: true });
|
||||
|
||||
Reference in New Issue
Block a user