| 1 | import * as Alchemy from "alchemy"; |
| 2 | import * as Effect from "effect/Effect"; |
| 3 | import * as FileSystem from "effect/FileSystem"; |
| 4 | import * as Path from "effect/Path"; |
| 5 | import * as Stream from "effect/Stream"; |
| 6 | import * as ChildProcess from "effect/process/ChildProcess"; |
| 7 | import * as ChildProcessSpawner from "effect/process/ChildProcessSpawner"; |
| 8 | |
| 9 | const run = Effect.fn(function* (bin: string, args: string[], cwd?: string) { |
| 10 | const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; |
| 11 | const result = yield* ChildProcess.make(bin, args, { |
| 12 | cwd, |
| 13 | stdin: "ignore", |
| 14 | stdout: "pipe", |
| 15 | stderr: "pipe", |
| 16 | detached: false, |
| 17 | extendEnv: true, |
| 18 | }).pipe( |
| 19 | spawner.spawn, |
| 20 | Effect.flatMap((child) => |
| 21 | Effect.all( |
| 22 | { |
| 23 | exitCode: child.exitCode, |
| 24 | stderr: child.stderr.pipe(Stream.decodeText, Stream.mkString), |
| 25 | }, |
| 26 | { concurrency: "unbounded" }, |
| 27 | ), |
| 28 | ), |
| 29 | ); |
| 30 | if (result.exitCode !== 0) { |
| 31 | return yield* Effect.fail( |
| 32 | new Error( |
| 33 | `${bin} ${args.join(" ")} exited ${result.exitCode}: ${result.stderr}`, |
| 34 | ), |
| 35 | ); |
| 36 | } |
| 37 | }); |
| 38 | |
| 39 | /** |
| 40 | * Deploy-time Action that fetches the AWS HyperPod Helm chart (the |
| 41 | * MANDATORY dependencies — health-monitoring agent, training operators, |
| 42 | * device plugins, RBAC — that SageMaker validates before an EKS-orchestrated |
| 43 | * cluster will attach) and vendors its subcharts. Returns the local chart |
| 44 | * path for `Kubernetes.HelmChart` to render. |
| 45 | */ |
| 46 | export const FetchHyperPodChart = Alchemy.Action( |
| 47 | "FetchHyperPodChart", |
| 48 | (input: { repo: string }) => |
| 49 | Effect.gen(function* () { |
| 50 | const fs = yield* FileSystem.FileSystem; |
| 51 | const path = yield* Path.Path; |
| 52 | const cwd = yield* Effect.sync(() => process.cwd()); |
| 53 | const cloneDir = path.join(cwd, ".alchemy", "cache", "hyperpod-cli"); |
| 54 | if (!(yield* fs.exists(path.join(cloneDir, "helm_chart")))) { |
| 55 | yield* run("git", ["clone", "--depth", "1", input.repo, cloneDir]); |
| 56 | } |
| 57 | const chartPath = path.join(cloneDir, "helm_chart", "HyperPodHelmChart"); |
| 58 | yield* run("helm", ["dependency", "update", chartPath]); |
| 59 | return { chartPath }; |
| 60 | }), |
| 61 | ); |