#!/usr/bin/env node 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.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(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];