#!/usr/bin/env node import { getRequiredEnvVars, getStdout, LogTraceable, LogMetricTraceable, Metric, TraceUtil, Command, } 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.js', arguments: baseArgs, }, ); const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config)); const buildImageMetric = Metric.fromName('dockerImage.build').asResult(); const loginMetric = Metric.fromName('dockerRegistry.login').asResult(); const _logJob = LogTraceable.of(eitherJob).flatMap((tEitherJob) => { const trace = tEitherJob.get().fold( () => 'NO_BUILD_TARGET', ({ arguments: { buildTarget } }) => buildTarget, ); return tEitherJob.traceScope(() => `build_docker_image.${trace}`); }); await LogMetricTraceable.ofLogTraceable(_logJob) .flatMap(TraceUtil.withMetricTrace(buildImageMetric)) .flatMap(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(TraceUtil.promiseify(TraceUtil.traceResultingEither(loginMetric))) .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 })), ); }) .flatMapAsync(TraceUtil.promiseify(TraceUtil.traceResultingEither(buildImageMetric))) .peek( TraceUtil.promiseify((tBuilt) => tBuilt.get().fold( (err) => tBuilt.trace.trace(`oh nyoo we couldn't buiwd the img :(( ${err}`), (ok) => tBuilt.trace.traceScope('buildOutput').trace(ok.buildOutput), ), ), ) .map(async (tEitherWithBuiltImage) => { const eitherWithBuiltImage = await tEitherWithBuiltImage.get(); return eitherWithBuiltImage .mapRight(({ job }) => tEitherWithBuiltImage.move(getPushCommand(job.arguments.imageTag))) .flatMapAsync((tPushCommand) => getStdout(tPushCommand)); }) .get(); function getDockerLoginCommand(username: string, registry: string): Command { return `docker login --username ${username} --password $REGISTRY_PASSWORD ${registry}`.split(' '); } function getBuildCommand({ buildTarget, imageTag, dockerfile, context }: BuildDockerImageJobProps): Command { return ['docker', 'build', '--target', buildTarget, '-t', imageTag, '-f', dockerfile, context]; } function getPushCommand(tag: string): Command { return ['docker', 'push', tag]; }