| 1 | import * as Auth from "@distilled.cloud/aws/Auth"; |
| 2 | import * as EffectConsole from "effect/Console"; |
| 3 | import * as ConfigProvider from "effect/ConfigProvider"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Layer from "effect/Layer"; |
| 6 | import * as Option from "effect/Option"; |
| 7 | import { AuthError } from "../../Auth/AuthProvider.ts"; |
| 8 | import { silentConsole } from "../../AWS/AuthProvider.ts"; |
| 9 | import { |
| 10 | bootstrap as bootstrapAws, |
| 11 | destroyBootstrap as destroyBootstrapAws, |
| 12 | } from "../../AWS/Bootstrap.ts"; |
| 13 | import * as AWSCredentials from "../../AWS/Credentials.ts"; |
| 14 | import { AWSEnvironment } from "../../AWS/Environment.ts"; |
| 15 | import * as AWSRegion from "../../AWS/Region.ts"; |
| 16 | import { loadConfigProvider } from "../../Util/ConfigProvider.ts"; |
| 17 | import type { Target } from "../Session.ts"; |
| 18 | |
| 19 | export interface AwsTarget extends Target { |
| 20 | readonly profile: string; |
| 21 | readonly region?: string; |
| 22 | } |
| 23 | |
| 24 | const environment = Effect.fn(function* (target: AwsTarget) { |
| 25 | const ssoProfile = yield* Auth.loadProfile(target.profile); |
| 26 | if (!ssoProfile.sso_account_id) { |
| 27 | return yield* Effect.fail( |
| 28 | new AuthError({ |
| 29 | message: `AWS SSO profile '${target.profile}' is missing sso_account_id`, |
| 30 | }), |
| 31 | ); |
| 32 | } |
| 33 | const region = target.region ?? ssoProfile.region ?? "us-east-1"; |
| 34 | // The credentials effect runs later, inside the built layer, where these |
| 35 | // services are no longer in context — capture them here so the route's |
| 36 | // requirements stay visible in its type. |
| 37 | const credentialServices = |
| 38 | yield* Effect.context< |
| 39 | Effect.Services<ReturnType<typeof Auth.loadProfileCredentials>> |
| 40 | >(); |
| 41 | const aws = Layer.provideMerge( |
| 42 | Layer.mergeAll(AWSRegion.fromEnvironment, AWSCredentials.fromEnvironment), |
| 43 | Layer.succeed( |
| 44 | AWSEnvironment, |
| 45 | Effect.succeed({ |
| 46 | accountId: ssoProfile.sso_account_id, |
| 47 | region, |
| 48 | credentials: Auth.loadProfileCredentials(target.profile).pipe( |
| 49 | Effect.provideService(EffectConsole.Console, silentConsole), |
| 50 | Effect.provide(credentialServices), |
| 51 | ), |
| 52 | profile: target.profile, |
| 53 | }), |
| 54 | ), |
| 55 | ); |
| 56 | return { |
| 57 | accountId: ssoProfile.sso_account_id, |
| 58 | region, |
| 59 | layer: Layer.provide( |
| 60 | aws, |
| 61 | ConfigProvider.layer( |
| 62 | yield* loadConfigProvider(Option.fromNullishOr(target.envFile)), |
| 63 | ), |
| 64 | ), |
| 65 | }; |
| 66 | }); |
| 67 | |
| 68 | /** Provision the AWS deployment assets bucket. */ |
| 69 | export const bootstrap = Effect.fn("Alchemist.provider.aws.bootstrap")( |
| 70 | function* (target: AwsTarget) { |
| 71 | const env = yield* environment(target); |
| 72 | const result = yield* Effect.provide(bootstrapAws(), env.layer); |
| 73 | return { accountId: env.accountId, region: env.region, ...result }; |
| 74 | }, |
| 75 | ); |
| 76 | |
| 77 | /** Destroy every Alchemy bootstrap bucket in the region. */ |
| 78 | export const teardown = Effect.fn("Alchemist.provider.aws.teardown")(function* ( |
| 79 | target: AwsTarget, |
| 80 | ) { |
| 81 | const env = yield* environment(target); |
| 82 | const result = yield* Effect.provide(destroyBootstrapAws(), env.layer); |
| 83 | return { |
| 84 | accountId: env.accountId, |
| 85 | region: env.region, |
| 86 | destroyed: result.bucketNames, |
| 87 | }; |
| 88 | }); |