summaryrefslogtreecommitdiff
path: root/worker/scripts/build_docker_image.ts
diff options
context:
space:
mode:
Diffstat (limited to 'worker/scripts/build_docker_image.ts')
-rwxr-xr-xworker/scripts/build_docker_image.ts162
1 files changed, 162 insertions, 0 deletions
diff --git a/worker/scripts/build_docker_image.ts b/worker/scripts/build_docker_image.ts
new file mode 100755
index 0000000..49abe41
--- /dev/null
+++ b/worker/scripts/build_docker_image.ts
@@ -0,0 +1,162 @@
+#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run
+
+import {
+ getRequiredEnvVars,
+ getStdout,
+ LogLevel,
+ LogTraceable,
+ LogMetricTraceable,
+ Metric,
+ TraceUtil,
+} from "@emprespresso/pengueno";
+import type {
+ BuildDockerImageJob,
+ BuildDockerImageJobProps,
+} from "@emprespresso/ci_model";
+import { Bitwarden, type LoginItem } from "@emprespresso/ci_worker";
+
+const eitherJob = getRequiredEnvVars([
+ "registry",
+ "namespace",
+ "repository",
+ "imageTag",
+ "context",
+ "dockerfile",
+ "buildTarget",
+])
+ .mapRight((baseArgs) => (
+ <BuildDockerImageJob> {
+ type: "build_docker_image.ts",
+ arguments: baseArgs,
+ }
+ ));
+const eitherVault = Bitwarden.getConfigFromEnvironment()
+ .mapRight((config) => new Bitwarden(config));
+
+const buildImageMetric = Metric.fromName("dockerImage.build");
+const loginMetric = Metric.fromName("dockerRegistry.login");
+const _logJob = LogTraceable.of(eitherJob).bimap(
+ (tEitherJob) => {
+ const trace = "build_docker_image." +
+ tEitherJob.get().fold(({ isRight, value }) => isRight ? value.arguments.buildTarget : "");
+ return [tEitherJob.get(), trace];
+ },
+ );
+await LogMetricTraceable.ofLogTraceable(_logJob)
+ .bimap(TraceUtil.withMetricTrace(buildImageMetric))
+ .bimap(TraceUtil.withMetricTrace(loginMetric))
+ .peek((tEitherJob) =>
+ tEitherJob.trace.trace("starting docker image build job! (⑅˘꒳˘)")
+ )
+ .map((tEitherJob) =>
+ tEitherJob.get()
+ .flatMapAsync((job) =>
+ eitherVault.flatMapAsync(async (vault) => {
+ const eitherKey = await vault.unlock(tEitherJob);
+ return eitherKey.mapRight((key) => ({ job, key, vault }));
+ })
+ )
+ )
+ .map(async (tEitherJobVault) => {
+ tEitherJobVault.trace.trace("logging into the wegistwy uwu~");
+ const eitherJobVault = await tEitherJobVault.get();
+ const eitherDockerRegistryLoginItem = await eitherJobVault.flatMapAsync((
+ { job, key, vault },
+ ) =>
+ vault.fetchSecret<LoginItem>(tEitherJobVault, key, job.arguments.registry)
+ .finally(() => vault.lock(tEitherJobVault, key))
+ );
+ return eitherDockerRegistryLoginItem.flatMapAsync(({ login }) =>
+ eitherJobVault.flatMapAsync(async ({ job }) => {
+ const loginCommand = getDockerLoginCommand(
+ login.username,
+ job.arguments.registry,
+ );
+ const eitherLoggedIn = await tEitherJobVault.move(loginCommand).map((
+ tLoginCmd,
+ ) =>
+ getStdout(tLoginCmd, { env: { REGISTRY_PASSWORD: login.password } })
+ ).get();
+ return eitherLoggedIn.moveRight(job);
+ })
+ );
+ })
+ .peek(async (tEitherWithAuthdRegistry) => {
+ const eitherWithAuthdRegistry = await tEitherWithAuthdRegistry.get();
+ return tEitherWithAuthdRegistry.trace.trace(
+ eitherWithAuthdRegistry.fold(({ isLeft}) =>
+ loginMetric[isLeft ? "failure" : "success"]
+ ),
+ );
+ })
+ .map(async (tEitherWithAuthdRegistryBuildJob) => {
+ const eitherWithAuthdRegistryBuildJob =
+ await tEitherWithAuthdRegistryBuildJob.get();
+ tEitherWithAuthdRegistryBuildJob.trace.trace(
+ "finally building the image~ (◕ᴗ◕✿)",
+ );
+ const eitherBuiltImage = await eitherWithAuthdRegistryBuildJob.flatMapAsync(
+ (job) =>
+ tEitherWithAuthdRegistryBuildJob
+ .move(getBuildCommand(job.arguments))
+ .map((tBuildCmd) =>
+ getStdout(tBuildCmd, {
+ env: {},
+ clearEnv: true,
+ })
+ )
+ .get(),
+ );
+ return eitherBuiltImage.flatMap((buildOutput) =>
+ eitherWithAuthdRegistryBuildJob.mapRight((job) => ({ buildOutput, job }))
+ );
+ })
+ .peek(async (tEitherWithBuiltImage) => {
+ const eitherWithBuiltImage = await tEitherWithBuiltImage.get();
+ eitherWithBuiltImage.fold(({ isLeft, value}) => {
+ tEitherWithBuiltImage.trace.trace(
+ buildImageMetric[isLeft ? "failure" : "success"],
+ );
+ if (isLeft) {
+ tEitherWithBuiltImage.trace.addTrace(LogLevel.ERROR).trace(
+ `oh nyoo we couldn't buiwd the img :(( ${value}`,
+ );
+ return;
+ }
+ tEitherWithBuiltImage.trace.addTrace("buildOutput").trace(value.buildOutput);
+ });
+ })
+ .map(async (tEitherWithBuiltImage) => {
+ const eitherWithBuiltImage = await tEitherWithBuiltImage.get();
+ return eitherWithBuiltImage
+ .mapRight(({ job }) =>
+ tEitherWithBuiltImage.move(getPushCommand(job.arguments.imageTag))
+ )
+ .flatMapAsync((tPushCommand) => getStdout(tPushCommand));
+ })
+ .get();
+
+const getDockerLoginCommand = (username: string, registry: string) =>
+ `docker login --username ${username} --password $REGISTRY_PASSWORD ${registry}`
+ .split(" ");
+
+const getBuildCommand = (
+ {
+ buildTarget,
+ imageTag,
+ dockerfile,
+ context,
+ }: BuildDockerImageJobProps,
+) => [
+ "docker",
+ "build",
+ "--target",
+ buildTarget,
+ "-t",
+ imageTag,
+ "-f",
+ dockerfile,
+ context,
+];
+
+const getPushCommand = (tag: string) => ["docker", "push", tag];