Skip to content

Commit

Permalink
Clean up the logic that determines which push mode to use
Browse files Browse the repository at this point in the history
  • Loading branch information
sandydoo committed Apr 2, 2024
1 parent 205a065 commit 0c62322
Showing 1 changed file with 146 additions and 107 deletions.
253 changes: 146 additions & 107 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,82 +27,107 @@ const installCommand =

const ENV_CACHIX_DAEMON_DIR = 'CACHIX_DAEMON_DIR';

enum PushMode {
// Disable pushing entirely.
None = 'None',
// Scans the entire store during the pre- and post-hooks and uploads the difference.
// This is a very simple method and is likely to work in any environment.
// There are two downsides:
// 1. The final set of paths to push is computed in the post-build hook, so paths are not pushed during builds.
// 2. It is not safe to use in a multi-user environment, as it may leak store paths built by other users.
StoreScan = 'StoreScan',
// Uses the Cachix Daemon to register a post-build hook with the Nix Daemon.
// Very efficient as it can upload paths as they are built.
// May not be supported in all environment (e.g. NixOS Containers) and inherits all of the implementation deficiencies of Nix's post-build hook.
Daemon = 'Daemon',
}

async function setup() {
try {
let cachixBin = cachixBinInput;
let cachixBin = cachixBinInput;

if (cachixBin !== "") {
core.debug(`Using Cachix executable from input: ${cachixBin}`);
} else {
// Find the Cachix executable in PATH
let resolvedCachixBin = which.sync('cachix', { nothrow: true });
if (cachixBin !== "") {
core.debug(`Using Cachix executable from input: ${cachixBin}`);
} else {
// Find the Cachix executable in PATH
let resolvedCachixBin = which.sync('cachix', { nothrow: true });

if (resolvedCachixBin) {
core.debug(`Found Cachix executable: ${cachixBin}`);
cachixBin = resolvedCachixBin;
} else {
core.startGroup('Cachix: installing')
await exec.exec('bash', ['-c', installCommand]);
cachixBin = which.sync('cachix');
core.debug(`Installed Cachix executable: ${cachixBin}`);
core.endGroup()
}
if (resolvedCachixBin) {
core.debug(`Found Cachix executable: ${cachixBin}`);
cachixBin = resolvedCachixBin;
} else {
core.startGroup('Cachix: installing')
await exec.exec('bash', ['-c', installCommand]);
cachixBin = which.sync('cachix');
core.debug(`Installed Cachix executable: ${cachixBin}`);
core.endGroup()
}
}

core.saveState('cachixBin', cachixBin);
core.saveState('cachixBin', cachixBin);

// Print the executable version.
// Also verifies that the binary exists and is executable.
core.startGroup('Cachix: checking version')
let cachixVersion =
await execToVariable(cachixBin, ['--version'])
.then((res) => semver.coerce(res.split(" ")[1]));
core.endGroup()
// Print the executable version.
// Also verifies that the binary exists and is executable.
core.startGroup('Cachix: checking version')
let cachixVersion =
await execToVariable(cachixBin, ['--version'])
.then((res) => semver.coerce(res.split(" ")[1]));
core.endGroup()

// for managed signing key and private caches
if (authToken !== "") {
await exec.exec(cachixBin, ['authtoken', authToken]);
}
// For managed signing key and private caches
if (authToken !== "") {
await exec.exec(cachixBin, ['authtoken', authToken]);
}

if (skipAddingSubstituter) {
core.info('Not adding Cachix cache to substituters as skipAddingSubstituter is set to true')
} else {
core.startGroup(`Cachix: using cache ` + name);
await exec.exec(cachixBin, ['use', name]);
core.endGroup();
}
if (signingKey !== "") {
core.exportVariable('CACHIX_SIGNING_KEY', signingKey);
}

if (extraPullNames != "") {
core.startGroup(`Cachix: using extra caches ` + extraPullNames);
const extraPullNameList = extraPullNames.split(',');
for (let itemName of extraPullNameList) {
const trimmedItemName = itemName.trim();
await exec.exec(cachixBin, ['use', trimmedItemName]);
}
core.endGroup();
}
let hasPushCredentials = signingKey !== "" || authToken !== "";
core.saveState('hasPushCredentials', hasPushCredentials);

if (signingKey !== "") {
core.exportVariable('CACHIX_SIGNING_KEY', signingKey);
}
if (skipAddingSubstituter) {
core.info('Not adding Cachix cache to substituters as skipAddingSubstituter is set to true')
} else {
core.startGroup(`Cachix: using cache ` + name);
await exec.exec(cachixBin, ['use', name]);
core.endGroup();
}

let supportsDaemonInterface = (cachixVersion) ? semver.gte(cachixVersion, '1.7.0') : false;
let supportsPostBuildHook = await isTrustedUser();
let hasPushCredentials = signingKey !== "" || authToken !== "";
if (useDaemon && !supportsDaemonInterface) {
core.warning(`Cachix Daemon is not supported by this version of Cachix (${cachixVersion}). Ignoring the 'useDaemon' option.`)
}
if (useDaemon && !supportsPostBuildHook) {
core.warning("This user is not allowed to set the post-build-hook. Ignoring the 'useDaemon' option.");
if (extraPullNames != "") {
core.startGroup(`Cachix: using extra caches ` + extraPullNames);
const extraPullNameList = extraPullNames.split(',');
for (let itemName of extraPullNameList) {
const trimmedItemName = itemName.trim();
await exec.exec(cachixBin, ['use', trimmedItemName]);
}
if (useDaemon && !hasPushCredentials) {
core.warning("No push credentials found. Ignoring the 'useDaemon' option.");
core.endGroup();
}

// Determine the push mode to use
let pushMode = PushMode.None;

if (hasPushCredentials && !skipPush) {
if (useDaemon) {
let supportsDaemonInterface = (cachixVersion) ? semver.gte(cachixVersion, '1.7.0') : false;
let supportsPostBuildHook = await isTrustedUser();

if (!supportsDaemonInterface) {
core.warning(`Cachix Daemon is not supported by this version of Cachix (${cachixVersion}). Ignoring the 'useDaemon' option.`)
}
if (!supportsPostBuildHook) {
core.warning("This user is not allowed to set the post-build-hook. Ignoring the 'useDaemon' option.");
}

pushMode = (supportsDaemonInterface && supportsPostBuildHook) ? PushMode.Daemon : PushMode.StoreScan;
} else {
pushMode = PushMode.StoreScan;
}
}

let supportsDaemon = supportsDaemonInterface && supportsPostBuildHook && hasPushCredentials;
core.saveState('supportsDaemon', supportsDaemon);
core.saveState('pushMode', pushMode);

if (useDaemon && supportsDaemon) {
switch (pushMode) {
case PushMode.Daemon: {
const tmpdir = process.env['RUNNER_TEMP'] ?? os.tmpdir();
const daemonDir = await fs.mkdtemp(path.join(tmpdir, 'cachix-daemon-'));
const daemonLog = openSync(`${daemonDir}/daemon.log`, 'a');
Expand Down Expand Up @@ -140,61 +165,71 @@ async function setup() {

// Detach the daemon process from the current process
daemon.unref();
} else {

break;
}
case PushMode.StoreScan: {
// Remember existing store paths
await exec.exec("sh", ["-c", `${__dirname}/list-nix-store.sh > /tmp/store-path-pre-build`]);
break;
}
} catch (error) {
core.setFailed(`Action failed with error: ${error}`);

default:
break;
}
}

async function upload() {
core.startGroup('Cachix: push');

const cachixBin = core.getState('cachixBin');
const supportsDaemon = core.getState('supportsDaemon') === 'true';
const pushMode = core.getState('pushMode');

try {
if (skipPush) {
core.info('Pushing is disabled as skipPush is set to true');
} else if (signingKey !== "" || authToken !== "") {
if (useDaemon && supportsDaemon) {
const daemonDir = process.env[ENV_CACHIX_DAEMON_DIR];

if (!daemonDir) {
core.error('Cachix Daemon not started. Skipping push');
return;
}
switch (pushMode) {
case PushMode.None: {
if (skipPush) {
core.info('Pushing is disabled as skipPush is set to true');
break;
}

const daemonPid = parseInt(await fs.readFile(pidFilePath(daemonDir), { encoding: 'utf8' }));
break;
}
case PushMode.Daemon: {
const daemonDir = process.env[ENV_CACHIX_DAEMON_DIR];

if (!daemonPid) {
core.error('Failed to find PID of Cachix Daemon. Skipping push.');
return;
}
if (!daemonDir) {
core.error('Cachix Daemon not started. Skipping push');
return;
}

core.debug(`Found Cachix daemon with pid ${daemonPid}`);
const daemonPid = parseInt(await fs.readFile(pidFilePath(daemonDir), { encoding: 'utf8' }));

let daemonLog = new Tail(`${daemonDir}/daemon.log`, { fromBeginning: true });
daemonLog.on('line', (line) => core.info(line));
if (!daemonPid) {
core.error('Failed to find PID of Cachix Daemon. Skipping push.');
return;
}

try {
core.debug('Waiting for Cachix daemon to exit...');
await exec.exec(cachixBin, ["daemon", "stop", "--socket", `${daemonDir}/daemon.sock`]);
} finally {
// Wait a bit for the logs to flush through
await new Promise((resolve) => setTimeout(resolve, 1000));
daemonLog.unwatch();
}
} else {
await exec.exec(`${__dirname}/push-paths.sh`, [cachixBin, cachixArgs, name, pathsToPush, pushFilter]);
core.debug(`Found Cachix daemon with pid ${daemonPid}`);

let daemonLog = new Tail(`${daemonDir}/daemon.log`, { fromBeginning: true });
daemonLog.on('line', (line) => core.info(line));

try {
core.debug('Waiting for Cachix daemon to exit...');
await exec.exec(cachixBin, ["daemon", "stop", "--socket", `${daemonDir}/daemon.sock`]);
} finally {
// Wait a bit for the logs to flush through
await new Promise((resolve) => setTimeout(resolve, 1000));
daemonLog.unwatch();
}
} else {
core.info('Pushing is disabled because neither signingKey nor authToken are set (or are empty?) in your YAML file.');

break;
}

case PushMode.StoreScan: {
await exec.exec(`${__dirname}/push-paths.sh`, [cachixBin, cachixArgs, name, pathsToPush, pushFilter]);
break;
}
} catch (error) {
core.setFailed(`Action failed with error: ${error}`);
}

core.endGroup();
Expand Down Expand Up @@ -361,13 +396,17 @@ function partitionUsersAndGroups(mixedUsers: string[]): [string[], string[]] {
const isPost = !!core.getState('isPost');

// Main
if (!isPost) {
// Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic.
// This is necessary since we don't have a separate entry point.
core.saveState('isPost', 'true');
setup()
core.debug('Setup done');
} else {
// Post
upload()
try {
if (!isPost) {
// Publish a variable so that when the POST action runs, it can determine it should run the cleanup logic.
// This is necessary since we don't have a separate entry point.
core.saveState('isPost', 'true');
setup()
core.debug('Setup done');
} else {
// Post
upload()
}
} catch (error) {
core.setFailed(`Action failed with error: ${error}`);
}

0 comments on commit 0c62322

Please sign in to comment.