summaryrefslogtreecommitdiff
path: root/worker/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'worker/scripts')
-rwxr-xr-xworker/scripts/ansible_playbook.ts (renamed from worker/scripts/ansible_playbook)0
-rwxr-xr-xworker/scripts/build_docker_image.ts (renamed from worker/scripts/build_docker_image)2
-rwxr-xr-xworker/scripts/checkout_ci.ts182
3 files changed, 183 insertions, 1 deletions
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";