diff options
Diffstat (limited to 'worker/jobs/ci_pipeline.run')
-rw-r--r-- | worker/jobs/ci_pipeline.run | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/worker/jobs/ci_pipeline.run b/worker/jobs/ci_pipeline.run new file mode 100644 index 0000000..337bd53 --- /dev/null +++ b/worker/jobs/ci_pipeline.run @@ -0,0 +1,166 @@ +import { + type Command, + Either, + 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) => ( + <CheckoutCiJob> { + type: "checkout_ci", + arguments: { + ...baseArgs, + run, + returnPath: Deno.cwd(), + }, + } + )); + +const ciRunMetric = Metric.fromName("checkout_ci.run"); +const trace = `checkout_ci.${run}`; +await LogMetricTraceable.from(eitherJob).bimap(TraceUtil.withTrace(trace)) + .bimap(TraceUtil.withMetricTrace(ciRunMetric)) + .map((tEitherJob) => + tEitherJob.get().flatMapAsync((ciJob) => { + const wd = getWorkingDirectoryForCiJob(ciJob); + const fetchPackageJob = <FetchCodeJob> { + type: "fetch_code", + arguments: { + remoteUrl: ciJob.arguments.remote, + checkout: ciJob.arguments.rev, + path: getSrcDirectoryForCiJob(ciJob), + }, + }; + return Either.fromFailableAsync<Error, CheckoutCiJob>( + 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<Error, string>( + Deno.readTextFile( + `${getSrcDirectoryForCiJob(ciJob)}/${CI_WORKFLOW_FILE}`, + ), + ).then((eitherWorkflowJson) => + eitherWorkflowJson.flatMap( + (json) => Either.fromFailable<Error, unknown>(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((err, val) => { + if (err) throw err; + return Deno.remove(getWorkingDirectoryForCiJob(val), { 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"; |