#!/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) => ( { 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"); 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(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];