summaryrefslogtreecommitdiff
path: root/worker/scripts/ansible_playbook.ts
blob: 6eb4d47c74818ede8e62cc2bfdccc443748443c2 (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
#!/usr/bin/env node

import {
    Either,
    getRequiredEnvVars,
    getStdout,
    type IEither,
    LogTraceable,
    LogMetricTraceable,
    Metric,
    prependWith,
    TraceUtil,
} from '@emprespresso/pengueno';
import type { AnsiblePlaybookJob } from '@emprespresso/ci_model';
import { Bitwarden, type SecureNote } from '@emprespresso/ci_worker';
import { writeFile, mkdtemp } from 'fs/promises';
import { join } from 'path';
import { tmpdir } from 'os';

const eitherJob = getRequiredEnvVars(['path', 'playbooks']).mapRight(
    (baseArgs) =>
        <AnsiblePlaybookJob>{
            type: 'ansible_playbook.js',
            arguments: baseArgs,
        },
);

const eitherVault = Bitwarden.getConfigFromEnvironment().mapRight((config) => new Bitwarden(config));

const playbookMetric = Metric.fromName('ansiblePlaybook.playbook');
const _logJob = LogTraceable.of(eitherJob).flatMap(TraceUtil.withTrace('ansible_playbook'));
await LogMetricTraceable.ofLogTraceable(_logJob)
    .flatMap(TraceUtil.withMetricTrace(playbookMetric))
    .peek((tEitherJob) => tEitherJob.trace.trace('starting ansible playbook 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('getting ansible secwets uwu~');
        const eitherJobVault = await tEitherJobVault.get();

        const eitherSshKey = await eitherJobVault.flatMapAsync(({ key, vault }) =>
            vault.fetchSecret<SecureNote>(tEitherJobVault, key, 'ssh_key'),
        );
        const eitherSshKeyFile = await eitherSshKey.mapRight(({ notes }) => notes).flatMapAsync(saveToTempFile);
        const eitherAnsibleSecrets = await eitherJobVault.flatMapAsync(({ key, vault }) =>
            vault.fetchSecret<SecureNote>(tEitherJobVault, key, 'ansible_playbooks'),
        );
        const eitherAnsibleSecretsFile = await eitherAnsibleSecrets
            .mapRight(({ notes }) => notes)
            .flatMapAsync(saveToTempFile);

        return eitherJobVault.flatMapAsync(async ({ job, vault, key }) => {
            const eitherLocked = await vault.lock(tEitherJobVault, key);
            return eitherLocked.flatMap((_locked) =>
                eitherSshKeyFile.flatMap((sshKeyFile) =>
                    eitherAnsibleSecretsFile.mapRight((secretsFile) => ({
                        job,
                        sshKeyFile,
                        secretsFile,
                    })),
                ),
            );
        });
    })
    .map(async (tEitherJobAndSecrets) => {
        const eitherJobAndSecrets = await tEitherJobAndSecrets.get();
        return eitherJobAndSecrets.flatMapAsync(({ job, sshKeyFile, secretsFile }) => {
            const volumes = [
                `${job.arguments.path}:/ansible`,
                `${sshKeyFile}:/root/id_rsa`,
                `${secretsFile}:/ansible/secrets.yml`,
            ];
            const playbookCmd = `ansible-playbook -e @secrets.yml ${job.arguments.playbooks}`;
            const deployCmd = [
                'docker',
                'run',
                ...prependWith(volumes, '-v'),
                'willhallonline/ansible:latest',
                ...playbookCmd.split(' '),
            ];
            tEitherJobAndSecrets.trace.trace(`running ansible magic~ (◕ᴗ◕✿) ${deployCmd}`);
            return tEitherJobAndSecrets.move(deployCmd).map(getStdout).get();
        });
    })
    .map(async (tEitherJob) => {
        const eitherJob = await tEitherJob.get();
        return eitherJob.fold(
            (e) => Promise.reject(e),
            () => Promise.resolve(0),
        );
    })
    .get();

function saveToTempFile(text: string): Promise<IEither<Error, string>> {
    return Either.fromFailableAsync(() =>
        mkdtemp(join(tmpdir(), 'ci-')).then(async (dir) => {
            const filePath = join(dir, 'temp-file');
            await writeFile(filePath, text);
            return filePath;
        }),
    );
}