-
Notifications
You must be signed in to change notification settings - Fork 196
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sdk): add utilities for custom bind methods (#3368)
This PR introduces the capability to write Wing classes with `_bind` methods that specify custom permissions for resources. This addresses a key limitation that has prevented classes like those found in the standard library (cloud.Bucket, cloud.Counter, etc.) from being written in Wing. Writing `_bind` methods that add permissions hasn't been possible before because the `_bind` method only provides a `std.IInflightHost` as an argument, while in practice it will be provided a concrete class, like the `sim` target's `cloud.Function` implementation or the `tf-aws` target's `cloud.Function` implementation. To narrow the type, the SDK source code currently uses the JS `instanceof` operator (see [here](https://github.com/winglang/wing/blob/d7769ada65a6f3a917fd54843d4690280d421e11/libs/wingsdk/src/target-tf-aws/bucket.ts#L113-L116)) but this isn't supported in Wing today. To support this use case, I added a module called `aws` that exposes a bare minimum class `AwsFunction` that can safely cast a `std.IInflightHost` into a `aws.IAwsFunction`, which has methods like `addEnvironment` and `addIamPolicy` for customizing permissions. I've added an example to our test suite that demonstrates how it's used. ```js class DynamoTable { // ... _bind(host: std.IInflightHost, ops: Array<str>) { if let host = aws.AwsFunction.from(host) { if ops.contains("putItem") { host.addIamPolicy(aws.PolicyStatement { actions: ["dynamodb:PutItem"], resources: [this.table.arn], effect: aws.Effect.ALLOW, }); } } } } ``` ## Checklist - [ ] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted) - [ ] Description explains motivation and solution - [ ] 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 [Monada Contribution License](https://www.winglang.io/terms-and-policies/contribution-license.html)*.
- Loading branch information
Showing
28 changed files
with
464 additions
and
67 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
label: AWS utilities | ||
collapsible: true | ||
collapsed: true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
--- | ||
title: API reference | ||
id: api-reference | ||
description: Wing standard library API reference for the aws module | ||
keywords: [Wing sdk, sdk, Wing API Reference] | ||
hide_title: true | ||
sidebar_position: 100 | ||
--- | ||
|
||
<!-- This file is automatically generated. Do not edit manually. --> | ||
|
||
# API Reference <a name="API Reference" id="api-reference"></a> | ||
|
||
|
||
## Classes <a name="Classes" id="Classes"></a> | ||
|
||
### Function <a name="Function" id="@winglang/sdk.aws.Function"></a> | ||
|
||
A helper class for working with AWS functions. | ||
|
||
#### Initializers <a name="Initializers" id="@winglang/sdk.aws.Function.Initializer"></a> | ||
|
||
```wing | ||
bring aws; | ||
new aws.Function(); | ||
``` | ||
|
||
| **Name** | **Type** | **Description** | | ||
| --- | --- | --- | | ||
|
||
--- | ||
|
||
|
||
#### Static Functions <a name="Static Functions" id="Static Functions"></a> | ||
|
||
| **Name** | **Description** | | ||
| --- | --- | | ||
| <code><a href="#@winglang/sdk.aws.Function.from">from</a></code> | If the inflight host is an AWS function, return a helper interface for working with it. | | ||
|
||
--- | ||
|
||
##### `from` <a name="from" id="@winglang/sdk.aws.Function.from"></a> | ||
|
||
```wing | ||
bring aws; | ||
aws.Function.from(host: IInflightHost); | ||
``` | ||
|
||
If the inflight host is an AWS function, return a helper interface for working with it. | ||
|
||
###### `host`<sup>Required</sup> <a name="host" id="@winglang/sdk.aws.Function.from.parameter.host"></a> | ||
|
||
- *Type:* <a href="#@winglang/sdk.std.IInflightHost">IInflightHost</a> | ||
|
||
The inflight host. | ||
|
||
--- | ||
|
||
|
||
|
||
## Structs <a name="Structs" id="Structs"></a> | ||
|
||
### PolicyStatement <a name="PolicyStatement" id="@winglang/sdk.aws.PolicyStatement"></a> | ||
|
||
AWS IAM Policy Statement. | ||
|
||
#### Initializer <a name="Initializer" id="@winglang/sdk.aws.PolicyStatement.Initializer"></a> | ||
|
||
```wing | ||
bring aws; | ||
let PolicyStatement = aws.PolicyStatement{ ... }; | ||
``` | ||
|
||
#### Properties <a name="Properties" id="Properties"></a> | ||
|
||
| **Name** | **Type** | **Description** | | ||
| --- | --- | --- | | ||
| <code><a href="#@winglang/sdk.aws.PolicyStatement.property.actions">actions</a></code> | <code>MutArray<str></code> | Actions. | | ||
| <code><a href="#@winglang/sdk.aws.PolicyStatement.property.effect">effect</a></code> | <code><a href="#@winglang/sdk.aws.Effect">Effect</a></code> | Effect ("Allow" or "Deny"). | | ||
| <code><a href="#@winglang/sdk.aws.PolicyStatement.property.resources">resources</a></code> | <code>MutArray<str></code> | Resources. | | ||
|
||
--- | ||
|
||
##### `actions`<sup>Optional</sup> <a name="actions" id="@winglang/sdk.aws.PolicyStatement.property.actions"></a> | ||
|
||
```wing | ||
actions: MutArray<str>; | ||
``` | ||
|
||
- *Type:* MutArray<str> | ||
|
||
Actions. | ||
|
||
--- | ||
|
||
##### `effect`<sup>Optional</sup> <a name="effect" id="@winglang/sdk.aws.PolicyStatement.property.effect"></a> | ||
|
||
```wing | ||
effect: Effect; | ||
``` | ||
|
||
- *Type:* <a href="#@winglang/sdk.aws.Effect">Effect</a> | ||
|
||
Effect ("Allow" or "Deny"). | ||
|
||
--- | ||
|
||
##### `resources`<sup>Optional</sup> <a name="resources" id="@winglang/sdk.aws.PolicyStatement.property.resources"></a> | ||
|
||
```wing | ||
resources: MutArray<str>; | ||
``` | ||
|
||
- *Type:* MutArray<str> | ||
|
||
Resources. | ||
|
||
--- | ||
|
||
## Protocols <a name="Protocols" id="Protocols"></a> | ||
|
||
### IAwsFunction <a name="IAwsFunction" id="@winglang/sdk.aws.IAwsFunction"></a> | ||
|
||
- *Implemented By:* <a href="#@winglang/sdk.aws.IAwsFunction">IAwsFunction</a> | ||
|
||
A shared interface for AWS functions. | ||
|
||
#### Methods <a name="Methods" id="Methods"></a> | ||
|
||
| **Name** | **Description** | | ||
| --- | --- | | ||
| <code><a href="#@winglang/sdk.aws.IAwsFunction.addEnvironment">addEnvironment</a></code> | Add an environment variable to the function. | | ||
| <code><a href="#@winglang/sdk.aws.IAwsFunction.addPolicyStatements">addPolicyStatements</a></code> | Add policy statements to the function's IAM role. | | ||
|
||
--- | ||
|
||
##### `addEnvironment` <a name="addEnvironment" id="@winglang/sdk.aws.IAwsFunction.addEnvironment"></a> | ||
|
||
```wing | ||
addEnvironment(key: str, value: str): void | ||
``` | ||
|
||
Add an environment variable to the function. | ||
|
||
###### `key`<sup>Required</sup> <a name="key" id="@winglang/sdk.aws.IAwsFunction.addEnvironment.parameter.key"></a> | ||
|
||
- *Type:* str | ||
|
||
--- | ||
|
||
###### `value`<sup>Required</sup> <a name="value" id="@winglang/sdk.aws.IAwsFunction.addEnvironment.parameter.value"></a> | ||
|
||
- *Type:* str | ||
|
||
--- | ||
|
||
##### `addPolicyStatements` <a name="addPolicyStatements" id="@winglang/sdk.aws.IAwsFunction.addPolicyStatements"></a> | ||
|
||
```wing | ||
addPolicyStatements(policies: MutArray<PolicyStatement>): 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`<sup>Required</sup> <a name="policies" id="@winglang/sdk.aws.IAwsFunction.addPolicyStatements.parameter.policies"></a> | ||
|
||
- *Type:* MutArray<<a href="#@winglang/sdk.aws.PolicyStatement">PolicyStatement</a>> | ||
|
||
--- | ||
|
||
|
||
## Enums <a name="Enums" id="Enums"></a> | ||
|
||
### Effect <a name="Effect" id="@winglang/sdk.aws.Effect"></a> | ||
|
||
The Effect element of an AWS IAM policy statement. | ||
|
||
#### Members <a name="Members" id="Members"></a> | ||
|
||
| **Name** | **Description** | | ||
| --- | --- | | ||
| <code><a href="#@winglang/sdk.aws.Effect.ALLOW">ALLOW</a></code> | Allow. | | ||
| <code><a href="#@winglang/sdk.aws.Effect.DENY">DENY</a></code> | Deny. | | ||
|
||
--- | ||
|
||
##### `ALLOW` <a name="ALLOW" id="@winglang/sdk.aws.Effect.ALLOW"></a> | ||
|
||
Allow. | ||
|
||
--- | ||
|
||
|
||
##### `DENY` <a name="DENY" id="@winglang/sdk.aws.Effect.DENY"></a> | ||
|
||
Deny. | ||
|
||
--- | ||
|
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
const { DynamoDBClient, PutItemCommand } = require("@aws-sdk/client-dynamodb"); | ||
|
||
const client = new DynamoDBClient({}); | ||
|
||
export async function _putItem(tableName, item) { | ||
const command = new PutItemCommand({ | ||
TableName: tableName, | ||
Item: item, | ||
}); | ||
|
||
const response = await client.send(command); | ||
console.log(response); | ||
return; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/*\ | ||
skip: true | ||
\*/ | ||
// this example only works on AWS (intentionally) | ||
|
||
bring "@cdktf/provider-aws" as tfaws; | ||
bring aws; | ||
bring cloud; | ||
bring util; | ||
|
||
enum AttributeType { | ||
String, | ||
Number, | ||
Binary, | ||
} | ||
|
||
struct Attribute { | ||
type: AttributeType; | ||
value: Json; | ||
} | ||
|
||
class DynamoTable { | ||
table: tfaws.dynamodbTable.DynamodbTable; | ||
tableName: str; | ||
init() { | ||
let target = util.env("WING_TARGET"); | ||
if target != "tf-aws" { | ||
throw("Unsupported target: ${target} (expected 'tf-aws')"); | ||
} | ||
|
||
this.table = new tfaws.dynamodbTable.DynamodbTable( | ||
name: this.node.addr, | ||
billingMode: "PAY_PER_REQUEST", | ||
hashKey: "Flavor", | ||
attribute: [ | ||
{ | ||
name: "Flavor", | ||
type: "S", | ||
}, | ||
], | ||
); | ||
this.tableName = this.table.name; | ||
} | ||
|
||
_bind(host: std.IInflightHost, ops: Array<str>) { | ||
if let host = aws.Function.from(host) { | ||
if ops.contains("putItem") { | ||
host.addPolicyStatements([aws.PolicyStatement { | ||
actions: ["dynamodb:PutItem"], | ||
resources: [this.table.arn], | ||
effect: aws.Effect.ALLOW, | ||
}]); | ||
} | ||
} | ||
} | ||
|
||
extern "./dynamo.js" inflight _putItem(tableName: str, item: Json): void; | ||
|
||
inflight putItem(item: Map<Attribute>) { | ||
let json = this._itemToJson(item); | ||
this._putItem(this.tableName, json); | ||
} | ||
|
||
inflight _itemToJson(item: Map<Attribute>): 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 "put an item in the table" { | ||
table.putItem({ | ||
"Flavor" => Attribute { | ||
type: AttributeType.String, | ||
value: "Chocolate", | ||
}, | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.