summaryrefslogtreecommitdiff
path: root/worker/scripts/build_docker_image.ts
blob: 1783e7c362c34fd2b3bf1b51d7c4854c46b65ff1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#!/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) =>
        <BuildDockerImageJob>{
            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<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(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];
}