-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Expand file tree
/
Copy pathnginx-config-artifacts.js
More file actions
81 lines (72 loc) · 3.79 KB
/
Copy pathnginx-config-artifacts.js
File metadata and controls
81 lines (72 loc) · 3.79 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import fs from "node:fs/promises";
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import { getActivePath } from "./nginx-host-adapters.js";
export const deploymentRoot = (nginxRoot = "/data/nginx") => join(nginxRoot, ".deploy");
export const assertInside = (root, path) => {
const rootPath = resolve(root);
const target = resolve(path);
if (target !== rootPath && !target.startsWith(`${rootPath}${process.platform === "win32" ? "\\" : "/"}`)) {
throw new Error(`Path escapes configured nginx root: ${path}`);
}
return target;
};
export const activeArtifactPath = (hostType, hostId, nginxRoot = "/data/nginx") => getActivePath(hostType, hostId, nginxRoot);
export const candidateArtifactPath = (hostType, hostId, operationId, nginxRoot = "/data/nginx") =>
assertInside(deploymentRoot(nginxRoot), join(deploymentRoot(nginxRoot), "candidates", hostType, String(hostId), `${operationId}.conf`));
export const stagingArtifactPath = (hostType, hostId, operationId, nginxRoot = "/data/nginx") =>
assertInside(deploymentRoot(nginxRoot), join(deploymentRoot(nginxRoot), "staging", operationId, hostType, `${hostId}.conf`));
export const backupArtifactPath = (hostType, hostId, operationId, nginxRoot = "/data/nginx") =>
assertInside(deploymentRoot(nginxRoot), join(deploymentRoot(nginxRoot), "backups", operationId, hostType, `${hostId}.conf`));
export const journalPath = (operationId, nginxRoot = "/data/nginx") => assertInside(deploymentRoot(nginxRoot), join(deploymentRoot(nginxRoot), "journal", `${operationId}.json`));
export const readArtifact = async (path) => {
try {
return await fs.readFile(path, "utf8");
} catch (error) {
if (error.code === "ENOENT") return null;
throw error;
}
};
/** Atomic within one filesystem: write, fsync, rename and fsync directory. */
export const atomicWrite = async (path, content) => {
await fs.mkdir(dirname(path), { recursive: true });
const temp = join(dirname(path), `.${randomUUID()}.tmp`);
let handle;
try {
handle = await fs.open(temp, "wx", 0o600);
await handle.writeFile(content, "utf8");
await handle.sync();
await handle.close();
handle = null;
await fs.rename(temp, path);
try {
const directory = await fs.open(dirname(path), "r");
await directory.sync();
await directory.close();
} catch (error) {
if (!["EINVAL", "EPERM", "ENOTSUP"].includes(error.code)) throw error;
}
} finally {
if (handle) await handle.close();
await fs.rm(temp, { force: true }).catch(() => undefined);
}
};
export const removeArtifact = async (path) => fs.rm(path, { force: true });
export const writeJournal = async (journal, nginxRoot = "/data/nginx") => atomicWrite(journalPath(journal.operation_id, nginxRoot), `${JSON.stringify(journal, null, 2)}\n`);
export const readJournals = async (nginxRoot = "/data/nginx") => {
const directory = join(deploymentRoot(nginxRoot), "journal");
try {
const names = await fs.readdir(directory);
return Promise.all(names.filter((name) => name.endsWith(".json")).map(async (name) => JSON.parse(await fs.readFile(join(directory, name), "utf8"))));
} catch (error) {
if (error.code === "ENOENT") return [];
throw error;
}
};
export const deleteJournal = async (operationId, nginxRoot = "/data/nginx") => fs.rm(journalPath(operationId, nginxRoot), { force: true });
export const toLogicalPath = (path, nginxRoot = "/data/nginx") => {
const value = relative(nginxRoot, path).replace(/\\/g, "/");
if (!value || value.startsWith("../") || isAbsolute(value)) throw new Error("Artifact path is outside nginx root");
return value;
};
export default { deploymentRoot, assertInside, activeArtifactPath, candidateArtifactPath, stagingArtifactPath, backupArtifactPath, journalPath, readArtifact, atomicWrite, removeArtifact, writeJournal, readJournals, deleteJournal, toLogicalPath };