Skip to content

Commit

Permalink
feat(sdk): add utilities for custom bind methods (#3368)
Browse files Browse the repository at this point in the history
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
Chriscbr authored Jul 12, 2023
1 parent 0acee2a commit b81c75f
Show file tree
Hide file tree
Showing 28 changed files with 464 additions and 67 deletions.
3 changes: 3 additions & 0 deletions docs/docs/04-standard-library/05-aws/_category_.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
label: AWS utilities
collapsible: true
collapsed: true
205 changes: 205 additions & 0 deletions docs/docs/04-standard-library/05-aws/api-reference.md
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&lt;str&gt;</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&lt;str&gt;</code> | Resources. |

---

##### `actions`<sup>Optional</sup> <a name="actions" id="@winglang/sdk.aws.PolicyStatement.property.actions"></a>

```wing
actions: MutArray<str>;
```

- *Type:* MutArray&lt;str&gt;

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&lt;str&gt;

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&lt;<a href="#@winglang/sdk.aws.PolicyStatement">PolicyStatement</a>&gt;

---


## 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.

---

14 changes: 14 additions & 0 deletions examples/tests/valid/dynamo.js
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;
}
99 changes: 99 additions & 0 deletions examples/tests/valid/dynamo.w
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",
},
});
}
1 change: 1 addition & 0 deletions examples/tests/valid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"@aws-cdk/asset-awscli-v1": "^2.2.199",
"@aws-cdk/asset-kubectl-v20": "^2.1.2",
"@aws-cdk/asset-node-proxy-agent-v5": "^2.0.165",
"@aws-sdk/client-dynamodb": "^3.344.0",
"@cdktf/provider-aws": "^15.0.0",
"aws-cdk-lib": "^2.64.0",
"cdktf": "0.17.0",
Expand Down
4 changes: 3 additions & 1 deletion libs/wingc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ const WINGSDK_CLOUD_MODULE: &'static str = "cloud";
const WINGSDK_UTIL_MODULE: &'static str = "util";
const WINGSDK_HTTP_MODULE: &'static str = "http";
const WINGSDK_MATH_MODULE: &'static str = "math";
const WINGSDK_AWS_MODULE: &'static str = "aws";
const WINGSDK_EX_MODULE: &'static str = "ex";

const WINGSDK_BRINGABLE_MODULES: [&'static str; 5] = [
const WINGSDK_BRINGABLE_MODULES: [&'static str; 6] = [
WINGSDK_CLOUD_MODULE,
WINGSDK_UTIL_MODULE,
WINGSDK_HTTP_MODULE,
WINGSDK_MATH_MODULE,
WINGSDK_AWS_MODULE,
WINGSDK_EX_MODULE,
];

Expand Down
18 changes: 15 additions & 3 deletions libs/wingsdk/.projen/tasks.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion libs/wingsdk/.projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const CDKTF_PROVIDERS = [
"google@~>4.63.1",
];

const PUBLIC_MODULES = ["std", "http", "util", "ex"];
const PUBLIC_MODULES = ["std", "http", "util", "aws", "ex"];

const CLOUD_DOCS_PREFIX = "../../docs/docs/04-standard-library/01-cloud/";

Expand Down
1 change: 1 addition & 0 deletions libs/wingsdk/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * as core from "./core";
export * as ex from "./ex";
export * as http from "./http";
export * as math from "./math";
export * as aws from "./shared-aws";
export * as std from "./std";
export * as testing from "./testing";
export * as util from "./util";
Loading

0 comments on commit b81c75f

Please sign in to comment.