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;
}),
);
}
|