#!/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'; import path from 'path'; const job = getRequiredEnvVars([ 'registry', 'namespace', 'repository', 'imageTag', 'context', 'dockerfile', 'buildTarget', ]) .mapRight( (baseArgs) => { type: 'build_docker_image.js', arguments: baseArgs, }, ) .fold( (err) => { throw err; }, (x) => x, ); 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(job).flatMap((tJob) => { const trace = tJob.get().arguments.buildTarget; return tJob.traceScope(() => `build_docker_image.${trace}`); }); await LogMetricTraceable.ofLogTraceable(_logJob) .flatMap(TraceUtil.withMetricTrace(buildImageMetric)) .flatMap(TraceUtil.withMetricTrace(loginMetric)) .peek((tJob) => tJob.trace.trace('starting docker image build job! (⑅˘꒳˘)')) .map((tJob) => eitherVault.flatMapAsync(async (vault) => { const job = tJob.get(); const eitherKey = await vault.unlock(tJob); 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(({ arguments: args }) => tEitherWithAuthdRegistryBuildJob .move(getBuildCommand(args)) .map((tBuildCmd) => getStdout(tBuildCmd, { env: {}, clearEnv: true, }), ) .get(), ); return eitherBuiltImage.joinRight(eitherWithAuthdRegistryBuildJob, (job, buildOutput) => ({ job, buildOutput, })); }) .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)); }) .map(async (tEitherJob) => { const eitherJob = await tEitherJob.get(); return eitherJob.fold( (e) => Promise.reject(e), () => Promise.resolve(0), ); }) .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', path.join(context, dockerfile), context]; } function getPushCommand(tag: string): Command { return ['docker', 'push', tag]; }