#!/usr/bin/env -S deno run --allow-all import { type Command, Either, LogTraceable, getRequiredEnvVars, getStdout, isObject, LogMetricTraceable, Metric, prependWith, TraceUtil, } from "@emprespresso/pengueno"; import { type CheckoutCiJob, type FetchCodeJob, PipelineImpl, } from "@emprespresso/ci_model"; import { executeJob, executePipeline } from "@emprespresso/ci_worker"; const run = Date.now().toString(); const eitherJob = getRequiredEnvVars(["remote", "refname", "rev"]).mapRight( (baseArgs) => { type: "checkout_ci.ts", arguments: { ...baseArgs, run, returnPath: Deno.cwd(), }, }, ); const ciRunMetric = Metric.fromName("checkout_ci.run"); const _logJob = LogTraceable.of(eitherJob).bimap( TraceUtil.withTrace(`checkout_ci.${run}`), ); await LogMetricTraceable.ofLogTraceable(_logJob) .bimap(TraceUtil.withMetricTrace(ciRunMetric)) .map((tEitherJob) => tEitherJob.get().flatMapAsync((ciJob) => { const wd = getWorkingDirectoryForCiJob(ciJob); const fetchPackageJob = { type: "fetch_code.ts", arguments: { remoteUrl: ciJob.arguments.remote, checkout: ciJob.arguments.rev, path: getSrcDirectoryForCiJob(ciJob), }, }; return Either.fromFailableAsync(() => Deno.mkdir(wd) .then(() => Deno.chdir(wd)) .then(() => tEitherJob.move(fetchPackageJob).map(executeJob).get()) .then(() => ciJob), ); }), ) .map((tEitherCiJob) => tEitherCiJob.get().then((eitherCiJob) => eitherCiJob.flatMapAsync<{ cmd: Command; job: CheckoutCiJob }>((ciJob) => Either.fromFailableAsync(() => Deno.readTextFile( `${getSrcDirectoryForCiJob(ciJob)}/${CI_WORKFLOW_FILE}`, ), ).then((eitherWorkflowJson) => eitherWorkflowJson .flatMap((json) => Either.fromFailable(JSON.parse(json)), ) .flatMap((eitherWorkflowParse) => { if (isCiWorkflow(eitherWorkflowParse)) { return Either.right({ cmd: getPipelineGenerationCommand( ciJob, eitherWorkflowParse.workflow, ), job: ciJob, }); } return Either.left( new Error( "couldn't find any valid ci configuration (。•́︿•̀。), that's okay~", ), ); }), ), ), ), ) .map(async (tEitherPipelineGenerationCommand) => { const eitherJobCommand = await tEitherPipelineGenerationCommand.get(); const eitherPipeline = await eitherJobCommand.flatMapAsync((jobCommand) => tEitherPipelineGenerationCommand .move(jobCommand.cmd) .map(getStdout) .get(), ); return eitherPipeline .flatMap(PipelineImpl.from) .flatMap((pipeline) => eitherJobCommand.mapRight(({ job }) => ({ job, pipeline })), ); }) .peek( TraceUtil.promiseify((tEitherPipeline) => tEitherPipeline .get() .mapRight((val) => val.pipeline.serialize()) .mapRight( (pipeline) => `built the pipeline~ (◕ᴗ◕✿) let's make something amazing! ${pipeline}`, ) .mapRight((msg) => tEitherPipeline.trace.trace(msg)), ), ) .map(async (tEitherPipeline) => { const eitherPipeline = await tEitherPipeline.get(); return eitherPipeline.flatMapAsync(({ pipeline, job }) => tEitherPipeline .move(pipeline) .map((p) => executePipeline(p, { HOME: getWorkingDirectoryForCiJob(job), }), ) .get(), ); }) .get() .then((e) => e .flatMap(() => eitherJob) .fold(({ isLeft, isRight, value }) => { if (isLeft || !isRight) throw value; return Deno.remove(getWorkingDirectoryForCiJob(value), { recursive: true, }); }), ); const getWorkingDirectoryForCiJob = (job: CheckoutCiJob) => `${job.arguments.returnPath}/${job.arguments.run}`; const getSrcDirectoryForCiJob = (job: CheckoutCiJob) => `${job.arguments.returnPath}/${job.arguments.run}/src`; const _runFlags = ( "--rm --network none --cap-drop ALL" + "--security-opt no-new-privileges" ).split(" "); const _image = "oci.liz.coffee/img/ci-worker:release"; const getPipelineGenerationCommand = ( job: CheckoutCiJob, pipelineGeneratorPath: string, image = _image, runFlags = _runFlags, ) => [ "docker", "run", ...runFlags, ...prependWith( Object.entries(job.arguments).map(([key, val]) => `"${key}"="${val}"`), "-e", ), "-v", `${getSrcDirectoryForCiJob( job, )}/${pipelineGeneratorPath}:/pipeline_generator`, image, "/pipeline_generator", ]; export interface CiWorkflow { workflow: string; } export const isCiWorkflow = (t: unknown): t is CiWorkflow => isObject(t) && "workflow" in t && typeof t.workflow === "string" && !t.workflow.includes(".."); const CI_WORKFLOW_FILE = ".ci/ci.json";