From de2a081368820f709ea0062133d42f5bee2a9afb Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 9 Oct 2024 10:07:23 -0400 Subject: [PATCH] Function: expose referenced role inside nodes --- .../components/aws/cognito-identity-pool.ts | 8 +- platform/src/components/aws/function.ts | 374 +++++++++--------- platform/src/components/aws/helpers/arn.ts | 2 +- 3 files changed, 192 insertions(+), 192 deletions(-) diff --git a/platform/src/components/aws/cognito-identity-pool.ts b/platform/src/components/aws/cognito-identity-pool.ts index 7a05abb50..fac4a5640 100644 --- a/platform/src/components/aws/cognito-identity-pool.ts +++ b/platform/src/components/aws/cognito-identity-pool.ts @@ -6,7 +6,7 @@ import { Link } from "../link"; import { physicalName } from "../naming"; import { cognito, getRegionOutput, iam } from "@pulumi/aws"; import { permission } from "./permission"; -import { parseIamRoleArn } from "./helpers/arn"; +import { parseRoleArn } from "./helpers/arn"; export interface CognitoIdentityPoolArgs { /** @@ -399,14 +399,12 @@ export class CognitoIdentityPool extends Component implements Link.Linkable { ); const authRole = iam.Role.get( `${name}AuthRole`, - attachment.roles.authenticated.apply( - (arn) => parseIamRoleArn(arn).roleName, - ), + attachment.roles.authenticated.apply((arn) => parseRoleArn(arn).roleName), ); const unauthRole = iam.Role.get( `${name}UnauthRole`, attachment.roles.unauthenticated.apply( - (arn) => parseIamRoleArn(arn).roleName, + (arn) => parseRoleArn(arn).roleName, ), ); return new CognitoIdentityPool(name, { diff --git a/platform/src/components/aws/function.ts b/platform/src/components/aws/function.ts index a0346347f..bb8dcc370 100644 --- a/platform/src/components/aws/function.ts +++ b/platform/src/components/aws/function.ts @@ -38,6 +38,7 @@ import { Vpc } from "./vpc.js"; import { buildPython, buildPythonContainer } from "../../runtime/python.js"; import { Image } from "@pulumi/docker-build"; import { rpc } from "../rpc/rpc.js"; +import { parseRoleArn } from "./helpers/arn.js"; /** * Helper type to define function ARN type @@ -493,53 +494,53 @@ export interface FunctionArgs { logging?: Input< | false | { - /** - * The duration the function logs are kept in CloudWatch. - * - * Not application when an existing log group is provided. - * - * @default `forever` - * @example - * ```js - * { - * logging: { - * retention: "1 week" - * } - * } - * ``` - */ - retention?: Input; - /** - * Assigns the given CloudWatch log group name to the function. This allows you to pass in a previously created log group. - * - * By default, the function creates a new log group when it's created. - * - * @default Creates a log group - * @example - * ```js - * { - * logging: { - * logGroup: "/existing/log-group" - * } - * } - * ``` - */ - logGroup?: Input; - /** - * The [log format](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-advanced.html) - * of the Lambda function. - * @default `"text"` - * @example - * ```js - * { - * logging: { - * format: "json" - * } - * } - * ``` - */ - format?: Input<"text" | "json">; - } + /** + * The duration the function logs are kept in CloudWatch. + * + * Not application when an existing log group is provided. + * + * @default `forever` + * @example + * ```js + * { + * logging: { + * retention: "1 week" + * } + * } + * ``` + */ + retention?: Input; + /** + * Assigns the given CloudWatch log group name to the function. This allows you to pass in a previously created log group. + * + * By default, the function creates a new log group when it's created. + * + * @default Creates a log group + * @example + * ```js + * { + * logging: { + * logGroup: "/existing/log-group" + * } + * } + * ``` + */ + logGroup?: Input; + /** + * The [log format](https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs-advanced.html) + * of the Lambda function. + * @default `"text"` + * @example + * ```js + * { + * logging: { + * format: "json" + * } + * } + * ``` + */ + format?: Input<"text" | "json">; + } >; /** * The [architecture](https://docs.aws.amazon.com/lambda/latest/dg/foundation-arch.html) @@ -601,45 +602,45 @@ export interface FunctionArgs { url?: Input< | boolean | { - /** - * The authorization used for the function URL. Supports [IAM authorization](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html). - * @default `"none"` - * @example - * ```js - * { - * url: { - * authorization: "iam" - * } - * } - * ``` - */ - authorization?: Input<"none" | "iam">; - /** - * Customize the CORS (Cross-origin resource sharing) settings for the function URL. - * @default `true` - * @example - * Disable CORS. - * ```js - * { - * url: { - * cors: false - * } - * } - * ``` - * Only enable the `GET` and `POST` methods for `https://example.com`. - * ```js - * { - * url: { - * cors: { - * allowMethods: ["GET", "POST"], - * allowOrigins: ["https://example.com"] - * } - * } - * } - * ``` - */ - cors?: Input>; - } + /** + * The authorization used for the function URL. Supports [IAM authorization](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html). + * @default `"none"` + * @example + * ```js + * { + * url: { + * authorization: "iam" + * } + * } + * ``` + */ + authorization?: Input<"none" | "iam">; + /** + * Customize the CORS (Cross-origin resource sharing) settings for the function URL. + * @default `true` + * @example + * Disable CORS. + * ```js + * { + * url: { + * cors: false + * } + * } + * ``` + * Only enable the `GET` and `POST` methods for `https://example.com`. + * ```js + * { + * url: { + * cors: { + * allowMethods: ["GET", "POST"], + * allowOrigins: ["https://example.com"] + * } + * } + * } + * ``` + */ + cors?: Input>; + } >; /** * Configure how your function is bundled. @@ -1132,7 +1133,7 @@ export interface FunctionArgs { */ export class Function extends Component implements Link.Linkable { private function: Output; - private role?: iam.Role; + private role: iam.Role; private logGroup: Output; private fnUrl: Output; private missingSourcemap?: boolean; @@ -1334,10 +1335,10 @@ export class Function extends Component implements Link.Linkable { : url.cors === true || url.cors === undefined ? defaultCors : { - ...defaultCors, - ...url.cors, - maxAge: url.cors.maxAge && toSeconds(url.cors.maxAge), - }; + ...defaultCors, + ...url.cors, + maxAge: url.cors.maxAge && toSeconds(url.cors.maxAge), + }; return { authorization, cors }; }); @@ -1424,7 +1425,7 @@ export class Function extends Component implements Link.Linkable { if (result.type === "error") { throw new VisibleError( `Failed to build function "${args.handler}": ` + - result.errors.join("\n").trim(), + result.errors.join("\n").trim(), ); } return result; @@ -1436,7 +1437,7 @@ export class Function extends Component implements Link.Linkable { if (result.type === "error") { throw new VisibleError( `Failed to build function "${args.handler}": ` + - result.errors.join("\n").trim(), + result.errors.join("\n").trim(), ); } return result; @@ -1483,7 +1484,7 @@ export class Function extends Component implements Link.Linkable { if (result.type === "error") { throw new VisibleError( `Failed to build function "${args.handler}": ` + - result.errors.join("\n").trim(), + result.errors.join("\n").trim(), ); } return result; @@ -1528,12 +1529,12 @@ export class Function extends Component implements Link.Linkable { const linkInjection = hasLinkInjections ? linkData - .map((item) => [ - `process.env["SST_RESOURCE_${item.name}"] = ${JSON.stringify( - JSON.stringify(item.properties), - )};\n`, - ]) - .join("") + .map((item) => [ + `process.env["SST_RESOURCE_${item.name}"] = ${JSON.stringify( + JSON.stringify(item.properties), + )};\n`, + ]) + .join("") : ""; const parsed = path.posix.parse(handler); @@ -1563,21 +1564,21 @@ export class Function extends Component implements Link.Linkable { name: path.posix.join(handlerDir, `${newHandlerFileName}.mjs`), content: streaming ? [ - linkInjection, - `export const ${newHandlerFunction} = awslambda.streamifyResponse(async (event, responseStream, context) => {`, - ...injections, - ` const { ${oldHandlerFunction}: rawHandler} = await import("./${oldHandlerFileName}${newHandlerFileExt}");`, - ` return rawHandler(event, responseStream, context);`, - `});`, - ].join("\n") + linkInjection, + `export const ${newHandlerFunction} = awslambda.streamifyResponse(async (event, responseStream, context) => {`, + ...injections, + ` const { ${oldHandlerFunction}: rawHandler} = await import("./${oldHandlerFileName}${newHandlerFileExt}");`, + ` return rawHandler(event, responseStream, context);`, + `});`, + ].join("\n") : [ - linkInjection, - `export const ${newHandlerFunction} = async (event, context) => {`, - ...injections, - ` const { ${oldHandlerFunction}: rawHandler} = await import("./${oldHandlerFileName}${newHandlerFileExt}");`, - ` return rawHandler(event, context);`, - `};`, - ].join("\n"), + linkInjection, + `export const ${newHandlerFunction} = async (event, context) => {`, + ...injections, + ` const { ${oldHandlerFunction}: rawHandler} = await import("./${oldHandlerFileName}${newHandlerFileExt}");`, + ` return rawHandler(event, context);`, + `};`, + ].join("\n"), }, }; }, @@ -1589,7 +1590,13 @@ export class Function extends Component implements Link.Linkable { } function createRole() { - if (args.role) return; + if (args.role) + return iam.Role.get( + `${name}Role`, + output(args.role).apply(parseRoleArn).roleName, + {}, + { parent }, + ); const policy = all([args.permissions || [], linkPermissions, dev]).apply( ([argsPermissions, linkPermissions, dev]) => @@ -1602,18 +1609,18 @@ export class Function extends Component implements Link.Linkable { })), ...(dev ? [ - { - actions: ["iot:*"], - resources: ["*"], - }, - { - actions: ["s3:*"], - resources: [ - interpolate`arn:aws:s3:::${bootstrapData.asset}`, - interpolate`arn:aws:s3:::${bootstrapData.asset}/*`, - ], - }, - ] + { + actions: ["iot:*"], + resources: ["*"], + }, + { + actions: ["s3:*"], + resources: [ + interpolate`arn:aws:s3:::${bootstrapData.asset}`, + interpolate`arn:aws:s3:::${bootstrapData.asset}/*`, + ], + }, + ] : []), ], }), @@ -1626,28 +1633,29 @@ export class Function extends Component implements Link.Linkable { { assumeRolePolicy: !$dev ? iam.assumeRolePolicyForPrincipal({ - Service: "lambda.amazonaws.com", - }) + Service: "lambda.amazonaws.com", + }) : iam.getPolicyDocumentOutput({ - statements: [ - { - actions: ["sts:AssumeRole"], - principals: [ - { - type: "Service", - identifiers: ["lambda.amazonaws.com"], - }, - { - type: "AWS", - identifiers: [ - interpolate`arn:aws:iam::${getCallerIdentityOutput().accountId + statements: [ + { + actions: ["sts:AssumeRole"], + principals: [ + { + type: "Service", + identifiers: ["lambda.amazonaws.com"], + }, + { + type: "AWS", + identifiers: [ + interpolate`arn:aws:iam::${ + getCallerIdentityOutput().accountId }:root`, - ], - }, - ], - }, - ], - }).json, + ], + }, + ], + }, + ], + }).json, // if there are no statements, do not add an inline policy. // adding an inline policy with no statements will cause an error. inlinePolicies: policy.apply(({ statements }) => @@ -1656,13 +1664,13 @@ export class Function extends Component implements Link.Linkable { managedPolicyArns: logging.apply((logging) => [ ...(logging ? [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", - ] + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ] : []), ...(vpc ? [ - "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", - ] + "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", + ] : []), ]), }, @@ -1790,9 +1798,9 @@ export class Function extends Component implements Link.Linkable { entry.isDir ? archive.directory(entry.from, entry.to, { date: new Date(0) }) : archive.file(entry.from, { - name: entry.to, - date: new Date(0), - }); + name: entry.to, + date: new Date(0), + }); }); await archive.finalize(); }); @@ -1827,8 +1835,9 @@ export class Function extends Component implements Link.Linkable { args.transform?.logGroup, `${name}LogGroup`, { - name: interpolate`/aws/lambda/${args.name ?? physicalName(64, `${name}Function`) - }`, + name: interpolate`/aws/lambda/${ + args.name ?? physicalName(64, `${name}Function`) + }`, retentionInDays: RETENTION[logging.retention], }, { parent }, @@ -1887,21 +1896,21 @@ export class Function extends Component implements Link.Linkable { reservedConcurrentExecutions: concurrency?.reserved, ...(isContainer ? { - packageType: "Image", - imageUri: imageAsset!.ref.apply( - (ref) => ref?.replace(":latest", ""), - ), - imageConfig: { - commands: [handler], - }, - } + packageType: "Image", + imageUri: imageAsset!.ref.apply( + (ref) => ref?.replace(":latest", ""), + ), + imageConfig: { + commands: [handler], + }, + } : { - packageType: "Zip", - s3Bucket: zipAsset!.bucket, - s3Key: zipAsset!.key, - handler: unsecret(handler), - runtime, - }), + packageType: "Zip", + s3Bucket: zipAsset!.bucket, + s3Key: zipAsset!.key, + handler: unsecret(handler), + runtime, + }), }, { parent }, ); @@ -1911,14 +1920,14 @@ export class Function extends Component implements Link.Linkable { ...transformed[1], ...(dev ? { - description: transformed[1].description - ? output(transformed[1].description).apply( - (v) => `${v.substring(0, 240)} (live)`, - ) - : "live", - runtime: "provided.al2023", - architectures: ["x86_64"], - } + description: transformed[1].description + ? output(transformed[1].description).apply( + (v) => `${v.substring(0, 240)} (live)`, + ) + : "live", + runtime: "provided.al2023", + architectures: ["x86_64"], + } : {}), }, transformed[2], @@ -1975,18 +1984,11 @@ export class Function extends Component implements Link.Linkable { * The underlying [resources](/docs/components/#nodes) this component creates. */ public get nodes() { - const self = this; return { /** * The IAM Role the function will use. */ - get role() { - if (!self.role) - throw new VisibleError( - `"nodes.role" is not available when a pre-existing role is used.`, - ); - return self.role; - }, + role: this.role, /** * The AWS Lambda function. */ diff --git a/platform/src/components/aws/helpers/arn.ts b/platform/src/components/aws/helpers/arn.ts index 18aee6d39..58ba44925 100644 --- a/platform/src/components/aws/helpers/arn.ts +++ b/platform/src/components/aws/helpers/arn.ts @@ -84,7 +84,7 @@ export function parseEventBusArn(arn: string) { return { busName }; } -export function parseIamRoleArn(arn: string) { +export function parseRoleArn(arn: string) { // arn:aws:iam::123456789012:role/MyRole const roleName = arn.split("/")[1]; if (!arn.startsWith("arn:") || !roleName)