Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/lambda-external/external/lambda.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const handler = async (event) => {
console.log("event", event);
return {
statusCode: 200,
body: JSON.stringify({ message: "Hello, world!" }),
};
};
Binary file added examples/lambda-external/external/lambda.zip
Binary file not shown.
8 changes: 8 additions & 0 deletions examples/lambda-external/infra/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { api, router } from "@notation/aws/api-gateway";
import { externalJsLambda, externalZipLambda } from "./lambda";

const helloApi = api({ name: "hello-api" });
const helloRouter = router(helloApi);

helloRouter.get("/hello1", externalJsLambda);
helloRouter.get("/hello2", externalZipLambda);
2 changes: 2 additions & 0 deletions examples/lambda-external/infra/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import "./api";
import "./schedule";
19 changes: 19 additions & 0 deletions examples/lambda-external/infra/lambda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { lambda } from "@notation/aws/lambda";

export const externalJsLambda = lambda({
id: "external-js",
handler: "handler",
code: {
type: "file",
path: "external/lambda.mjs",
},
});

export const externalZipLambda = lambda({
id: "external-zip",
handler: "handler",
code: {
type: "zip",
path: "external/lambda.zip",
},
});
8 changes: 8 additions & 0 deletions examples/lambda-external/infra/schedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { schedule, rate } from "@notation/aws/event-bridge";
import { externalJsLambda } from "./lambda";

export const myschedule = schedule({
name: "my-schedule",
schedule: rate(1, "minute"),
handler: externalJsLambda,
});
16 changes: 16 additions & 0 deletions examples/lambda-external/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"private": true,
"name": "lambda",
"scripts": {
"compile": "notation compile infra/index.ts",
"dashboard": "notation dashboard",
"deploy": "notation deploy infra/index.ts",
"destroy": "notation destroy infra/index.ts",
"viz": "notation viz infra/index.ts",
"watch": "notation watch infra/index.ts"
},
"dependencies": {
"@notation/aws": "workspace:*",
"@notation/cli": "workspace:*"
}
}
32 changes: 32 additions & 0 deletions examples/lambda-external/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Default",
"compilerOptions": {
"lib": ["esnext"],
"module": "esnext",
"target": "esnext",

"moduleResolution": "bundler",
"noEmit": true,

"composite": false,
"declaration": true,
"declarationMap": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"strict": true,
"strictNullChecks": true,

"paths": {
"runtime/*": ["./runtime/*"],
"infra/*": ["./infra/*"]
}
},
"exclude": ["node_modules"]
}
31 changes: 12 additions & 19 deletions packages/aws.iac/src/resources/lambda/lambda.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export type LambdaFunctionSchema = AwsSchema<{

export type LambdaDependencies = {
role: LambdaIamRoleInstance;
zipFile: fs.ZipFileInstance;
zipFile: fs.ZipFileInstance | fs.FileInstance;
};

const lambdaFunction = resource<LambdaFunctionSchema>({
Expand Down Expand Up @@ -51,11 +51,6 @@ const lambdaFunctionSchema = lambdaFunction.defineSchema({
presence: "required",
hidden: true,
},
CodeZipPath: {
valueType: z.string(),
propertyType: "param",
presence: "required",
},
CodeSha256: {
valueType: z.string(),
propertyType: "param",
Expand Down Expand Up @@ -290,20 +285,20 @@ const lambdaFunctionSchema = lambdaFunction.defineSchema({
export const LambdaFunction = lambdaFunctionSchema
.defineOperations({
create: async (params) => {
const zip = await fs.getZip(params.CodeZipPath);
const command = new sdk.CreateFunctionCommand({
...params,
Code: { ZipFile: zip },
Code: { ZipFile: params.Code.ZipFile },
});

await lambdaClient.send(command);

if (params.ReservedConcurrentExecutions) {
const concurrencyCommand = new sdk.PutFunctionConcurrencyCommand({
FunctionName: params.FunctionName,
ReservedConcurrentExecutions: params.ReservedConcurrentExecutions,
});
await lambdaClient.send(concurrencyCommand);
}
// if (params.ReservedConcurrentExecutions) {
// const concurrencyCommand = new sdk.PutFunctionConcurrencyCommand({
// FunctionName: params.FunctionName,
// ReservedConcurrentExecutions: params.ReservedConcurrentExecutions,
// });
// await lambdaClient.send(concurrencyCommand);
// }
},

read: async (key) => {
Expand Down Expand Up @@ -339,10 +334,9 @@ export const LambdaFunction = lambdaFunctionSchema
}

if (CodeSha256) {
const zip = await fs.getZip(params.CodeZipPath);
const codeCommand = new sdk.UpdateFunctionCodeCommand({
...key,
ZipFile: zip,
ZipFile: params.Code.ZipFile,
});
await lambdaClient.send(codeCommand);
}
Expand Down Expand Up @@ -381,9 +375,8 @@ export const LambdaFunction = lambdaFunctionSchema
.requireDependencies<LambdaDependencies>()
.setIntrinsicConfig(async ({ deps }) => ({
PackageType: "Zip",
Code: { ZipFile: undefined },
Code: { ZipFile: deps.zipFile.output.file },
CodeSha256: deps.zipFile.output.sourceSha256,
CodeZipPath: deps.zipFile.config.filePath,
Role: deps.role.output.Arn,
}));

Expand Down
53 changes: 28 additions & 25 deletions packages/aws/src/api-gateway/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import type {
JWTAuthorizedApiGatewayHandler,
} from "src/shared";
import * as aws from "@notation/aws.iac";
import { lambda } from "src/lambda";
import { api } from "./api";
import { AuthorizerConfig } from "./auth";
import { mapAuthConfig, mapAuthType } from "./utils";
Expand All @@ -13,29 +12,41 @@ export const route = (
method: string, // todo: http methods only
path: `/${string}`,
auth: AuthorizerConfig,
handler: ApiGatewayHandler | JWTAuthorizedApiGatewayHandler<any>,
handler:
| ApiGatewayHandler
| JWTAuthorizedApiGatewayHandler<any>
// todo: narrow to lambda group
| aws.AwsResourceGroup,
) => {
const apiResource = apiGroup.findResource(aws.apiGateway.Api)!;

// at compile time becomes infra module
const lambdaGroup = handler as any as ReturnType<typeof lambda>;

const routeGroup = new aws.AwsResourceGroup("API Gateway/Route", {
dependencies: { router: apiGroup.id, fn: lambdaGroup.id },
});

const routeId = `${apiResource.id}-${method}-${path}`;

let integration;
const lambdaGroup =
handler instanceof aws.AwsResourceGroup
? handler
: // at compile time, runtime module becomes infra resource group
(handler as any as aws.AwsResourceGroup);

const lambdaResource = lambdaGroup.findResource(aws.lambda.LambdaFunction)!;

let integration = lambdaGroup.findResource(aws.apiGateway.LambdaIntegration);

if (!integration) {
integration = lambdaGroup.add(
new aws.apiGateway.LambdaIntegration({
id: `${apiResource.id}-${lambdaResource.id}-integration`,
dependencies: {
api: apiResource,
lambda: lambdaResource,
},
}),
);
}

const permission = lambdaGroup.findResource(
aws.lambda.LambdaApiGatewayV2Permission,
);

integration = lambdaGroup.findResource(aws.apiGateway.LambdaIntegration);

if (!permission) {
lambdaGroup.add(
new aws.lambda.LambdaApiGatewayV2Permission({
Expand All @@ -48,6 +59,10 @@ export const route = (
);
}

const routeGroup = new aws.AwsResourceGroup("API Gateway/Route", {
dependencies: { router: apiGroup.id, fn: lambdaGroup.id },
});

if (auth.type != "NONE") {
const authConfig = mapAuthConfig(apiResource.id, method, path, auth);

Expand All @@ -62,18 +77,6 @@ export const route = (
routeGroup.add(authorizer);
}

if (!integration) {
integration = lambdaGroup.add(
new aws.apiGateway.LambdaIntegration({
id: `${apiResource.id}-${lambdaResource.id}-integration`,
dependencies: {
api: apiResource,
lambda: lambdaResource,
},
}),
);
}

const authorizerResource = routeGroup.findResource(aws.apiGateway.RouteAuth);

routeGroup.add(
Expand Down
4 changes: 3 additions & 1 deletion packages/aws/src/api-gateway/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ import type {
ApiGatewayHandler,
JWTAuthorizedApiGatewayHandler,
} from "src/shared";
import * as aws from "@notation/aws.iac";
import { route } from "./route";
import { api } from "./api";
import { AuthorizerConfig, JWTAuthorizerConfig, NO_AUTH } from "./auth";

export const router = (apiGroup: ReturnType<typeof api>) => {
const createRouteCallback =
(method: string) => (path: `/${string}`, handler: ApiGatewayHandler) => {
(method: string) =>
(path: `/${string}`, handler: ApiGatewayHandler | aws.AwsResourceGroup) => {
return route(apiGroup, method, path, NO_AUTH, handler);
};

Expand Down
17 changes: 11 additions & 6 deletions packages/aws/src/event-bridge/event-bridge-schedule.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import { ResourceGroup } from "@notation/core";
import { EventBridgeHandler } from "src/shared/lambda.handler";
import { Schedule } from "./schedule";
import { lambda } from "src/lambda";
import * as aws from "@notation/aws.iac";
import { toAwsScheduleExpression } from "./aws-conversions";

export const schedule = (config: {
name: string;
schedule: Schedule;
handler: EventBridgeHandler<"Scheduled Event", any>;
}): ResourceGroup => {
handler:
| EventBridgeHandler<"Scheduled Event", any>
// todo: narrow to lambda group
| aws.AwsResourceGroup;
}): aws.AwsResourceGroup => {
const eventBridgeScheduleGroup = new aws.AwsResourceGroup(
"aws/eventBridge/schedule",
config,
);

// at compile time becomes infra module
const lambdaGroup = config.handler as any as ReturnType<typeof lambda>;
const lambdaGroup =
config.handler instanceof aws.AwsResourceGroup
? config.handler
: // at compile time, runtime module becomes infra resource group
(config.handler as any as aws.AwsResourceGroup);

const lambdaResource = lambdaGroup.findResource(aws.lambda.LambdaFunction)!;

const eventBridgeRule = new aws.eventBridge.EventBridgeRule({
Expand Down
62 changes: 46 additions & 16 deletions packages/aws/src/lambda/lambda.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,50 @@
import * as aws from "@notation/aws.iac";
import * as std from "@notation/std.iac";
import crypto from "crypto";
import path from "path";

export const lambda = (config: { fileName: string; handler: string }) => {
type LambdaConfig = {
id?: string;
handler: string;
code: {
type: "file" | "zip";
path: string;
};
};

export const lambda = (config: LambdaConfig) => {
const functionGroup = new aws.AwsResourceGroup("Lambda", { config });
const filePathHash = crypto
.createHash("BLAKE2s256")
.update(config.fileName)
.digest("hex")
.slice(0, 8);

const lambdaId = `${config.handler}-${filePathHash}`;

const zipFile = functionGroup.add(
new std.fs.Zip({
id: `${lambdaId}-zip`,
config: { filePath: config.fileName },
}),
);
const filePath = config.code.path;

let lambdaId = config.id;

if (!lambdaId) {
const filePathHash = crypto
.createHash("BLAKE2s256")
.update(filePath)
.digest("hex")
.slice(0, 8);

lambdaId = `${config.handler}-${filePathHash}`;
}

let zipFile: std.fs.ZipFileInstance | std.fs.FileInstance;

if (config.code.type === "file") {
zipFile = functionGroup.add(
new std.fs.Zip({
id: `${lambdaId}-zip`,
config: { sourceFilePath: filePath },
}),
);
} else {
zipFile = functionGroup.add(
new std.fs.File({
id: `${lambdaId}-zip`,
config: { filePath },
}),
);
}

const role = functionGroup.add(
new aws.lambda.LambdaIamRole({
Expand All @@ -35,13 +62,16 @@ export const lambda = (config: { fileName: string; handler: string }) => {
}),
);

const fileName = path.parse(filePath).name;

const lambdaResource = functionGroup.add(
new aws.lambda.LambdaFunction({
id: lambdaId,
config: {
FunctionName: lambdaId,
Handler: `index.${config.handler}`,
Handler: `${fileName}.${config.handler}`,
Runtime: "nodejs18.x",
// todo: make this configurable and remove it as a default
ReservedConcurrentExecutions: 1,
},
dependencies: {
Expand Down
Loading