magic-nix-cache-action/dist/main.js

261 lines
7.9 KiB
JavaScript
Raw Normal View History

2024-04-21 01:57:38 +02:00
// src/main.ts
2024-04-12 01:21:15 +02:00
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
2024-04-21 01:57:38 +02:00
import { spawn, exec } from "node:child_process";
2024-04-12 01:21:15 +02:00
import { openSync, readFileSync } from "node:fs";
import { inspect, promisify } from "node:util";
import * as http from "http";
import * as core from "@actions/core";
import { Tail } from "tail";
import got from "got";
2024-04-12 01:21:15 +02:00
import { IdsToolbox } from "detsys-ts";
2024-04-21 01:57:38 +02:00
var ENV_CACHE_DAEMONDIR = "MAGIC_NIX_CACHE_DAEMONDIR";
var gotClient = got.extend({
retry: {
2023-12-05 02:12:12 +01:00
limit: 1,
2024-04-21 01:57:38 +02:00
methods: ["POST", "GET", "PUT", "HEAD", "DELETE", "OPTIONS", "TRACE"]
},
hooks: {
beforeRetry: [
(error, retryCount) => {
core.info(`Retrying after error ${error.code}, retry #: ${retryCount}`);
2024-04-21 01:57:38 +02:00
}
]
}
});
2024-04-21 01:57:38 +02:00
async function fetchAutoCacher(toolbox) {
2024-04-12 01:21:15 +02:00
const closurePath = await toolbox.fetch();
toolbox.recordEvent("load_closure");
const { stdout } = await promisify(exec)(
2024-04-21 01:57:38 +02:00
`cat "${closurePath}" | xz -d | nix-store --import`
2024-04-12 01:21:15 +02:00
);
const paths = stdout.split(os.EOL);
const last_path = paths.at(-2);
return `${last_path}/bin/magic-nix-cache`;
}
2024-04-21 01:57:38 +02:00
function tailLog(daemonDir) {
2024-04-12 01:21:15 +02:00
const log = new Tail(path.join(daemonDir, "daemon.log"));
2024-01-11 16:55:51 +01:00
core.debug(`tailing daemon.log...`);
2024-04-12 01:21:15 +02:00
log.on("line", (line) => {
2024-01-11 16:55:51 +01:00
core.info(line);
});
return log;
}
2024-04-21 01:57:38 +02:00
async function setUpAutoCache(toolbox) {
const tmpdir2 = process.env["RUNNER_TEMP"] || os.tmpdir();
2024-04-12 01:21:15 +02:00
const required_env = [
"ACTIONS_CACHE_URL",
"ACTIONS_RUNTIME_URL",
2024-04-21 01:57:38 +02:00
"ACTIONS_RUNTIME_TOKEN"
2024-04-12 01:21:15 +02:00
];
let anyMissing = false;
for (const n of required_env) {
if (!process.env.hasOwnProperty(n)) {
anyMissing = true;
2024-04-12 01:21:15 +02:00
core.warning(
2024-04-21 01:57:38 +02:00
`Disabling automatic caching since required environment ${n} isn't available`
2024-04-12 01:21:15 +02:00
);
}
}
if (anyMissing) {
return;
}
2024-04-12 01:21:15 +02:00
core.debug(`GitHub Action Cache URL: ${process.env["ACTIONS_CACHE_URL"]}`);
2024-04-21 01:57:38 +02:00
const daemonDir = await fs.mkdtemp(path.join(tmpdir2, "magic-nix-cache-"));
let daemonBin;
2024-04-12 01:21:15 +02:00
if (core.getInput("source-binary")) {
daemonBin = core.getInput("source-binary");
} else {
2024-04-12 01:21:15 +02:00
daemonBin = await fetchAutoCacher(toolbox);
}
2024-04-12 01:21:15 +02:00
let runEnv;
if (core.isDebug()) {
runEnv = {
2023-06-26 18:27:45 +02:00
RUST_LOG: "trace,magic_nix_cache=debug,gha_cache=debug",
RUST_BACKTRACE: "full",
2024-04-21 01:57:38 +02:00
...process.env
};
} else {
runEnv = process.env;
}
2024-04-12 01:21:15 +02:00
const notifyPort = core.getInput("startup-notification-port");
2024-04-21 01:57:38 +02:00
const notifyPromise = new Promise((resolveListening) => {
const promise = new Promise(async (resolveQuit) => {
const notifyServer = http.createServer((req, res) => {
2024-04-12 01:21:15 +02:00
if (req.method === "POST" && req.url === "/") {
core.debug(`Notify server shutting down.`);
2024-04-12 01:21:15 +02:00
res.writeHead(200, { "Content-Type": "application/json" });
res.end("{}");
notifyServer.close(() => {
resolveQuit();
});
}
});
notifyServer.listen(notifyPort, () => {
core.debug(`Notify server running.`);
resolveListening(promise);
});
});
});
const outputPath = `${daemonDir}/daemon.log`;
2024-04-12 01:21:15 +02:00
const output = openSync(outputPath, "a");
2024-01-11 16:55:51 +01:00
const log = tailLog(daemonDir);
const netrc = await netrcPath();
const nixConfPath = `${process.env["HOME"]}/.config/nix/nix.conf`;
2024-04-21 01:57:38 +02:00
const daemonCliFlags = [
2024-04-12 01:21:15 +02:00
"--startup-notification-url",
`http://127.0.0.1:${notifyPort}`,
"--listen",
core.getInput("listen"),
"--upstream",
core.getInput("upstream-cache"),
"--diagnostic-endpoint",
core.getInput("diagnostic-endpoint"),
"--nix-conf",
2024-04-21 01:57:38 +02:00
nixConfPath
].concat(
core.getBooleanInput("use-flakehub") ? [
"--use-flakehub",
"--flakehub-cache-server",
core.getInput("flakehub-cache-server"),
"--flakehub-api-server",
core.getInput("flakehub-api-server"),
"--flakehub-api-server-netrc",
netrc,
"--flakehub-flake-name",
core.getInput("flakehub-flake-name")
] : []
).concat(core.getBooleanInput("use-gha-cache") ? ["--use-gha-cache"] : []);
const opts = {
2024-04-12 01:21:15 +02:00
stdio: ["ignore", output, output],
env: runEnv,
2024-04-21 01:57:38 +02:00
detached: true
2024-04-12 01:21:15 +02:00
};
core.debug("Full daemon start command:");
core.debug(`${daemonBin} ${daemonCliFlags.join(" ")}`);
const daemon = spawn(daemonBin, daemonCliFlags, opts);
2024-04-12 01:21:15 +02:00
const pidFile = path.join(daemonDir, "daemon.pid");
await fs.writeFile(pidFile, `${daemon.pid}`);
core.info("Waiting for magic-nix-cache to start...");
2024-04-21 01:57:38 +02:00
await new Promise((resolve, reject) => {
notifyPromise.then((_value) => {
resolve();
}).catch((err) => {
reject(new Error(`error in notifyPromise: ${err}`));
});
2024-04-12 01:21:15 +02:00
daemon.on("exit", async (code, signal) => {
if (signal) {
2024-01-11 16:55:51 +01:00
reject(new Error(`Daemon was killed by signal ${signal}`));
} else if (code) {
2024-01-11 16:55:51 +01:00
reject(new Error(`Daemon exited with code ${code}`));
} else {
2024-01-11 16:55:51 +01:00
reject(new Error(`Daemon unexpectedly exited`));
}
});
});
daemon.unref();
2024-04-12 01:21:15 +02:00
core.info("Launched Magic Nix Cache");
core.exportVariable(ENV_CACHE_DAEMONDIR, daemonDir);
2024-01-11 16:55:51 +01:00
log.unwatch();
}
2024-04-21 01:57:38 +02:00
async function notifyAutoCache() {
const daemonDir = process.env[ENV_CACHE_DAEMONDIR];
if (!daemonDir) {
return;
}
try {
core.debug(`Indicating workflow start`);
2024-04-21 01:57:38 +02:00
const res = await gotClient.post(`http://${core.getInput("listen")}/api/workflow-start`).json();
2024-04-12 01:21:15 +02:00
core.debug(`back from post: ${res}`);
} catch (e) {
core.info(`Error marking the workflow as started:`);
core.info(inspect(e));
core.info(`Magic Nix Cache may not be running for this workflow.`);
}
}
2024-04-21 01:57:38 +02:00
async function netrcPath() {
2024-04-12 01:21:15 +02:00
const expectedNetrcPath = path.join(
process.env["RUNNER_TEMP"] || os.tmpdir(),
2024-04-21 01:57:38 +02:00
"determinate-nix-installer-netrc"
2024-04-12 01:21:15 +02:00
);
try {
2024-04-12 01:21:15 +02:00
await fs.access(expectedNetrcPath);
return expectedNetrcPath;
} catch {
2024-04-12 01:21:15 +02:00
const destinedNetrcPath = path.join(
process.env["RUNNER_TEMP"] || os.tmpdir(),
2024-04-21 01:57:38 +02:00
"magic-nix-cache-netrc"
2024-04-12 01:21:15 +02:00
);
try {
await flakehub_login(destinedNetrcPath);
2024-02-13 20:40:27 +01:00
} catch (e) {
core.info("FlakeHub cache disabled.");
2024-04-12 01:21:15 +02:00
core.debug(`Error while logging into FlakeHub: ${e}`);
}
return destinedNetrcPath;
}
}
2024-04-21 01:57:38 +02:00
async function flakehub_login(netrc) {
const jwt = await core.getIDToken("api.flakehub.com");
await fs.writeFile(
netrc,
[
`machine api.flakehub.com login flakehub password ${jwt}`,
`machine flakehub.com login flakehub password ${jwt}`,
2024-04-21 01:57:38 +02:00
`machine cache.flakehub.com login flakehub password ${jwt}`
].join("\n")
);
2024-02-13 20:40:27 +01:00
core.info("Logged in to FlakeHub.");
}
2024-04-21 01:57:38 +02:00
async function tearDownAutoCache() {
const daemonDir = process.env[ENV_CACHE_DAEMONDIR];
if (!daemonDir) {
2024-04-12 01:21:15 +02:00
core.debug("magic-nix-cache not started - Skipping");
return;
}
2024-04-12 01:21:15 +02:00
const pidFile = path.join(daemonDir, "daemon.pid");
const pid = parseInt(await fs.readFile(pidFile, { encoding: "ascii" }));
core.debug(`found daemon pid: ${pid}`);
if (!pid) {
throw new Error("magic-nix-cache did not start successfully");
}
2024-01-11 16:55:51 +01:00
const log = tailLog(daemonDir);
try {
core.debug(`about to post to localhost`);
2024-04-21 01:57:38 +02:00
const res = await gotClient.post(`http://${core.getInput("listen")}/api/workflow-finish`).json();
2024-04-12 01:21:15 +02:00
core.debug(`back from post: ${res}`);
} finally {
core.debug(`unwatching the daemon log`);
log.unwatch();
}
core.debug(`killing`);
try {
2024-04-12 01:21:15 +02:00
process.kill(pid, "SIGTERM");
} catch (e) {
2024-04-12 01:21:15 +02:00
if (typeof e === "object" && e && "code" in e && e.code !== "ESRCH") {
throw e;
}
2023-12-05 02:30:23 +01:00
} finally {
if (core.isDebug()) {
core.info("Entire log:");
2024-04-12 01:21:15 +02:00
const entireLog = readFileSync(path.join(daemonDir, "daemon.log"));
core.info(entireLog.toString());
2023-12-05 02:30:23 +01:00
}
}
}
2024-04-21 01:57:38 +02:00
var idslib = new IdsToolbox({
2024-04-12 01:21:15 +02:00
name: "magic-nix-cache",
fetchStyle: "gh-env-style",
2024-04-12 01:25:26 +02:00
idsProjectName: "magic-nix-cache-closure",
2024-04-21 01:57:38 +02:00
requireNix: "warn"
2024-04-12 01:21:15 +02:00
});
idslib.onMain(async () => {
await setUpAutoCache(idslib);
await notifyAutoCache();
});
idslib.onPost(async () => {
await tearDownAutoCache();
});
idslib.execute();
2024-04-21 01:57:38 +02:00
//# sourceMappingURL=main.js.map