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
|
#!/usr/bin/env -S deno run --allow-env --allow-net --allow-run --allow-read --allow-write
import {
Either,
getRequiredEnvVars,
getStdout,
type IEither,
LogMetricTraceable,
Metric,
prependWith,
TraceUtil,
} from "@emprespresso/pengueno";
import type { AnsiblePlaybookJob } from "@emprespresso/ci-model";
import { Bitwarden, type SecureNote } from "@emprespresso/ci-worker";
const eitherJob = getRequiredEnvVars([
"path",
"playbooks",
])
.mapRight((baseArgs) => (
<AnsiblePlaybookJob> {
type: "ansible_playbook",
arguments: baseArgs,
}
));
const eitherVault = Bitwarden.getConfigFromEnvironment()
.mapRight((config) => new Bitwarden(config));
const playbookMetric = Metric.fromName("ansiblePlaybook.playbook");
await LogMetricTraceable.from(eitherJob)
.bimap(TraceUtil.withTrace("ansible_playbook"))
.bimap(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();
},
);
})
.get();
const saveToTempFile = (text: string): Promise<IEither<Error, string>> =>
Either.fromFailableAsync(
Deno.makeTempDir({ dir: Deno.cwd() })
.then((dir) => Deno.makeTempFile({ dir }))
.then(async (f) => {
await Deno.writeTextFile(f, text);
return f;
}),
);
|