diff options
author | Elizabeth Hunt <me@liz.coffee> | 2025-06-15 21:13:00 -0700 |
---|---|---|
committer | Elizabeth Hunt <me@liz.coffee> | 2025-06-15 21:13:00 -0700 |
commit | 037c85fdd373322a84afd8acd9c652deeab37520 (patch) | |
tree | 9d830ca8b2f79e3e2ec2e5218873e0028d7fede6 | |
parent | 1e38d81ceec451f25d3a11fbd82e5c225848d172 (diff) | |
download | ci-037c85fdd373322a84afd8acd9c652deeab37520.tar.gz ci-037c85fdd373322a84afd8acd9c652deeab37520.zip |
Use .ts extensions for deno parser
-rw-r--r-- | .ci/ci.ts | 8 | ||||
-rw-r--r-- | model/job.ts | 8 | ||||
-rwxr-xr-x | worker/jobs/ci_pipeline.run | 170 | ||||
-rwxr-xr-x | worker/scripts/ansible_playbook.ts (renamed from worker/scripts/ansible_playbook) | 0 | ||||
-rwxr-xr-x | worker/scripts/build_docker_image.ts (renamed from worker/scripts/build_docker_image) | 2 | ||||
-rwxr-xr-x | worker/scripts/checkout_ci.ts | 182 |
6 files changed, 193 insertions, 177 deletions
@@ -24,7 +24,7 @@ const getPipeline = () => { }; const baseCiPackageBuild: BuildDockerImageJob = { - type: "build_docker_image", + type: "build_docker_image.ts", arguments: { ...commonBuildArgs, context: gitHookPipeline.getSourceDestination(), @@ -38,7 +38,7 @@ const getPipeline = () => { }); const subPackages = ["worker", "hooks"].map((_package) => ({ - type: "build_docker_image", + type: "build_docker_image.ts", arguments: { ...commonBuildArgs, repository: `${IMG}_${_package}`, @@ -56,7 +56,7 @@ const getPipeline = () => { } const fetchAnsibleCode: FetchCodeJob = { - type: "fetch_code", + type: "fetch_code.ts", arguments: { remoteUrl: `${REMOTE}/infra`, checkout: "main", @@ -64,7 +64,7 @@ const getPipeline = () => { }, }; const thenDeploy: AnsiblePlaybookJob = { - type: "ansible_playbook", + type: "ansible_playbook.ts", arguments: { path: "infra", playbooks: "playbooks/ci.yml", diff --git a/model/job.ts b/model/job.ts index 52386eb..187ed56 100644 --- a/model/job.ts +++ b/model/job.ts @@ -22,7 +22,7 @@ export interface FetchCodeJobProps extends JobArgT { } export interface FetchCodeJob { - readonly type: "fetch_code"; + readonly type: "fetch_code.ts"; readonly arguments: FetchCodeJobProps; } @@ -38,7 +38,7 @@ export interface BuildDockerImageJobProps extends JobArgT { } export interface BuildDockerImageJob extends Job { - readonly type: "build_docker_image"; + readonly type: "build_docker_image.ts"; readonly arguments: BuildDockerImageJobProps; } @@ -48,7 +48,7 @@ export interface AnsiblePlaybookJobProps extends JobArgT { } export interface AnsiblePlaybookJob extends Job { - readonly type: "ansible_playbook"; + readonly type: "ansible_playbook.ts"; readonly arguments: AnsiblePlaybookJobProps; } @@ -62,6 +62,6 @@ export interface CheckoutCiJobProps extends JobArgT { } export interface CheckoutCiJob extends Job { - readonly type: "checkout_ci"; + readonly type: "checkout_ci.ts"; readonly arguments: CheckoutCiJobProps; } diff --git a/worker/jobs/ci_pipeline.run b/worker/jobs/ci_pipeline.run index 66b02ed..c2f1880 100755 --- a/worker/jobs/ci_pipeline.run +++ b/worker/jobs/ci_pipeline.run @@ -1,169 +1,3 @@ -#!/usr/bin/env -S deno run --allow-all +#!/bin/sh -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) => ( - <CheckoutCiJob> { - type: "checkout_ci", - 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 = <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(({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"; +checkout_ci.ts diff --git a/worker/scripts/ansible_playbook b/worker/scripts/ansible_playbook.ts index fe2810b..fe2810b 100755 --- a/worker/scripts/ansible_playbook +++ b/worker/scripts/ansible_playbook.ts diff --git a/worker/scripts/build_docker_image b/worker/scripts/build_docker_image.ts index f2fa522..49abe41 100755 --- a/worker/scripts/build_docker_image +++ b/worker/scripts/build_docker_image.ts @@ -26,7 +26,7 @@ const eitherJob = getRequiredEnvVars([ ]) .mapRight((baseArgs) => ( <BuildDockerImageJob> { - type: "build_docker_image", + type: "build_docker_image.ts", arguments: baseArgs, } )); diff --git a/worker/scripts/checkout_ci.ts b/worker/scripts/checkout_ci.ts new file mode 100755 index 0000000..efe74fb --- /dev/null +++ b/worker/scripts/checkout_ci.ts @@ -0,0 +1,182 @@ +#!/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) => + <CheckoutCiJob>{ + 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 = <FetchCodeJob>{ + type: "fetch_code.ts", + 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(({ 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"; |