From ddd52cb4becb9746e98527e35ae315665356a576 Mon Sep 17 00:00:00 2001 From: Elad Ben-Israel Date: Mon, 22 Apr 2024 00:26:26 +0300 Subject: [PATCH] fix(platforms): unable to perform awscdk context lookups (#6286) 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)*. --- docs/docs/055-platforms/awscdk.md | 149 ++++++++++++++++++++++++++---- libs/awscdk/package.json | 1 + libs/awscdk/src/app.ts | 14 ++- libs/awscdk/test/platform.test.ts | 66 +++++++++++++ 4 files changed, 207 insertions(+), 23 deletions(-) create mode 100644 libs/awscdk/test/platform.test.ts diff --git a/docs/docs/055-platforms/awscdk.md b/docs/docs/055-platforms/awscdk.md index a6fd5ad5ba8..fa4dc4f1ec9 100644 --- a/docs/docs/055-platforms/awscdk.md +++ b/docs/docs/055-platforms/awscdk.md @@ -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/.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 diff --git a/libs/awscdk/package.json b/libs/awscdk/package.json index 34b9f9e5d96..422cef6c2ba 100644 --- a/libs/awscdk/package.json +++ b/libs/awscdk/package.json @@ -33,6 +33,7 @@ }, "scripts": { "compile": "tsc", + "watch": "tsc --watch", "package": "bump-pack -b", "test": "vitest run --passWithNoTests --update" }, diff --git a/libs/awscdk/src/app.ts b/libs/awscdk/src/app.ts index 9e0a64fc52c..1d36def2b38 100644 --- a/libs/awscdk/src/app.ts +++ b/libs/awscdk/src/app.ts @@ -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; } /** @@ -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( @@ -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 = ( diff --git a/libs/awscdk/test/platform.test.ts b/libs/awscdk/test/platform.test.ts new file mode 100644 index 00000000000..56de662133e --- /dev/null +++ b/libs/awscdk/test/platform.test.ts @@ -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"); +});