t3-code-android-nightly/.repos/alchemy-effect/packages/alchemy/test/AWS/EC2/fixtures/bindings-handler.ts
Julius Marminge 6f9cea00ae
chore(refs): sync Effect and Alchemy references to 4.0.1 and beta.80 (#16170)
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-05 13:22:30 -07:00

319 lines
11 KiB
TypeScript

import * as AWS from "@/AWS";
import {
amazonLinux2023,
AuthorizeSecurityGroupIngress,
AuthorizeSecurityGroupIngressHttp,
consumeInstanceStateEvents,
CreateSnapshot,
CreateSnapshotHttp,
DescribeInstance,
DescribeInstanceHttp,
DescribeInstanceStatus,
DescribeInstanceStatusHttp,
GetConsoleOutput,
GetConsoleOutputHttp,
GetPasswordData,
GetPasswordDataHttp,
Instance,
RebootInstance,
RebootInstanceHttp,
RevokeSecurityGroupIngress,
RevokeSecurityGroupIngressHttp,
SecurityGroup,
StartInstance,
StartInstanceHttp,
StopInstance,
StopInstanceHttp,
Subnet,
Volume,
Vpc,
} from "@/AWS/EC2";
import type * as ec2 from "@distilled.cloud/aws/ec2";
import * as Context from "effect/Context";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Redacted from "effect/Redacted";
import * as Schedule from "effect/Schedule";
import * as Stream from "effect/Stream";
import { HttpServerRequest } from "effect/http/HttpServerRequest";
import * as HttpServerResponse from "effect/http/HttpServerResponse";
type BindingError =
| ec2.DescribeInstancesError
| ec2.DescribeInstanceStatusError
| ec2.StartInstancesError
| ec2.StopInstancesError
| ec2.RebootInstancesError
| ec2.GetConsoleOutputError
| ec2.GetPasswordDataError
| ec2.CreateSnapshotError
| ec2.AuthorizeSecurityGroupIngressError
| ec2.RevokeSecurityGroupIngressError;
const authorizationResult = <A, E extends BindingError, R>(
operation: Effect.Effect<A, E, R>,
) =>
operation.pipe(
Effect.retry({
while: (error) => error._tag === "UnauthorizedOperation",
schedule: Schedule.spaced("3 seconds"),
times: 8,
}),
// Return a typed timeout before Lambda's 30-second invocation limit.
Effect.timeout("25 seconds"),
Effect.result,
);
/** The dynamic-allowlisting rule the authorize/revoke routes add and remove. */
export const testIngressRule = {
IpProtocol: "tcp",
FromPort: 443,
ToPort: 443,
CidrIp: "203.0.113.0/24",
} as const;
export class Ec2BindingsFunction extends AWS.Lambda.Function<AWS.Lambda.Function>()(
"Ec2BindingsFunction",
) {}
/**
* Shared fleet for the bindings E2E: a dedicated VPC/subnet (the testing
* account has no default VPC), a t3.micro instance, a 1 GiB volume, and an
* empty security group. The AMI is an `Output` resolved at deploy time only,
* so this composition is safe to re-execute inside the deployed Lambda
* without runtime guards.
*/
export class BindingsFleet extends Context.Service<
BindingsFleet,
{ instance: Instance; group: SecurityGroup; volume: Volume }
>()("Ec2BindingsFleet") {}
export const BindingsFleetLive = Layer.effect(
BindingsFleet,
Effect.gen(function* () {
const imageId = amazonLinux2023();
const vpc = yield* Vpc("BindingsVpc", { cidrBlock: "10.61.0.0/16" });
const subnet = yield* Subnet("BindingsSubnet", {
vpcId: vpc.vpcId,
cidrBlock: "10.61.1.0/24",
});
const group = yield* SecurityGroup("BindingsSecurityGroup", {
vpcId: vpc.vpcId,
description: "EC2 bindings test group",
});
const volume = yield* Volume("BindingsVolume", {
availabilityZone: subnet.availabilityZone,
size: 1,
});
const instance = yield* Instance("BindingsInstance", {
imageId,
instanceType: "t3.micro",
subnetId: subnet.subnetId,
});
return { instance, group, volume };
}),
);
export default Ec2BindingsFunction.make(
{
main: import.meta.url,
functionUrl: true,
// The AWS defaults (128 MB / 3s) are too tight for the EC2 client:
// cold routes time out at 3s (502 from the function URL) with memory
// pegged against the 128 MB floor.
timeout: Duration.seconds(30),
memorySize: 512,
},
Effect.gen(function* () {
const { instance, group, volume } = yield* BindingsFleet;
const describeInstance = yield* DescribeInstance(instance);
const describeStatus = yield* DescribeInstanceStatus(instance);
const startInstance = yield* StartInstance(instance);
const stopInstance = yield* StopInstance(instance);
const rebootInstance = yield* RebootInstance(instance);
const getConsoleOutput = yield* GetConsoleOutput(instance);
const getPasswordData = yield* GetPasswordData(instance);
const createSnapshot = yield* CreateSnapshot(volume);
const authorizeIngress = yield* AuthorizeSecurityGroupIngress(group);
const revokeIngress = yield* RevokeSecurityGroupIngress(group);
// Subscribe to instance stop/terminate transitions (creates the
// EventBridge rule at deploy time).
yield* consumeInstanceStateEvents(
instance,
{ states: ["stopped", "terminated"] },
(events) =>
Stream.runForEach(events, (event) =>
Effect.log(
`${event.detail["instance-id"]} is now ${event.detail.state}`,
),
),
);
return {
fetch: Effect.gen(function* () {
const request = yield* HttpServerRequest;
const pathname = new URL(request.originalUrl).pathname;
if (request.method === "GET" && pathname === "/health") {
return yield* HttpServerResponse.json({ ok: true });
}
if (request.method === "GET" && pathname === "/describe") {
const result = yield* describeInstance().pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
state:
result._tag === "Success"
? result.success?.State?.Name
: undefined,
});
}
if (request.method === "GET" && pathname === "/status") {
const result = yield* describeStatus({
IncludeAllInstances: true,
}).pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
count:
result._tag === "Success"
? (result.success.InstanceStatuses ?? []).length
: undefined,
});
}
if (request.method === "GET" && pathname === "/console") {
const result = yield* getConsoleOutput().pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
});
}
// The instance runs Linux without a key pair, so the password data is
// empty — the probe proves the IAM grant and that a present
// `PasswordData` surfaces as Redacted (never a raw string).
if (request.method === "GET" && pathname === "/password") {
const result = yield* getPasswordData().pipe(authorizationResult);
const passwordData =
result._tag === "Success" ? result.success.PasswordData : undefined;
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
hasField: passwordData !== undefined,
redacted:
passwordData !== undefined
? Redacted.isRedacted(passwordData)
: null,
});
}
// Starting an already-running instance succeeds without effect.
if (request.method === "POST" && pathname === "/start") {
const result = yield* startInstance().pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
});
}
if (request.method === "POST" && pathname === "/reboot") {
const result = yield* rebootInstance().pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
});
}
// Runs LAST in the test file — the instance stays stopped until the
// stack is destroyed.
if (request.method === "POST" && pathname === "/stop") {
const result = yield* stopInstance().pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
state:
result._tag === "Success"
? result.success.StoppingInstances?.[0]?.CurrentState?.Name
: undefined,
});
}
if (request.method === "POST" && pathname === "/authorize") {
const result = yield* authorizeIngress({
IpPermissions: [
{
IpProtocol: testIngressRule.IpProtocol,
FromPort: testIngressRule.FromPort,
ToPort: testIngressRule.ToPort,
IpRanges: [{ CidrIp: testIngressRule.CidrIp }],
},
],
}).pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
});
}
if (request.method === "POST" && pathname === "/revoke") {
const result = yield* revokeIngress({
IpPermissions: [
{
IpProtocol: testIngressRule.IpProtocol,
FromPort: testIngressRule.FromPort,
ToPort: testIngressRule.ToPort,
IpRanges: [{ CidrIp: testIngressRule.CidrIp }],
},
],
}).pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
});
}
// Creates a real (1 GiB) snapshot; the test deletes it out-of-band.
if (request.method === "POST" && pathname === "/snapshot") {
const result = yield* createSnapshot({
Description: "alchemy EC2 bindings test snapshot",
}).pipe(authorizationResult);
return yield* HttpServerResponse.json({
ok: result._tag === "Success",
tag: result._tag === "Failure" ? result.failure._tag : "Success",
snapshotId:
result._tag === "Success" ? result.success.SnapshotId : undefined,
});
}
return yield* HttpServerResponse.json(
{ error: "Not found", method: request.method, pathname },
{ status: 404 },
);
}).pipe(Effect.orDie),
};
}).pipe(
Effect.provide(
Layer.mergeAll(
AWS.Lambda.EventSource,
DescribeInstanceHttp,
DescribeInstanceStatusHttp,
StartInstanceHttp,
StopInstanceHttp,
RebootInstanceHttp,
GetConsoleOutputHttp,
GetPasswordDataHttp,
CreateSnapshotHttp,
AuthorizeSecurityGroupIngressHttp,
RevokeSecurityGroupIngressHttp,
BindingsFleetLive,
),
),
),
);