dotfiles/actions/build-docker-container.ts

119 lines
5.3 KiB
JavaScript
Executable file

#!/usr/bin/env node
import { $, quote } from "zx";
import path from "node:path";
import { strict as assert } from "node:assert";
import { fileURLToPath } from "node:url";
// This has to be explicitly set for some reason with the way we install zx in CI
$.quote = quote;
$.verbose = true;
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**Pushes the image + tags to registry*/
export async function dockerPush(registry: string, registryUser: string, registryToken: string, image: string, fullySpecifiedTags: string[]) {
const registryHost = registry.split("/")[0];
console.log(`Logging in to ${registry}...`);
await $`echo ${registryToken} | docker login ${registryHost} -u ${registryUser} --password-stdin`;
for (const tag of fullySpecifiedTags) {
console.log(`Pushing ${tag}...`);
await $`docker push ${tag}`;
}
}
/**Builds the image + tags*/
export async function dockerBuild(buildContext: string, dockerfilePath: string, fullySpecifiedTags: string[], buildArgs?: Record<string, string>) {
console.log(`Tagging: ${fullySpecifiedTags.join(", ")}`);
const tagArgs = fullySpecifiedTags.flatMap((tag) => ["-t", tag]);
const buildArgsBuilt = Object.entries(buildArgs ?? {})
.filter(([k,v]) => v !== undefined && v !== null && v !== "")
.flatMap(([k,v]) => [`--build-arg`, `${k}=${v}`]);
await $`docker build --progress=plain ${buildArgsBuilt} ${tagArgs} -f ${dockerfilePath} ${buildContext}`;
}
// Get the current git hash
const GIT_HASH = (await $`git rev-parse --short HEAD`).stdout.trim();
const DOTFILES_VERSION = "0.0.1";
const BUILDDIR = path.join(__dirname, "..");
const DOCKERFILE = path.join(BUILDDIR, "dev.Dockerfile");
const REGISTRY = "git.b4t.dev";
const IMAGE = "cobertos/dotfiles";
const REGISTRY_TOKEN = process.env.REGISTRY_TOKEN;
const REGISTRY_USER = process.env.GITHUB_ACTOR;
const posArgs = process.argv.filter(a => !a.startsWith("-"));
// Usage: node build-docker-container.ts [CMD] [NAME] [DOTFILES_ENVIRONMENT] [DOTFILES_ARCHITECTURE] [DOTFILES_USER] [DOTFILES_EMAIL] [DOTFILES_GIT_DEFAULT_BRANCH]
const [/*node*/, /*script name*/, CMD, NAME, DOTFILES_ENVIRONMENT, DOTFILES_ARCHITECTURE, DOTFILES_USER, DOTFILES_EMAIL, DOTFILES_GIT_DEFAULT_BRANCH] = posArgs;
const envs = { DOTFILES_ENVIRONMENT, DOTFILES_ARCHITECTURE, DOTFILES_USER, DOTFILES_EMAIL, DOTFILES_GIT_DEFAULT_BRANCH };
assert(["all", "build"].includes(CMD), `CMD must be one of "all" or "build". Got "${CMD}"`);
const dotfilesUserForPath = DOTFILES_USER ? DOTFILES_USER : "cobertos";
const TAGS = [
`${NAME}-${GIT_HASH}`,
`${NAME}-${DOTFILES_VERSION}`,
`${NAME}-latest`
]
// Convert to final tags
.map(t => `${REGISTRY}/${IMAGE}:${t}`);
// TODO: Readd build args
console.log(`===== BUILDING DOTFILES DOCKERFILE ${NAME} =====`);
await dockerBuild(BUILDDIR, DOCKERFILE, TAGS, envs);
const imgSizeBytes = await $`docker image inspect ${TAGS[0]} --format '{{.Size}}'`;
// .pipe($`numfmt --to=iec --suffix=B`);
console.log(`Image built! Image size ${imgSizeBytes}`);
if (CMD === "build") {
process.exit(0);
}
console.log(`===== NIX CLOSURE SIZES =====`)
// Print closure sizes
// -s prints NAR size of the thing itself
// -S prints total size of it + all it's dependencies
// -r for recursive
// we omit -h (human readable sizes) so we can sort and then format after
// TODO: Readd
// | numfmt --to=iec --field=2 --suffix=B \
// | numfmt --to=iec --field=3 --suffix=B \
// ...
// | column -t
// Pipe to sed to remove any "/nix/store/ffffff..." prefixes
await $`docker run --rm ${TAGS[0]} nix path-info -rsS /home/${dotfilesUserForPath}/.nix-profile`
.pipe($`sort -k2 -n`)
.pipe($`sed 's|/nix/store/[a-z0-9]\{32\}-||g'`);
console.log(`\n\n`);
// TODO: Put this somewhere else, I like having it in the build but it makes it
// take so longggg
// Scan for vulns with vulnix
// console.log(`===== RUNNING VULNERABILITY SCAN =====`);
// console.log(`Running the vulnerability scan on just runtime dependencies`);
// // This runs vulnix in the container against JUST the runtime dependencies, not
// // all build dependencies. This is why we use the output of nix-store as vulnix
// // doesnt do this otherwise, but it greatly reduces what vulns come out (and is
// // much more what I want to check against)
// // ~/.nix-profile (the current profile, contains the home manager generation)
// // (we have to do all this with sh so we can xargs to vulnix but it has to be in the container)
// // ALSO Fuck vulnix but it throws where there's no deriver, so I have to manually
// // fetch that with nix-store first and sort those out. UGH this is a pain
// await $`docker run --rm ${TAGS[0]} sh -c ${
// `nix-store -q --requisites /home/cobertos/.nix-profile \
// | xargs -n1 nix-store -qd 2>/dev/null \
// | grep -v unknown-deriver \
// | sort -u \
// | xargs nix run nixpkgs#vulnix -- --no-requisites`
// }`.nothrow();
// // if this one returns non-zero we break the build
// console.log(`\n\n\nEnd vulnerability scan\n===================================`);
console.log(`===== PUSHING TO REPOSITORY =====`);
// Push to repository if the above passed
assert(REGISTRY_TOKEN, "REGISTRY_TOKEN must be set to push, received nothing");
assert(REGISTRY_USER, "GITHUB_ACTOR must be set to push, received nothing");
await dockerPush(REGISTRY, REGISTRY_USER, REGISTRY_TOKEN, IMAGE, TAGS);
console.log(`Finished! :3`);
console.log(`Image size ${imgSizeBytes}`);