#!/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) => { 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(tEitherJobVault, key, 'ssh_key'), ); const eitherSshKeyFile = await eitherSshKey.mapRight(({ notes }) => notes).flatMapAsync(saveToTempFile); const eitherAnsibleSecrets = await eitherJobVault.flatMapAsync(({ key, vault }) => vault.fetchSecret(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> => Either.fromFailableAsync(() => mkdtemp(join(tmpdir(), 'ci-')).then(async (dir) => { const filePath = join(dir, 'temp-file'); await writeFile(filePath, text); return filePath; }), );