From 8b517b844edbfda1fe1e8376962908ec261817e0 Mon Sep 17 00:00:00 2001 From: Marcio Cruz de Almeida <67694075+marciocadev@users.noreply.github.com> Date: Fri, 11 Aug 2023 08:10:09 -0300 Subject: [PATCH] fix(sdk): custom bind method on awscdk targets (#3769) Following @eladb's recommendation in PR #3398, I removed the import of `target-awscdk` and `target-tf-aws`, and checked if the `host` implements the methods of the `IAwsFunction` interface. This way, the error that was occurring due to not having `aws-cdk-lib` globally installed no longer happens. ## Checklist - [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [x] Description explains motivation and solution - [x] Tests added (always) - [ ] Docs updated (only required for features) - [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing *By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*. --- examples/tests/valid/dynamo.js | 15 ++- examples/tests/valid/dynamo_awscdk.w | 123 +++++++++++++++++++++ libs/wingsdk/src/shared-aws/function.ts | 11 +- libs/wingsdk/src/target-awscdk/function.ts | 4 +- 4 files changed, 146 insertions(+), 7 deletions(-) create mode 100644 examples/tests/valid/dynamo_awscdk.w diff --git a/examples/tests/valid/dynamo.js b/examples/tests/valid/dynamo.js index 93bbf2af889..04c09c8c2c1 100644 --- a/examples/tests/valid/dynamo.js +++ b/examples/tests/valid/dynamo.js @@ -1,4 +1,4 @@ -const { DynamoDBClient, PutItemCommand } = require("@aws-sdk/client-dynamodb"); +const { DynamoDBClient, PutItemCommand, GetItemCommand } = require("@aws-sdk/client-dynamodb"); const client = new DynamoDBClient({}); @@ -7,8 +7,19 @@ export async function _putItem(tableName, item) { TableName: tableName, Item: item, }); - + const response = await client.send(command); console.log(response); return; } + +export async function _getItem(tableName, key) { + const command = new GetItemCommand({ + TableName: tableName, + Key: key + }); + + const response = await client.send(command); + console.log(response); + return response; +} \ No newline at end of file diff --git a/examples/tests/valid/dynamo_awscdk.w b/examples/tests/valid/dynamo_awscdk.w new file mode 100644 index 00000000000..f140f6a4e0d --- /dev/null +++ b/examples/tests/valid/dynamo_awscdk.w @@ -0,0 +1,123 @@ +/*\ +skip: true +\*/ +// this example only works on AWS (intentionally) + +bring "aws-cdk-lib" as awscdk; +bring aws; +bring cloud; +bring util; + +enum AttributeType { + String, + Number, + Binary, +} + +struct Attribute { + type: AttributeType; + value: Json; +} + +class DynamoTable { + table: awscdk.aws_dynamodb.Table; + tableName: str; + init() { + let target = util.env("WING_TARGET"); + if target != "awscdk" { + throw("Unsupported target: ${target} (expected 'awscdk')"); + } + + this.table = new awscdk.aws_dynamodb.Table( + tableName: this.node.addr, + billingMode: awscdk.aws_dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: awscdk.RemovalPolicy.DESTROY, + partitionKey: awscdk.aws_dynamodb.Attribute { + name: "Flavor", + type: awscdk.aws_dynamodb.AttributeType.STRING, + }, + ); + this.tableName = this.table.tableName; + } + + bind(host: std.IInflightHost, ops: Array) { + if let host = aws.Function.from(host) { + if ops.contains("putItem") { + host.addPolicyStatements([aws.PolicyStatement { + actions: ["dynamodb:PutItem"], + resources: [this.table.tableArn], + effect: aws.Effect.ALLOW, + }]); + } + + if ops.contains("getItem") { + host.addPolicyStatements([aws.PolicyStatement { + actions: ["dynamodb:GetItem"], + resources: [this.table.tableArn], + effect: aws.Effect.ALLOW, + }]); + } + } + } + + extern "./dynamo.js" inflight _putItem(tableName: str, item: Json): void; + inflight putItem(item: Map) { + let json = this._itemToJson(item); + this._putItem(this.tableName, json); + } + + extern "./dynamo.js" inflight _getItem(tableName: str, key: Json): Json; + inflight getItem(key: Map): Json { + let json = this._itemToJson(key); + return this._getItem(this.tableName, json); + } + + inflight _itemToJson(item: Map): Json { + let json = MutJson {}; + for key in item.keys() { + let attribute = item.get(key); + let attributeTypeStr = this._attributeTypeToString(attribute.type); + + let innerJson = MutJson {}; + innerJson.set(attributeTypeStr, attribute.value); + json.set(key, innerJson); + } + return json; + } + + inflight _attributeTypeToString(type: AttributeType): str { + if type == AttributeType.String { + return "S"; + } elif type == AttributeType.Number { + return "N"; + } elif type == AttributeType.Binary { + return "B"; + } + } +} + +// --- tests --- + +let table = new DynamoTable(); + +test "cdk table" { + table.putItem({ + "Flavor" => Attribute { + type: AttributeType.String, + value: "Chocolate", + }, + "Quantity" => Attribute { + type: AttributeType.String, + value: "20Kg" + } + }); + + let c = table.getItem({ + "Flavor" => Attribute { + type: AttributeType.String, + value: "Chocolate", + } + }); + assert(c.get("Item").get("Flavor").get("S").asStr() == "Chocolate"); + assert(c.get("Item").get("Quantity").get("S").asStr() == "20Kg"); +} \ No newline at end of file diff --git a/libs/wingsdk/src/shared-aws/function.ts b/libs/wingsdk/src/shared-aws/function.ts index d5466056690..773df3ebad8 100644 --- a/libs/wingsdk/src/shared-aws/function.ts +++ b/libs/wingsdk/src/shared-aws/function.ts @@ -1,6 +1,5 @@ import { PolicyStatement } from "./types"; import { IInflightHost } from "../std"; -import { Function as TfAwsFunction } from "../target-tf-aws"; /** * A shared interface for AWS functions. @@ -30,10 +29,16 @@ export class Function { * @param host The inflight host. */ public static from(host: IInflightHost): IAwsFunction | undefined { - if (host instanceof TfAwsFunction) { + if (this.isAwsFunction(host)) { return host; } - return undefined; } + + private static isAwsFunction(obj: any): obj is IAwsFunction { + return ( + typeof obj.addPolicyStatements === "function" && + typeof obj.addEnvironment === "function" + ); + } } diff --git a/libs/wingsdk/src/target-awscdk/function.ts b/libs/wingsdk/src/target-awscdk/function.ts index 4fc0f0e47bb..f01614d55f5 100644 --- a/libs/wingsdk/src/target-awscdk/function.ts +++ b/libs/wingsdk/src/target-awscdk/function.ts @@ -11,7 +11,7 @@ import { Construct } from "constructs"; import * as cloud from "../cloud"; import * as core from "../core"; import { createBundle } from "../shared/bundling"; -import { PolicyStatement } from "../shared-aws"; +import { IAwsFunction, PolicyStatement } from "../shared-aws"; import { IInflightHost } from "../std"; /** @@ -19,7 +19,7 @@ import { IInflightHost } from "../std"; * * @inflight `@winglang/sdk.cloud.IFunctionClient` */ -export class Function extends cloud.Function { +export class Function extends cloud.Function implements IAwsFunction { private readonly function: CdkFunction; /** Function ARN */ public readonly arn: string;