diff options
Diffstat (limited to 'worker/scripts/build_docker_image')
-rwxr-xr-x | worker/scripts/build_docker_image | 161 |
1 files changed, 161 insertions, 0 deletions
diff --git a/worker/scripts/build_docker_image b/worker/scripts/build_docker_image new file mode 100755 index 0000000..1dd0c3d --- /dev/null +++ b/worker/scripts/build_docker_image @@ -0,0 +1,161 @@ +#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run + +import { + getRequiredEnvVars, + getStdout, + LogLevel, + 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", + arguments: baseArgs, + } + )); +const eitherVault = Bitwarden.getConfigFromEnvironment() + .mapRight((config) => new Bitwarden(config)); + +const buildImageMetric = Metric.fromName("dockerImage.build"); +const loginMetric = Metric.fromName("dockerRegistry.login"); +await LogMetricTraceable.from(eitherJob) + .bimap( + (tEitherJob) => { + const trace = "build_docker_image." + + tEitherJob.get().fold((_, v) => v?.buildTarget ?? ""); + return [tEitherJob.get(), trace]; + }, + ) + .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((err, _val) => + loginMetric[err ? "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((err, val) => { + tEitherWithBuiltImage.trace.trace( + buildImageMetric[err ? "failure" : "success"], + ); + if (err) { + tEitherWithBuiltImage.trace.addTrace(LogLevel.ERROR).trace( + `oh nyoo we couldn't buiwd the img :(( ${err}`, + ); + return; + } + tEitherWithBuiltImage.trace.addTrace("buildOutput").trace(val); + }); + }) + .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]; |