diff options
Diffstat (limited to 'worker/scripts/ansible_playbook')
-rwxr-xr-x | worker/scripts/ansible_playbook | 167 |
1 files changed, 101 insertions, 66 deletions
diff --git a/worker/scripts/ansible_playbook b/worker/scripts/ansible_playbook index 062680d..e9e967c 100755 --- a/worker/scripts/ansible_playbook +++ b/worker/scripts/ansible_playbook @@ -1,78 +1,113 @@ #!/usr/bin/env -S deno run --allow-env --allow-net --allow-run --allow-read --allow-write import { - BitwardenSession, - getRequiredEnv, + Either, + getRequiredEnvVars, getStdout, - loggerWithPrefix, + type IEither, + LogMetricTraceable, + Metric, prependWith, - type SecureNote, -} from "@liz-ci/utils"; -import type { AnsiblePlaybookJobProps } from "@liz-ci/model"; + TraceUtil, +} from "@emprespresso/pengueno"; +import type { AnsiblePlaybookJob } from "@emprespresso/ci-model"; +import { Bitwarden, type SecureNote } from "@emprespresso/ci-worker"; -const args: AnsiblePlaybookJobProps = { - path: getRequiredEnv("path"), - playbooks: getRequiredEnv("playbooks"), -}; -const logger = loggerWithPrefix(() => - `[${new Date().toISOString()}] [ansible_playbook.'${args.playbooks}']` -); +const eitherJob = getRequiredEnvVars([ + "path", + "playbooks", +]) + .mapRight((baseArgs) => ( + <AnsiblePlaybookJob> { + type: "ansible_playbook", + arguments: baseArgs, + } + )); -const run = async () => { - logger.log("Starting Ansible playbook job"); +const eitherVault = Bitwarden.getConfigFromEnvironment() + .mapRight((config) => new Bitwarden(config)); - const bitwardenSession = new BitwardenSession(); - const secretFiles = await Promise.all( - ["ansible_secrets", "ssh_key"] - .map((secretName) => - bitwardenSession - .getItem<SecureNote>(secretName) - .then(async ({ notes: recoveredSecret }) => { - const tempFile = await Deno.makeTempFile(); - await Deno.writeTextFile(tempFile, recoveredSecret); - logger.log(secretName, "stored at", tempFile); - return tempFile; - }) - ), - ); - const [ansibleSecrets, sshKey] = secretFiles; +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(); - try { - const volumes = [ - `${args.path}:/ansible`, - `${sshKey}:/root/id_rsa`, - `${ansibleSecrets}:/ansible/secrets.yml`, - ]; + 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); - const playbookCmd = `ansible-playbook -e @secrets.yml ${args.playbooks}`; - const deployCmd = [ - "docker", - "run", - ...prependWith(volumes, "-v"), - "willhallonline/ansible:latest", - ...playbookCmd.split(" "), - ]; - logger.log("deploying...", deployCmd); - await getStdout(deployCmd); - } finally { - await Promise.allSettled( - [bitwardenSession.close()].concat( - secretFiles.map((p) => { - logger.log(`cleanup`, p); - return Deno.remove(p); - }), - ), + 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(); - logger.log("ansible playbook job completed"); -}; - -if (import.meta.main) { - try { - await run(); - } catch (e) { - logger.error("womp womp D:", e); - throw e; - } -} +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; + }), + ); |