Skip to content

Commit

Permalink
fix(platforms): unable to perform awscdk context lookups (#6286)
Browse files Browse the repository at this point in the history
In order to fix #6279, we need synthesis to be triggered by the CDK CLI instead of directly from the Wing CLI.

This is actually really easy to do. All you need is to create a `cdk.json` file (or use one from `cdk init`) and modify the `app` and the `output` options like so:

```json
{
  "app": "CDK_STACK_NAME=MyStack wing compile --platform @winglang/platform-awscdk main.w",
  "output": "target/main.awscdk"
}
```

Then, you can simply use `cdk deploy`, `cdk diff`, `cdk synth` as if it was a normal CDK app. No need to explicitly interact with the Wing CLI in this case.

To allow context lookups, we need to support specifying the stack's AWS environment (account/region), so two new environment variables have been added: `CDK_AWS_ACCOUNT` and `CDK_AWS_REGION`. These can be set together with `CDK_STACK_NAME` in the `cdk.json` file mentioned above.

## 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)
- [x] Docs updated (only required for features)
- [x] 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
eladb authored Apr 21, 2024
1 parent 65d66c2 commit ddd52cb
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 23 deletions.
149 changes: 130 additions & 19 deletions docs/docs/055-platforms/awscdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,50 +6,161 @@ description: AWS CDK platform
keywords: [Wing reference, Wing language, language, Wing language spec, Wing programming language, aws, awscdk, amazon web services, cloudformation]
---

The `@winglang/platform-awscdk` [platform](../02-concepts/03-platforms.md) compiles your program for the AWS CDK (CloudFormation).
The `@winglang/platform-awscdk` [platform](../02-concepts/03-platforms.md) compiles your program for
the AWS CDK and deployed through the CDK CLI (and AWS CloudFormation).

## Prerequisites

* Install the AWS CDK (or use via `npx cdk`):
```sh
npm i aws-cdk
```

* Install the Wing `awscdk` platform:
```sh
npm i @winglang/platform-awscdk
```

## Usage

You will need to install the `@winglang/platform-awscdk` library in order to use this platform.
Let's create `main.w` with our Wing program:
```js
bring cloud;
new cloud.Bucket();
```
> At this point, you can just run `wing it` (or `wing run`) to open the Wing Simulator.
To use Wing with the AWS CDK, we will need to tell the CDK CLI to run `wing compile` as the CDK app
and that the synthesis output is in the Wing's target directory.

This can be done by creating a `cdk.json` file manually or through `cdk init` and editing the `app`
and the `output` fields:

```json
{
"app": "CDK_STACK_NAME='MyStack' CDK_AWS_ACCOUNT='111111555555' CDK_AWS_REGION='us-east-1' wing compile --platform @winglang/platform-awscdk main.w",
"output": "target/main.awscdk",
// ... rest of cdk.json
}
```

The `awscdk` platform uses the following environment variables as configuration options:

* `CDK_STACK_NAME` (required) - sets the CloudFormation stack name to use.
- `CDK_AWS_ACCOUNT` and `CDK_AWS_REGION` (optional) - the AWS environment for deployment and context
lookups (e.g. VPC lookups). The default is to use the AWS account region defined in the CLI
environment.

Now, the AWS CDK CLI will work as normal:

* `npx cdk bootstrap` bootstrap your AWS account for AWS CDK use (once per account/region).
* `npx cdk deploy` deploy the Wing app to your default AWS account/region.
* `npx cdk synth` synthesize output to `cdk.out`.
* `npx cdk diff` show a diff between your code and the deployed version

## Deployment

Before you can first deploy an AWS CDK app to your account, you'll need to bootstrap the account/region:
```sh
$ npm i @winglang/platform-awscdk
npx cdk bootstrap
```
This platform requires the environment variable `CDK_STACK_NAME` to be set to the name of the CDK
stack to synthesize.
Now, you can use `cdk deploy` to deploy the latest version:
```sh
$ export CDK_STACK_NAME="my-project"
$ wing compile --platform @winglang/platform-awscdk [entrypoint]
npx cdk deploy
```
## Parameters
Let's make a change to your app:

The `CDK_STACK_NAME` environment variable specifies the name of the CDK stack to synthesize.
```js
bring cloud;
## Output
let b = new cloud.Bucket();
The output includes both a AWS-CDK configuration file (under `target/<entrypoint>.awscdk`) and
JavaScript bundles that include inflight code that executes on compute platforms such as AWS Lambda.
new cloud.Function(inflight () => {
b.put("hello.txt", "world");
});
```

## Deployment
If we run `cdk diff` we should see the new resources that are about to be created:

To deploy your app, you will first need to install the [AWS CDK
CLI](https://docs.aws.amazon.com/cdk/v2/guide/cli.html).
```sh
$ npx cdk diff
IAM Statement Changes
┌───┬──────────────────────────────────┬────────┬──────────────────────────────────┬──────────────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼──────────────────────────────────┼────────┼──────────────────────────────────┼──────────────────────────────────┼───────────┤
│ + │ ${Default/Default/Bucket/Default │ Allow │ s3:Abort* │ AWS:${Default/Default/Function/D │ │
│ │ .Arn} │ │ s3:PutObject* │ efault/ServiceRole} │ │
│ │ ${Default/Default/Bucket/Default │ │ │ │ │
│ │ .Arn}/* │ │ │ │ │
├───┼──────────────────────────────────┼────────┼──────────────────────────────────┼──────────────────────────────────┼───────────┤
│ + │ ${Default/Default/Function/Defau │ Allow │ sts:AssumeRole │ Service:lambda.amazonaws.com │ │
│ │ lt/ServiceRole.Arn} │ │ │ │ │
└───┴──────────────────────────────────┴────────┴──────────────────────────────────┴──────────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬──────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────────┐
│ │ Resource │ Managed Policy ARN │
├───┼──────────────────────────────────────────────────────────────┼──────────────────────────────────────────────────────────────┤
│ + │ ${Default/Default/Function/Default/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambda │
│ │ │ BasicExecutionRole │
└───┴──────────────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)
Resources
[+] AWS::Logs::LogGroup Default/Default/Function/LogGroup FunctionLogGroup55B80E27
[+] AWS::IAM::Role Default/Default/Function/Default/ServiceRole FunctionServiceRole675BB04A
[+] AWS::IAM::Policy Default/Default/Function/Default/ServiceRole/DefaultPolicy FunctionServiceRoleDefaultPolicy2F49994A
[+] AWS::Lambda::Function Default/Default/Function/Default Function76856677
✨ Number of stacks with differences: 1
```

If not previously done, you will need to bootstrap your environment (account/region):
Sweet!, now deploy again:

```sh
$ cdk bootstrap --app target/app.awscdk
npx cdk deploy
```

And then you can deploy:
To destroy your stack, you can use:

```sh
$ cdk deploy --app target/app.awscdk
npx cdk destroy
```

## Bringing AWS CDK constructs to your Wing code

You can bring any AWS CDK library and use constructs in your Wing programs.

> Using AWS CDK constructs directly in your Wing application will only have an effect when compiling
> and deploying AWS CDK applications and not when running in the Wing Simulator.

The following example shows how to define an EC2 instance inside a VPC (from a lookup):

```js
bring "aws-cdk-lib" as cdk;
let myVpc = cdk.aws_ec2.Vpc.fromLookup(this, "MyVpc", vpcId: "vpc-111111111222ddddd");
let type = cdk.aws_ec2.InstanceType.of(cdk.aws_ec2.InstanceClass.T2, cdk.aws_ec2.InstanceSize.MICRO);
new cdk.aws_ec2.Instance(
instanceType: type,
machineImage: cdk.aws_ec2.MachineImage.latestAmazonLinux2(),
vpc: myVpc,
);
```

Note that in order for the VPC lookup to work, you will need to make sure `CDK_AWS_ACCOUNT` and
`CDK_AWS_REGION` are configured properly in your `cdk.json` `app` configuration.

## Customizations

### Custom CDK Stack
Expand Down
1 change: 1 addition & 0 deletions libs/awscdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
},
"scripts": {
"compile": "tsc",
"watch": "tsc --watch",
"package": "bump-pack -b",
"test": "vitest run --passWithNoTests --update"
},
Expand Down
14 changes: 10 additions & 4 deletions libs/awscdk/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export interface CdkAppProps extends core.AppProps {
*
* @default - creates a standard `cdk.Stack`
*/
readonly stackFactory?: (app: cdk.App, stackName: string) => cdk.Stack;
readonly stackFactory?: (app: cdk.App, stackName: string, props?: cdk.StackProps) => cdk.Stack;
}

/**
Expand All @@ -76,6 +76,9 @@ export class App extends core.App {
private synthedOutput: string | undefined;

constructor(props: CdkAppProps) {
const account = process.env.CDK_AWS_ACCOUNT ?? process.env.CDK_DEFAULT_ACCOUNT;
const region = process.env.CDK_AWS_REGION ?? process.env.CDK_DEFAULT_REGION;

let stackName = props.stackName ?? process.env.CDK_STACK_NAME;
if (stackName === undefined) {
throw new Error(
Expand All @@ -95,11 +98,14 @@ export class App extends core.App {
const cdkApp = new cdk.App({ outdir: cdkOutdir });

const createStack =
props.stackFactory ?? ((app, stackName) => new cdk.Stack(app, stackName));
const cdkStack = createStack(cdkApp, stackName);
props.stackFactory ?? ((app, stackName, props) => new cdk.Stack(app, stackName, props));

super(cdkStack, props.rootId ?? "Default", props);
const cdkStack = createStack(cdkApp, stackName, {
env: { account, region }
});

super(cdkStack, props.rootId ?? "Default", props);

// HACK: monkey patch the `new` method on the cdk app (which is the root of the tree) so that
// we can intercept the creation of resources and replace them with our own.
(cdkApp as any).new = (
Expand Down
66 changes: 66 additions & 0 deletions libs/awscdk/test/platform.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { test, expect } from "vitest";
import { Platform } from "../src/platform"
import { mkdtemp } from "@winglang/sdk/test/util";
import { readdirSync } from "fs";
import { Bucket } from "../src";
import { Stack } from "aws-cdk-lib";

test("wing platform", async () => {
const workdir = mkdtemp();
const platform = new Platform();
process.env.CDK_STACK_NAME = "MyStack";
const app = platform.newApp?.({ entrypointDir: workdir, outdir: workdir });

new Bucket(app, "bucket");

const out = app.synth();

expect(JSON.parse(out).Resources.bucket43879C71).toStrictEqual({
DeletionPolicy: "Delete",
Properties: {
BucketEncryption: {
ServerSideEncryptionConfiguration: [
{
ServerSideEncryptionByDefault: {
SSEAlgorithm: "AES256",
},
},
],
},
PublicAccessBlockConfiguration: {
BlockPublicAcls: true,
BlockPublicPolicy: true,
IgnorePublicAcls: true,
RestrictPublicBuckets: true,
},
},
Type: "AWS::S3::Bucket",
UpdateReplacePolicy: "Delete",
});

// output directory only contains wing artifacts. cdk artifacts will be in the cdk.out directory
// when the CDK CLI is used
expect(readdirSync(workdir)).toStrictEqual([
"MyStack.assets.json",
"MyStack.template.json",
"cdk.out",
"connections.json",
"manifest.json",
"tree.json",
]);
});

test("CDK_STACK_NAME, CDK_AWS_ACCOUNT, CDK_AWS_REGION", async () => {
const workdir = mkdtemp();
const platform = new Platform();
process.env.CDK_STACK_NAME = "YourStack";
process.env.CDK_AWS_ACCOUNT = "123";
process.env.CDK_AWS_REGION = "us-west-2";

const app = platform.newApp?.({ entrypointDir: workdir, outdir: workdir });
const stack = Stack.of(app);

expect(stack.resolve(stack.region)).toStrictEqual("us-west-2");
expect(stack.resolve(stack.stackName)).toStrictEqual("YourStack");
expect(stack.resolve(stack.account)).toStrictEqual("123");
});

0 comments on commit ddd52cb

Please sign in to comment.