diff --git a/docs/docs/04-standard-library/01-cloud/function.md b/docs/docs/04-standard-library/01-cloud/function.md index 98f2315a6b2..968671f5d38 100644 --- a/docs/docs/04-standard-library/01-cloud/function.md +++ b/docs/docs/04-standard-library/01-cloud/function.md @@ -45,15 +45,35 @@ new cloud.Function(inflight () => { ### Simulator (`sim`) -The sim implementation of `cloud.Function` uses JavaScript's function +The sim implementation of `cloud.Function` runs the inflight code as a JavaScript function. ### AWS (`tf-aws` and `awscdk`) -The AWS implementation of `cloud.Function` uses [Amazon Lambda](https://aws.amazon.com/lambda/). +The AWS implementation of `cloud.Function` uses [AWS Lambda](https://aws.amazon.com/lambda/). + +To add extra IAM permissions to the function, you can use the `aws.Function` class as shown below. + +```ts playground +bring aws; +bring cloud; + +let f = new cloud.Function(inflight () => { + log("Hello world!"); +}); +if let lambdaFn = aws.Function.from(f) { + lambdaFn.addPolicyStatements( + aws.PolicyStatement { + actions: ["ses:sendEmail"], + effect: aws.Effect.ALLOW, + resources: ["*"], + }, + ); +} +``` ### Azure (`tf-azure`) -The Azure implementation of `cloud.Function` uses [Azure Function](https://azure.microsoft.com/en-us/products/function). +The Azure implementation of `cloud.Function` uses [Azure Functions](https://azure.microsoft.com/en-us/products/functions). 🚧 `invoke` API is not supported yet (tracking issue: [#1371](https://github.com/winglang/wing/issues/1371)) diff --git a/docs/docs/04-standard-library/05-aws/api-reference.md b/docs/docs/04-standard-library/05-aws/api-reference.md index ea8e592f8f9..77132e95adb 100644 --- a/docs/docs/04-standard-library/05-aws/api-reference.md +++ b/docs/docs/04-standard-library/05-aws/api-reference.md @@ -160,17 +160,14 @@ Add an environment variable to the function. ##### `addPolicyStatements` ```wing -addPolicyStatements(policies: MutArray): void +addPolicyStatements(...policies: Array): void ``` Add policy statements to the function's IAM role. -TODO: update this to accept a variadic parameter (...policies) -https://github.com/winglang/wing/issues/397 - ###### `policies`Required -- *Type:* MutArray<PolicyStatement> +- *Type:* PolicyStatement --- diff --git a/examples/tests/valid/dynamo.main.w b/examples/tests/valid/dynamo.main.w index 62e4b2d8e4b..97f89cb4479 100644 --- a/examples/tests/valid/dynamo.main.w +++ b/examples/tests/valid/dynamo.main.w @@ -45,11 +45,11 @@ class DynamoTable { pub bind(host: std.IInflightHost, ops: Array) { if let host = aws.Function.from(host) { if ops.contains("putItem") { - host.addPolicyStatements([aws.PolicyStatement { + host.addPolicyStatements(aws.PolicyStatement { actions: ["dynamodb:PutItem"], resources: [this.table.arn], effect: aws.Effect.ALLOW, - }]); + }); } } } diff --git a/examples/tests/valid/dynamo_awscdk.main.w b/examples/tests/valid/dynamo_awscdk.main.w index 5be0801c0e9..8a77d436c6c 100644 --- a/examples/tests/valid/dynamo_awscdk.main.w +++ b/examples/tests/valid/dynamo_awscdk.main.w @@ -43,19 +43,19 @@ class DynamoTable { pub bind(host: std.IInflightHost, ops: Array) { if let host = aws.Function.from(host) { if ops.contains("putItem") { - host.addPolicyStatements([aws.PolicyStatement { + host.addPolicyStatements(aws.PolicyStatement { actions: ["dynamodb:PutItem"], resources: [this.table.tableArn], effect: aws.Effect.ALLOW, - }]); + }); } if ops.contains("getItem") { - host.addPolicyStatements([aws.PolicyStatement { + host.addPolicyStatements(aws.PolicyStatement { actions: ["dynamodb:GetItem"], resources: [this.table.tableArn], effect: aws.Effect.ALLOW, - }]); + }); } } } diff --git a/libs/wingsdk/src/cloud/function.md b/libs/wingsdk/src/cloud/function.md index 6313711cc4f..813412f6b75 100644 --- a/libs/wingsdk/src/cloud/function.md +++ b/libs/wingsdk/src/cloud/function.md @@ -45,15 +45,35 @@ new cloud.Function(inflight () => { ### Simulator (`sim`) -The sim implementation of `cloud.Function` uses JavaScript's function +The sim implementation of `cloud.Function` runs the inflight code as a JavaScript function. ### AWS (`tf-aws` and `awscdk`) -The AWS implementation of `cloud.Function` uses [Amazon Lambda](https://aws.amazon.com/lambda/). +The AWS implementation of `cloud.Function` uses [AWS Lambda](https://aws.amazon.com/lambda/). + +To add extra IAM permissions to the function, you can use the `aws.Function` class as shown below. + +```ts playground +bring aws; +bring cloud; + +let f = new cloud.Function(inflight () => { + log("Hello world!"); +}); +if let lambdaFn = aws.Function.from(f) { + lambdaFn.addPolicyStatements( + aws.PolicyStatement { + actions: ["ses:sendEmail"], + effect: aws.Effect.ALLOW, + resources: ["*"], + }, + ); +} +``` ### Azure (`tf-azure`) -The Azure implementation of `cloud.Function` uses [Azure Function](https://azure.microsoft.com/en-us/products/function). +The Azure implementation of `cloud.Function` uses [Azure Functions](https://azure.microsoft.com/en-us/products/functions). 🚧 `invoke` API is not supported yet (tracking issue: [#1371](https://github.com/winglang/wing/issues/1371)) diff --git a/libs/wingsdk/src/shared-aws/function.ts b/libs/wingsdk/src/shared-aws/function.ts index 773df3ebad8..20bc0759cc6 100644 --- a/libs/wingsdk/src/shared-aws/function.ts +++ b/libs/wingsdk/src/shared-aws/function.ts @@ -12,11 +12,8 @@ export interface IAwsFunction { /** * Add policy statements to the function's IAM role. - * - * TODO: update this to accept a variadic parameter (...policies) - * https://github.com/winglang/wing/issues/397 */ - addPolicyStatements(policies: PolicyStatement[]): void; + addPolicyStatements(...policies: PolicyStatement[]): void; } /** diff --git a/libs/wingsdk/src/target-awscdk/bucket.ts b/libs/wingsdk/src/target-awscdk/bucket.ts index f27430fba47..77598b91d92 100644 --- a/libs/wingsdk/src/target-awscdk/bucket.ts +++ b/libs/wingsdk/src/target-awscdk/bucket.ts @@ -186,7 +186,7 @@ export class Bucket extends cloud.Bucket { } host.addPolicyStatements( - calculateBucketPermissions(this.bucket.bucketArn, ops) + ...calculateBucketPermissions(this.bucket.bucketArn, ops) ); // The bucket name needs to be passed through an environment variable since diff --git a/libs/wingsdk/src/target-awscdk/counter.ts b/libs/wingsdk/src/target-awscdk/counter.ts index d75a2533a44..88e57a77b4f 100644 --- a/libs/wingsdk/src/target-awscdk/counter.ts +++ b/libs/wingsdk/src/target-awscdk/counter.ts @@ -32,7 +32,7 @@ export class Counter extends cloud.Counter { } host.addPolicyStatements( - calculateCounterPermissions(this.table.tableArn, ops) + ...calculateCounterPermissions(this.table.tableArn, ops) ); host.addEnvironment(this.envName(), this.table.tableName); diff --git a/libs/wingsdk/src/target-awscdk/function.ts b/libs/wingsdk/src/target-awscdk/function.ts index 1b0d2358295..16212930faf 100644 --- a/libs/wingsdk/src/target-awscdk/function.ts +++ b/libs/wingsdk/src/target-awscdk/function.ts @@ -57,12 +57,10 @@ export class Function extends cloud.Function implements IAwsFunction { } if (ops.includes(cloud.FunctionInflightMethods.INVOKE)) { - host.addPolicyStatements([ - { - actions: ["lambda:InvokeFunction"], - resources: [`${this.function.functionArn}`], - }, - ]); + host.addPolicyStatements({ + actions: ["lambda:InvokeFunction"], + resources: [`${this.function.functionArn}`], + }); } // The function name needs to be passed through an environment variable since @@ -97,7 +95,7 @@ export class Function extends cloud.Function implements IAwsFunction { /** * Add a policy statement to the Lambda role. */ - public addPolicyStatements(statements: PolicyStatement[]) { + public addPolicyStatements(...statements: PolicyStatement[]) { for (const statement of statements) { this.function.addToRolePolicy(new CdkPolicyStatement(statement)); } diff --git a/libs/wingsdk/src/target-awscdk/queue.ts b/libs/wingsdk/src/target-awscdk/queue.ts index fd4636cb5fc..e9c6e37b137 100644 --- a/libs/wingsdk/src/target-awscdk/queue.ts +++ b/libs/wingsdk/src/target-awscdk/queue.ts @@ -81,7 +81,7 @@ export class Queue extends cloud.Queue { const env = this.envName(); host.addPolicyStatements( - calculateQueuePermissions(this.queue.queueArn, ops) + ...calculateQueuePermissions(this.queue.queueArn, ops) ); // The queue url needs to be passed through an environment variable since diff --git a/libs/wingsdk/src/target-awscdk/secret.ts b/libs/wingsdk/src/target-awscdk/secret.ts index 16471526138..927b3579375 100644 --- a/libs/wingsdk/src/target-awscdk/secret.ts +++ b/libs/wingsdk/src/target-awscdk/secret.ts @@ -47,7 +47,7 @@ export class Secret extends cloud.Secret { } host.addPolicyStatements( - calculateSecretPermissions(this.arnForPolicies, ops) + ...calculateSecretPermissions(this.arnForPolicies, ops) ); host.addEnvironment(this.envName(), this.secret.secretArn); diff --git a/libs/wingsdk/src/target-awscdk/topic.ts b/libs/wingsdk/src/target-awscdk/topic.ts index 435dad4a5b6..b56b5d61d1c 100644 --- a/libs/wingsdk/src/target-awscdk/topic.ts +++ b/libs/wingsdk/src/target-awscdk/topic.ts @@ -75,7 +75,7 @@ export class Topic extends cloud.Topic { } host.addPolicyStatements( - calculateTopicPermissions(this.topic.topicArn, ops) + ...calculateTopicPermissions(this.topic.topicArn, ops) ); host.addEnvironment(this.envName(), this.topic.topicArn); diff --git a/libs/wingsdk/src/target-tf-aws/bucket.ts b/libs/wingsdk/src/target-tf-aws/bucket.ts index e08c6eb47ad..fb966f619ae 100644 --- a/libs/wingsdk/src/target-tf-aws/bucket.ts +++ b/libs/wingsdk/src/target-tf-aws/bucket.ts @@ -113,7 +113,9 @@ export class Bucket extends cloud.Bucket { throw new Error("buckets can only be bound by tfaws.Function for now"); } - host.addPolicyStatements(calculateBucketPermissions(this.bucket.arn, ops)); + host.addPolicyStatements( + ...calculateBucketPermissions(this.bucket.arn, ops) + ); // The bucket name needs to be passed through an environment variable since // it may not be resolved until deployment time. diff --git a/libs/wingsdk/src/target-tf-aws/counter.ts b/libs/wingsdk/src/target-tf-aws/counter.ts index 8e88f15bdb7..6ed9c21e36d 100644 --- a/libs/wingsdk/src/target-tf-aws/counter.ts +++ b/libs/wingsdk/src/target-tf-aws/counter.ts @@ -42,7 +42,9 @@ export class Counter extends cloud.Counter { throw new Error("counters can only be bound by tfaws.Function for now"); } - host.addPolicyStatements(calculateCounterPermissions(this.table.arn, ops)); + host.addPolicyStatements( + ...calculateCounterPermissions(this.table.arn, ops) + ); host.addEnvironment(this.envName(), this.table.name); diff --git a/libs/wingsdk/src/target-tf-aws/function.ts b/libs/wingsdk/src/target-tf-aws/function.ts index f097174c3e3..94319a1bdad 100644 --- a/libs/wingsdk/src/target-tf-aws/function.ts +++ b/libs/wingsdk/src/target-tf-aws/function.ts @@ -229,12 +229,10 @@ export class Function extends cloud.Function implements IAwsFunction { } if (ops.includes(cloud.FunctionInflightMethods.INVOKE)) { - host.addPolicyStatements([ - { - actions: ["lambda:InvokeFunction"], - resources: [`${this.function.arn}`], - }, - ]); + host.addPolicyStatements({ + actions: ["lambda:InvokeFunction"], + resources: [`${this.function.arn}`], + }); } // The function name needs to be passed through an environment variable since @@ -269,7 +267,7 @@ export class Function extends cloud.Function implements IAwsFunction { /** * Add a policy statement to the Lambda role. */ - public addPolicyStatements(statements: PolicyStatement[]) { + public addPolicyStatements(...statements: PolicyStatement[]) { // we do lazy initialization here because addPolicyStatements() might be called through the // constructor chain of the Function base class which means that our constructor might not have // been called yet... yes, ugly. diff --git a/libs/wingsdk/src/target-tf-aws/queue.ts b/libs/wingsdk/src/target-tf-aws/queue.ts index a547e88645b..df9c5cf8aa0 100644 --- a/libs/wingsdk/src/target-tf-aws/queue.ts +++ b/libs/wingsdk/src/target-tf-aws/queue.ts @@ -65,18 +65,16 @@ export class Queue extends cloud.Queue { throw new Error("Queue only supports creating tfaws.Function right now"); } - fn.addPolicyStatements([ - { - actions: [ - "sqs:ReceiveMessage", - "sqs:ChangeMessageVisibility", - "sqs:GetQueueUrl", - "sqs:DeleteMessage", - "sqs:GetQueueAttributes", - ], - resources: [this.queue.arn], - }, - ]); + fn.addPolicyStatements({ + actions: [ + "sqs:ReceiveMessage", + "sqs:ChangeMessageVisibility", + "sqs:GetQueueUrl", + "sqs:DeleteMessage", + "sqs:GetQueueAttributes", + ], + resources: [this.queue.arn], + }); new LambdaEventSourceMapping(this, "EventSourceMapping", { functionName: fn._functionName, @@ -100,7 +98,7 @@ export class Queue extends cloud.Queue { const env = this.envName(); - host.addPolicyStatements(calculateQueuePermissions(this.queue.arn, ops)); + host.addPolicyStatements(...calculateQueuePermissions(this.queue.arn, ops)); // The queue url needs to be passed through an environment variable since // it may not be resolved until deployment time. diff --git a/libs/wingsdk/src/target-tf-aws/redis.ts b/libs/wingsdk/src/target-tf-aws/redis.ts index 34f2ede9471..1f2a57e6ead 100644 --- a/libs/wingsdk/src/target-tf-aws/redis.ts +++ b/libs/wingsdk/src/target-tf-aws/redis.ts @@ -102,12 +102,10 @@ export class Redis extends ex.Redis { // Ops do not matter here since the client connects directly to the cluster. // The only thing that we need to use AWS API for is to get the cluster endpoint // from the cluster ID. - host.addPolicyStatements([ - { - actions: ["elasticache:Describe*"], - resources: [this.clusterArn], - }, - ]); + host.addPolicyStatements({ + actions: ["elasticache:Describe*"], + resources: [this.clusterArn], + }); host.addEnvironment(env, this.clusterId); host.addNetworkConfig({ diff --git a/libs/wingsdk/src/target-tf-aws/secret.ts b/libs/wingsdk/src/target-tf-aws/secret.ts index 0a204800fb6..604f72589fd 100644 --- a/libs/wingsdk/src/target-tf-aws/secret.ts +++ b/libs/wingsdk/src/target-tf-aws/secret.ts @@ -54,7 +54,9 @@ export class Secret extends cloud.Secret { throw new Error("secrets can only be bound by tfaws.Function for now"); } - host.addPolicyStatements(calculateSecretPermissions(this.secret.arn, ops)); + host.addPolicyStatements( + ...calculateSecretPermissions(this.secret.arn, ops) + ); host.addEnvironment(this.envName(), this.secret.arn); diff --git a/libs/wingsdk/src/target-tf-aws/table.ts b/libs/wingsdk/src/target-tf-aws/table.ts index ba37fb13e8f..1b64fb03fd7 100644 --- a/libs/wingsdk/src/target-tf-aws/table.ts +++ b/libs/wingsdk/src/target-tf-aws/table.ts @@ -60,47 +60,37 @@ export class Table extends ex.Table { ops.includes(ex.TableInflightMethods.INSERT) || ops.includes(ex.TableInflightMethods.UPSERT) ) { - host.addPolicyStatements([ - { - actions: ["dynamodb:PutItem"], - resources: [this.table.arn], - }, - ]); + host.addPolicyStatements({ + actions: ["dynamodb:PutItem"], + resources: [this.table.arn], + }); } if (ops.includes(ex.TableInflightMethods.UPDATE)) { - host.addPolicyStatements([ - { - actions: ["dynamodb:UpdateItem"], - resources: [this.table.arn], - }, - ]); + host.addPolicyStatements({ + actions: ["dynamodb:UpdateItem"], + resources: [this.table.arn], + }); } if (ops.includes(ex.TableInflightMethods.DELETE)) { - host.addPolicyStatements([ - { - actions: ["dynamodb:DeleteItem"], - resources: [this.table.arn], - }, - ]); + host.addPolicyStatements({ + actions: ["dynamodb:DeleteItem"], + resources: [this.table.arn], + }); } if (ops.includes(ex.TableInflightMethods.GET)) { - host.addPolicyStatements([ - { - actions: ["dynamodb:GetItem"], - resources: [this.table.arn], - }, - ]); + host.addPolicyStatements({ + actions: ["dynamodb:GetItem"], + resources: [this.table.arn], + }); } if (ops.includes(ex.TableInflightMethods.LIST)) { - host.addPolicyStatements([ - { - actions: ["dynamodb:Scan"], - resources: [this.table.arn], - }, - ]); + host.addPolicyStatements({ + actions: ["dynamodb:Scan"], + resources: [this.table.arn], + }); } host.addEnvironment(this.envName(), this.table.name); diff --git a/libs/wingsdk/src/target-tf-aws/topic.ts b/libs/wingsdk/src/target-tf-aws/topic.ts index 5948bcf882d..742718587dc 100644 --- a/libs/wingsdk/src/target-tf-aws/topic.ts +++ b/libs/wingsdk/src/target-tf-aws/topic.ts @@ -139,7 +139,7 @@ export class Topic extends cloud.Topic { throw new Error("topics can only be bound by tfaws.Function for now"); } - host.addPolicyStatements(calculateTopicPermissions(this.topic.arn, ops)); + host.addPolicyStatements(...calculateTopicPermissions(this.topic.arn, ops)); host.addEnvironment(this.envName(), this.topic.arn);