Skip to content

Commit

Permalink
feat(sdk): aws.FunctionRef (#6111)
Browse files Browse the repository at this point in the history
Reference an existing lambda function with `aws.FunctionRef`

## 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)*.
  • Loading branch information
eladcon authored Apr 1, 2024
1 parent 4c03dd8 commit d096652
Show file tree
Hide file tree
Showing 6 changed files with 629 additions and 2 deletions.
141 changes: 141 additions & 0 deletions docs/docs/04-standard-library/aws/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,147 @@ The IAM certificate identifier value.
---


### FunctionRef <a name="FunctionRef" id="@winglang/sdk.aws.FunctionRef"></a>

A reference to an external Lambda function.

#### Initializers <a name="Initializers" id="@winglang/sdk.aws.FunctionRef.Initializer"></a>

```wing
bring aws;
new aws.FunctionRef(functionArn: str);
```

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@winglang/sdk.aws.FunctionRef.Initializer.parameter.functionArn">functionArn</a></code> | <code>str</code> | *No description.* |

---

##### `functionArn`<sup>Required</sup> <a name="functionArn" id="@winglang/sdk.aws.FunctionRef.Initializer.parameter.functionArn"></a>

- *Type:* str

---

#### Methods <a name="Methods" id="Methods"></a>

##### Inflight Methods

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@winglang/sdk.cloud.IFunctionClient.invoke">invoke</a></code> | Invokes the function with a payload and waits for the result. |
| <code><a href="#@winglang/sdk.cloud.IFunctionClient.invokeAsync">invokeAsync</a></code> | Kicks off the execution of the function with a payload and returns immediately while the function is running. |

---

##### `invoke` <a name="invoke" id="@winglang/sdk.cloud.IFunctionClient.invoke"></a>

```wing
inflight invoke(payload?: str): str
```

Invokes the function with a payload and waits for the result.

###### `payload`<sup>Optional</sup> <a name="payload" id="@winglang/sdk.cloud.IFunctionClient.invoke.parameter.payload"></a>

- *Type:* str

payload to pass to the function.

If not defined, an empty string will be passed.

---

##### `invokeAsync` <a name="invokeAsync" id="@winglang/sdk.cloud.IFunctionClient.invokeAsync"></a>

```wing
inflight invokeAsync(payload?: str): void
```

Kicks off the execution of the function with a payload and returns immediately while the function is running.

###### `payload`<sup>Optional</sup> <a name="payload" id="@winglang/sdk.cloud.IFunctionClient.invokeAsync.parameter.payload"></a>

- *Type:* str

payload to pass to the function.

If not defined, an empty string will be passed.

---

#### Static Functions <a name="Static Functions" id="Static Functions"></a>

| **Name** | **Description** |
| --- | --- |
| <code><a href="#@winglang/sdk.aws.FunctionRef.onLiftType">onLiftType</a></code> | A hook called by the Wing compiler once for each inflight host that needs to use this type inflight. |

---

##### `onLiftType` <a name="onLiftType" id="@winglang/sdk.aws.FunctionRef.onLiftType"></a>

```wing
bring aws;
aws.FunctionRef.onLiftType(host: IInflightHost, ops: MutArray<str>);
```

A hook called by the Wing compiler once for each inflight host that needs to use this type inflight.

The list of requested inflight methods
needed by the inflight host are given by `ops`.

This method is commonly used for adding permissions, environment variables, or
other capabilities to the inflight host.

###### `host`<sup>Required</sup> <a name="host" id="@winglang/sdk.aws.FunctionRef.onLiftType.parameter.host"></a>

- *Type:* <a href="#@winglang/sdk.std.IInflightHost">IInflightHost</a>

---

###### `ops`<sup>Required</sup> <a name="ops" id="@winglang/sdk.aws.FunctionRef.onLiftType.parameter.ops"></a>

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

---

#### Properties <a name="Properties" id="Properties"></a>

| **Name** | **Type** | **Description** |
| --- | --- | --- |
| <code><a href="#@winglang/sdk.aws.FunctionRef.property.node">node</a></code> | <code>constructs.Node</code> | The tree node. |
| <code><a href="#@winglang/sdk.aws.FunctionRef.property.functionArn">functionArn</a></code> | <code>str</code> | The ARN of this function. |

---

##### `node`<sup>Required</sup> <a name="node" id="@winglang/sdk.aws.FunctionRef.property.node"></a>

```wing
node: Node;
```

- *Type:* constructs.Node

The tree node.

---

##### `functionArn`<sup>Required</sup> <a name="functionArn" id="@winglang/sdk.aws.FunctionRef.property.functionArn"></a>

```wing
functionArn: str;
```

- *Type:* str

The ARN of this function.

---


### QueueRef <a name="QueueRef" id="@winglang/sdk.aws.QueueRef"></a>

A reference to an external SQS queue.
Expand Down
45 changes: 45 additions & 0 deletions examples/tests/sdk_tests/function/function-ref.test.w
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
bring cloud;
bring aws;
bring util;

// a really cool way to test a "ref" resource: basically, we define a cloud.Function
// and then we define a `FunctionRef` that references it's ARN. nice, ha?

let c = new cloud.Counter();
let f = new cloud.Function(inflight () => {
c.inc();
});

// this will only work if we are testing on tf-aws
if let arn = aws.Function.from(f)?.functionArn {
let fref = new aws.FunctionRef(arn);

test "can invoke a function (FunctionRef)" {
fref.invoke("");
util.waitUntil(() => {
return c.peek() == 1;
});
}
}

if util.env("WING_TARGET") == "sim" {
bring expect;

let dummyArn = "arn:aws:lambda:us-east-1:111111111111:function:Function-11111111";
let fr = new aws.FunctionRef(dummyArn);

test "functionArn returns the arn" {
expect.equal(dummyArn, fr.functionArn);
}

test "invoke() sends a request to aws, fails because we are using a dummy function" {
let var err = false;
try {
fr.invoke("");
} catch e {
err = true;
}

assert(err);
}
}
116 changes: 115 additions & 1 deletion libs/wingsdk/src/shared-aws/function.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { Construct } from "constructs";
import { PolicyStatement } from "./types";
import { IInflightHost } from "../std";
import { isValidArn } from "./util";
import { FunctionInflightMethods, IFunctionClient } from "../cloud";
import { InflightClient } from "../core";
import { INFLIGHT_SYMBOL } from "../core/types";
import { Testing } from "../simulator";
import { IInflightHost, Node, Resource } from "../std";
import * as ui from "../ui";

/**
* A shared interface for AWS functions.
Expand Down Expand Up @@ -51,3 +58,110 @@ export class Function {
);
}
}

/**
* A reference to an external Lambda function.
*
* @inflight `@winglang/sdk.cloud.IFunctionClient`
*/
export class FunctionRef extends Resource {
/** @internal */
public [INFLIGHT_SYMBOL]?: IFunctionClient;

/**
* The ARN of this function.
*/
public readonly functionArn: string;

constructor(scope: Construct, id: string, functionArn: string) {
super(scope, id);

if (!isValidArn(functionArn, "lambda")) {
throw new Error(`"${functionArn}" is not a valid Amazon Lambda ARN`);
}

this.functionArn = functionArn;

this.addUserInterface();
}

public onLift(host: IInflightHost, ops: string[]): void {
// if this is an AWS function, add the necessary IAM permissions
const fn = Function.from(host);

if (fn) {
if (
ops.includes(FunctionInflightMethods.INVOKE) ||
ops.includes(FunctionInflightMethods.INVOKE_ASYNC)
) {
fn.addPolicyStatements({
actions: ["lambda:InvokeFunction"],
resources: [this.functionArn],
});
}
}

host.addEnvironment(this.envName(), this.functionArn);
super.onLift(host, ops);
}

/** @internal */
public _toInflight(): string {
return InflightClient.for(__dirname, __filename, "FunctionClient", [
`process.env["${this.envName()}"]`,
]);
}

private envName(): string {
return `FUNCTION_NAME_${this.node.addr.slice(-8)}`;
}

/** @internal */
public _supportedOps(): string[] {
return [
FunctionInflightMethods.INVOKE,
FunctionInflightMethods.INVOKE_ASYNC,
];
}

private addUserInterface() {
Node.of(this).color = "pink";

const awsConsoleHandler = Testing.makeHandler(
`async handle() {
try {
const parts = this.function.functionArn.split(":");
const region = parts[3];
const name = parts[6];
return "https://" + region + ".console.aws.amazon.com/lambda/home?region=" + region + "#/functions/" + name;
} catch (e) {
return e.message;
}
}`,
{
function: {
obj: this,
ops: [],
},
}
);

new ui.Field(this, "AwsConsoleField", "AWS Console", awsConsoleHandler, {
link: true,
});

const functionArnHandler = Testing.makeHandler(
`async handle() {
return this.functionArn;
}`,
{
functionArn: {
obj: this.functionArn,
ops: [],
},
}
);

new ui.Field(this, "FunctionArnField", "Function ARN", functionArnHandler);
}
}
5 changes: 4 additions & 1 deletion libs/wingsdk/src/shared-aws/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ export function isValidArn(arn: string, service: string) {
return true;
}

// ARN format: arn:partition:service:region:account-id:resource-type?:resource
// e.g. arn:aws:lambda:us-east-1:111111111111:function:Function-11111111
// or, arn:aws:sqs:us-east-1:111111111111:Queue-11111111
const parts = arn.split(":");
if (parts.length !== 6) {
if (parts.length < 6 || parts.length > 7) {
return false;
}

Expand Down
Loading

0 comments on commit d096652

Please sign in to comment.