From f2248ec1cf0395a2d6934e92eea22d3642369e6c Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:34:10 -0400 Subject: [PATCH 01/12] Use cdk-cli-lib to synthesize CDK application --- examples/alb/index.ts | 27 ++- .../api-websocket-lambda-dynamodb/index.ts | 22 +- examples/apprunner/index.ts | 20 +- examples/appsvc/index.ts | 19 +- examples/cron-lambda/index.ts | 17 +- examples/ec2-instance/index.ts | 21 +- examples/ecscluster/Pulumi.yaml | 3 - examples/ecscluster/index.ts | 34 --- examples/ecscluster/tsconfig.json | 18 -- examples/examples_nodejs_test.go | 20 +- examples/fargate/index.ts | 18 +- examples/lookups/Pulumi.yaml | 3 + examples/lookups/index.ts | 147 ++++++++++++ examples/lookups/package.json | 12 + examples/lookups/tsconfig.json | 18 ++ examples/s3-object-lambda/index.ts | 22 +- .../src/s3-object-lambda-stack.ts | 7 +- package.json | 1 + src/converters/app-converter.ts | 53 ++--- src/converters/artifact-converter.ts | 4 +- src/stack.ts | 221 ++++++++++-------- src/types.ts | 82 ++++--- tests/assembly/manifest.test.ts | 10 +- tests/basic.test.ts | 48 ++-- tests/cdk-resource.test.ts | 128 ++++++---- tests/converters/app-converter.test.ts | 20 +- tests/converters/artifact-converter.test.ts | 0 tests/mocks.ts | 29 +-- yarn.lock | 5 + 29 files changed, 636 insertions(+), 393 deletions(-) delete mode 100644 examples/ecscluster/Pulumi.yaml delete mode 100644 examples/ecscluster/index.ts delete mode 100644 examples/ecscluster/tsconfig.json create mode 100644 examples/lookups/Pulumi.yaml create mode 100644 examples/lookups/index.ts create mode 100644 examples/lookups/package.json create mode 100644 examples/lookups/tsconfig.json create mode 100644 tests/converters/artifact-converter.test.ts diff --git a/examples/alb/index.ts b/examples/alb/index.ts index 78d95c62..22af2cbe 100644 --- a/examples/alb/index.ts +++ b/examples/alb/index.ts @@ -3,24 +3,23 @@ import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import * as pulumi from '@pulumi/pulumi'; import * as pulumicdk from '@pulumi/cdk'; +import { CfnOutput } from 'aws-cdk-lib'; class AlbStack extends pulumicdk.Stack { url: pulumi.Output; - constructor(id: string, options?: pulumicdk.StackOptions) { - super(id, options); - // necessary for local testing - const t = this as any; + constructor(app: pulumicdk.App, id: string) { + super(app, id); - const vpc = new ec2.Vpc(t, 'VPC'); + const vpc = new ec2.Vpc(this, 'VPC'); - const asg = new autoscaling.AutoScalingGroup(t, 'ASG', { + const asg = new autoscaling.AutoScalingGroup(this, 'ASG', { vpc, instanceType: ec2.InstanceType.of(ec2.InstanceClass.T2, ec2.InstanceSize.MICRO), machineImage: new ec2.AmazonLinuxImage(), }); - const lb = new elbv2.ApplicationLoadBalancer(t, 'LB', { + const lb = new elbv2.ApplicationLoadBalancer(this, 'LB', { vpc, internetFacing: true, }); @@ -45,10 +44,18 @@ class AlbStack extends pulumicdk.Stack { }); this.url = this.asOutput(lb.loadBalancerDnsName); + } +} - this.synth(); +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new AlbStack(scope, 'teststack'); + return { url: stack.url }; + }); } } -const stack = new AlbStack('teststack'); -export const url = stack.url; +const app = new MyApp(); + +export const url = app.outputs['url']; diff --git a/examples/api-websocket-lambda-dynamodb/index.ts b/examples/api-websocket-lambda-dynamodb/index.ts index 3ee11bff..3eabb1ac 100644 --- a/examples/api-websocket-lambda-dynamodb/index.ts +++ b/examples/api-websocket-lambda-dynamodb/index.ts @@ -10,8 +10,8 @@ import { LogGroup, RetentionDays } from 'aws-cdk-lib/aws-logs'; class ChatAppStack extends pulumicdk.Stack { public readonly url: Output; public readonly table: Output; - constructor(id: string) { - super(id); + constructor(app: pulumicdk.App, id: string) { + super(app, id); // initialise api const name = id + '-api'; @@ -93,11 +93,21 @@ class ChatAppStack extends pulumicdk.Stack { this.table = this.asOutput(table.tableName); this.url = this.asOutput(stage.url); + } +} - this.synth(); +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new ChatAppStack(scope, 'chat-app'); + return { + url: stack.url, + table: stack.table, + }; + }); } } -const stack = new ChatAppStack('chat-app'); -export const url = stack.url; -export const table = stack.table; +const app = new MyApp(); +export const url = app.outputs['url']; +export const table = app.outputs['table']; diff --git a/examples/apprunner/index.ts b/examples/apprunner/index.ts index 2777ca17..958aafab 100644 --- a/examples/apprunner/index.ts +++ b/examples/apprunner/index.ts @@ -1,15 +1,12 @@ -import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as pulumi from '@pulumi/pulumi'; import * as pulumicdk from '@pulumi/cdk'; -import { Construct } from 'constructs'; import { Service, Source } from '@aws-cdk/aws-apprunner-alpha'; -import { CfnOutput } from 'aws-cdk-lib'; class AppRunnerStack extends pulumicdk.Stack { url: pulumi.Output; - constructor(id: string) { - super(id); + constructor(app: pulumicdk.App, id: string) { + super(app, id); const service = new Service(this, 'service', { source: Source.fromEcrPublic({ @@ -19,10 +16,17 @@ class AppRunnerStack extends pulumicdk.Stack { }); this.url = this.asOutput(service.serviceUrl); + } +} - this.synth(); +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new AppRunnerStack(scope, 'teststack'); + return { url: stack.url }; + }); } } -const stack = new AppRunnerStack('teststack'); -export const url = stack.url; +const app = new MyApp(); +export const url = app.outputs['url']; diff --git a/examples/appsvc/index.ts b/examples/appsvc/index.ts index 58bd181d..f915bb09 100644 --- a/examples/appsvc/index.ts +++ b/examples/appsvc/index.ts @@ -22,8 +22,8 @@ const azs = aws.getAvailabilityZonesOutput({ class ClusterStack extends pulumicdk.Stack { serviceName: pulumi.Output; - constructor(name: string) { - super(name); + constructor(app: pulumicdk.App, name: string) { + super(app, name); const vpc = ec2.Vpc.fromVpcAttributes(this, 'Vpc', { vpcId: pulumicdk.asString(defaultVpc.id), @@ -82,11 +82,18 @@ class ClusterStack extends pulumicdk.Stack { ], }); - this.synth(); - this.serviceName = this.asOutput(service.serviceName); } } -const stack = new ClusterStack('teststack'); -export const serviceName = stack.serviceName; +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new ClusterStack(scope, 'teststack'); + return { serviceName: stack.serviceName }; + }); + } +} + +const app = new MyApp(); +export const serviceName = app.outputs['serviceName']; diff --git a/examples/cron-lambda/index.ts b/examples/cron-lambda/index.ts index 644fe821..1f16c520 100644 --- a/examples/cron-lambda/index.ts +++ b/examples/cron-lambda/index.ts @@ -9,8 +9,8 @@ import * as pulumicdk from '@pulumi/cdk'; class LambdaStack extends pulumicdk.Stack { lambdaArn: pulumi.Output; - constructor(id: string, options?: pulumicdk.StackOptions) { - super(id, options); + constructor(app: pulumicdk.App, id: string) { + super(app, id); // Use the AWS CDK Lambda Function API directly. const lambdaFn = new aws_lambda.Function(this, 'lambda', { @@ -31,10 +31,17 @@ class LambdaStack extends pulumicdk.Stack { // Export the Lambda function's ARN as an output. this.lambdaArn = this.asOutput(lambdaFn.functionArn); + } +} - this.synth(); +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new LambdaStack(scope, 'teststack'); + return { lambdaArn: stack.lambdaArn }; + }); } } -const stack = new LambdaStack('teststack'); -export const lambdaArn = stack.lambdaArn; +const app = new MyApp(); +export const lambdaArn = app.outputs['lambdaArn']; diff --git a/examples/ec2-instance/index.ts b/examples/ec2-instance/index.ts index 613c2ebe..08274a42 100644 --- a/examples/ec2-instance/index.ts +++ b/examples/ec2-instance/index.ts @@ -6,8 +6,8 @@ import * as pulumicdk from '@pulumi/cdk'; import { Asset } from 'aws-cdk-lib/aws-s3-assets'; export class Ec2CdkStack extends pulumicdk.Stack { - constructor(id: string) { - super(id); + constructor(app: pulumicdk.App, id: string) { + super(app, id); // Create a Key Pair to be used with this EC2 Instance // Temporarily disabled since `cdk-ec2-key-pair` is not yet CDK v2 compatible @@ -80,12 +80,19 @@ export class Ec2CdkStack extends pulumicdk.Stack { new cdk.CfnOutput(this, 'ssh command', { value: 'ssh -i cdk-key.pem -o IdentitiesOnly=yes ec2-user@' + ec2Instance.instancePublicIp, }); + } +} - this.synth(); +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App) => { + new Ec2CdkStack(scope, 'teststack'); + }); } } -const stack = new Ec2CdkStack('teststack'); -export const ipAddress = stack.outputs['IP Address']; -export const keyCommand = stack.outputs['Download Key Command']; -export const sshCommand = stack.outputs['sshCommand']; +const app = new MyApp(); + +export const ipAddress = app.outputs['IP Address']; +export const keyCommand = app.outputs['Download Key Command']; +export const sshCommand = app.outputs['sshCommand']; diff --git a/examples/ecscluster/Pulumi.yaml b/examples/ecscluster/Pulumi.yaml deleted file mode 100644 index 83e1803e..00000000 --- a/examples/ecscluster/Pulumi.yaml +++ /dev/null @@ -1,3 +0,0 @@ -name: pulumi-cdk-ecscluster -runtime: nodejs -description: ECS Cluster diff --git a/examples/ecscluster/index.ts b/examples/ecscluster/index.ts deleted file mode 100644 index fdc97b69..00000000 --- a/examples/ecscluster/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as ecs from 'aws-cdk-lib/aws-ecs'; -import * as pulumi from '@pulumi/pulumi'; -import * as pulumicdk from '@pulumi/cdk'; -import * as ec2 from 'aws-cdk-lib/aws-ec2'; -import * as pulumiaws from "@pulumi/aws-native"; - -class ECSClusterStack extends pulumicdk.Stack { - clusterArn: pulumi.Output; - - constructor(id: string, options?: pulumicdk.StackOptions) { - super(id, options); - - const vpc = ec2.Vpc.fromLookup(this, 'MyVpc', { - isDefault: true, - }) - const cluster = new ecs.Cluster(this, 'fargate-service-autoscaling', { vpc }); - - this.clusterArn = this.asOutput(cluster.clusterArn); - - this.synth(); - } -} - -export const clusterArn = pulumiaws.getAccountId().then(account => { - const stack = new ECSClusterStack('teststack', { - props: { - env: { - region: pulumiaws.config.region, - account: account.accountId, - } - } - }); - return stack.clusterArn; -}); diff --git a/examples/ecscluster/tsconfig.json b/examples/ecscluster/tsconfig.json deleted file mode 100644 index c7c2de61..00000000 --- a/examples/ecscluster/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "strict": true, - "outDir": "bin", - "target": "es2016", - "module": "commonjs", - "moduleResolution": "node", - "sourceMap": true, - "experimentalDecorators": true, - "pretty": true, - "noFallthroughCasesInSwitch": true, - "noImplicitReturns": true, - "forceConsistentCasingInFileNames": true - }, - "files": [ - "index.ts" - ] -} diff --git a/examples/examples_nodejs_test.go b/examples/examples_nodejs_test.go index 55c7a61f..e7589c48 100644 --- a/examples/examples_nodejs_test.go +++ b/examples/examples_nodejs_test.go @@ -34,15 +34,6 @@ func TestAppSvc(t *testing.T) { integration.ProgramTest(t, &test) } -func TestECSCluster(t *testing.T) { - test := getJSBaseOptions(t). - With(integration.ProgramTestOptions{ - Dir: filepath.Join(getCwd(t), "ecscluster"), - }) - - integration.ProgramTest(t, &test) -} - func TestAppRunner(t *testing.T) { test := getJSBaseOptions(t). With(integration.ProgramTestOptions{ @@ -110,6 +101,17 @@ func TestCloudFront(t *testing.T) { integration.ProgramTest(t, &test) } +func TestLookups(t *testing.T) { + test := getJSBaseOptions(t). + With(integration.ProgramTestOptions{ + Dir: filepath.Join(getCwd(t), "lookups"), + Config: map[string]string{ + "zoneName": "coolcompany.io", + }, + }) + + integration.ProgramTest(t, &test) +} func TestEventBridgeSNS(t *testing.T) { test := getJSBaseOptions(t). diff --git a/examples/fargate/index.ts b/examples/fargate/index.ts index bcbc98cf..372bc81a 100644 --- a/examples/fargate/index.ts +++ b/examples/fargate/index.ts @@ -10,8 +10,8 @@ import { CfnTargetGroup } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; class FargateStack extends pulumicdk.Stack { loadBalancerDNS: pulumi.Output; - constructor(id: string, options?: pulumicdk.StackOptions) { - super(id, options); + constructor(app: pulumicdk.App, id: string) { + super(app, id); // Create VPC and Fargate Cluster // NOTE: Limit AZs to avoid reaching resource quotas @@ -46,11 +46,17 @@ class FargateStack extends pulumicdk.Stack { }); this.loadBalancerDNS = this.asOutput(fargateService.loadBalancer.loadBalancerDnsName); + } +} - // Finalize the stack and deploy its resources. - this.synth(); +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new FargateStack(scope, 'fargatestack'); + return { loadBalancerURL: stack.loadBalancerDNS }; + }); } } -const stack = new FargateStack('fargatestack'); -export const loadBalancerURL = stack.loadBalancerDNS; +const app = new MyApp(); +export const loadBalancerURL = app.outputs['loadBalancerURL']; diff --git a/examples/lookups/Pulumi.yaml b/examples/lookups/Pulumi.yaml new file mode 100644 index 00000000..277f42ad --- /dev/null +++ b/examples/lookups/Pulumi.yaml @@ -0,0 +1,3 @@ +name: pulumi-lookups +runtime: nodejs +description: A minimal TypeScript Pulumi program diff --git a/examples/lookups/index.ts b/examples/lookups/index.ts new file mode 100644 index 00000000..fef0aed2 --- /dev/null +++ b/examples/lookups/index.ts @@ -0,0 +1,147 @@ +import * as pulumi from '@pulumi/pulumi'; +import * as aws from '@pulumi/aws'; +import * as pulumicdk from '@pulumi/cdk'; +import * as native from '@pulumi/aws-native'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import { + aws_elasticloadbalancingv2, + aws_elasticloadbalancingv2_targets, + aws_route53, + aws_route53_targets, + CfnOutput, +} from 'aws-cdk-lib'; + +const config = new pulumi.Config(); +const zoneName = config.require('zoneName'); + +export class Ec2CdkStack extends pulumicdk.Stack { + constructor(app: pulumicdk.App, id: string) { + super(app, id, { + props: { + env: { region: aws.config.region }, + }, + }); + + // Create new VPC with 2 Subnets + const vpc = new ec2.Vpc(this, 'VPC', { + natGateways: 0, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'asterisk', + subnetType: ec2.SubnetType.PUBLIC, + }, + ], + }); + + // use getAmiOutput to lookup the AMI instead of ec2.LookupMachineImage + const ami = aws.ec2.getAmiOutput({ + owners: ['amazon'], + mostRecent: true, + filters: [ + { + name: 'name', + values: ['al2023-ami-2023.*.*.*.*-arm64'], + }, + ], + }); + + const region = aws.config.requireRegion(); + const machineImage = ec2.MachineImage.genericLinux({ + [region]: pulumicdk.asString(ami.imageId), + }); + + const instance = new ec2.Instance(this, 'Instance', { + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MICRO), + machineImage, + }); + + const lb = new aws_elasticloadbalancingv2.ApplicationLoadBalancer(this, 'lb', { + vpc, + }); + + const listener = lb.addListener('http', { + protocol: aws_elasticloadbalancingv2.ApplicationProtocol.HTTP, + }); + + const tg = listener.addTargets('instance', { + protocol: aws_elasticloadbalancingv2.ApplicationProtocol.HTTP, + targets: [new aws_elasticloadbalancingv2_targets.InstanceTarget(instance)], + }); + // workaround for https://github.com/pulumi/pulumi-cdk/issues/62 + const cfnTargetGroup = tg.node.defaultChild as aws_elasticloadbalancingv2.CfnTargetGroup; + cfnTargetGroup.overrideLogicalId('LBListenerTG'); + + // use pulumi getZoneOutput and HostedZone.fromHostedZoneAttributes instead of HostedZone.fromLookup + const zone = aws.route53.getZoneOutput( + { + name: zoneName, + }, + { parent: app }, + ); + + const hostedZone = aws_route53.HostedZone.fromHostedZoneAttributes(this, 'hosted-zone', { + zoneName: pulumicdk.asString(zone.name), + hostedZoneId: pulumicdk.asString(zone.zoneId), + }); + + new aws_route53.AaaaRecord(this, 'record', { + zone: hostedZone, + target: aws_route53.RecordTarget.fromAlias(new aws_route53_targets.LoadBalancerTarget(lb)), + }); + + // use pulumi native resources side-by-side with CDK resources + new native.ssm.Parameter( + 'instance-param', + { + value: this.asOutput(instance.instanceId), + type: 'String', + }, + { parent: app }, + ); + new native.ssm.Parameter( + 'image-param', + { + value: this.asOutput(machineImage.getImage(this).imageId), + type: 'String', + }, + { parent: app }, + ); + + new CfnOutput(this, 'instanceId', { value: instance.instanceId }); + new CfnOutput(this, 'imageId', { value: machineImage.getImage(this).imageId }); + } +} + +const app = new pulumicdk.App( + 'app', + (scope: pulumicdk.App) => { + new Ec2CdkStack(scope, 'teststack'); + }, + { + remapCloudControlResource(logicalId, typeName, props, options) { + switch (typeName) { + case 'AWS::Route53::RecordSet': + return new aws.route53.Record(logicalId, { + zoneId: props.HostedZoneId, + aliases: [ + { + name: props.AliasTarget.DNSName, + zoneId: props.AliasTarget.HostedZoneId, + evaluateTargetHealth: props.AliasTarget.EvaluateTargetHealth ?? false, + }, + ], + name: props.Name, + type: props.Type, + records: props.ResourceRecords, + }); + default: + return undefined; + } + }, + }, +); + +export const imageId = app.outputs['imageId']; +export const instanceId = app.outputs['instanceId']; diff --git a/examples/lookups/package.json b/examples/lookups/package.json new file mode 100644 index 00000000..ae1d6b3f --- /dev/null +++ b/examples/lookups/package.json @@ -0,0 +1,12 @@ +{ + "name": "pulumi-aws-cdk", + "devDependencies": { + "@types/node": "^10.0.0" + }, + "dependencies": { + "@pulumi/aws-native": "^1.0.2", + "@pulumi/cdk": "^0.5.0", + "aws-cdk-lib": "2.149.0", + "constructs": "^10.0.111" + } +} diff --git a/examples/lookups/tsconfig.json b/examples/lookups/tsconfig.json new file mode 100644 index 00000000..2666e28e --- /dev/null +++ b/examples/lookups/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.ts" + ] +} \ No newline at end of file diff --git a/examples/s3-object-lambda/index.ts b/examples/s3-object-lambda/index.ts index 4754b8bb..0b661160 100644 --- a/examples/s3-object-lambda/index.ts +++ b/examples/s3-object-lambda/index.ts @@ -1,7 +1,19 @@ +import * as pulumi from '@pulumi/pulumi'; +import * as pulumicdk from '@pulumi/cdk'; +import type { AppOutputs } from '@pulumi/cdk'; import { S3ObjectLambdaStack } from './src/s3-object-lambda-stack'; -const s = new S3ObjectLambdaStack('stack'); -export const exampleBucketArn = s.exampleBucketArn; -export const objectLambdaArn = s.objectLambdaArn; -export const objectLambdaAccessPointArn = s.objectLambdaAccessPointArn; -export const objectLambdaAccessPointUrl = s.objectLambdaAccessPointUrl; +const app = new pulumicdk.App('app', (scope: pulumicdk.App): AppOutputs => { + const s = new S3ObjectLambdaStack(scope, 'stack'); + return { + exampleBucketArn: s.exampleBucketArn, + objectLambdaArn: s.objectLambdaArn, + objectLambdaAccessPointArn: s.objectLambdaAccessPointArn, + objectLambdaAccessPointUrl: s.objectLambdaAccessPointUrl, + }; +}); +export const exampleBucketArn = app.outputs['exampleBucketArn']; +export const objectLambdaArn = app.outputs['objectLambdaArn']; +export const objectLambdaAccessPointArn = app.outputs['objectLambdaAccessPointArn']; +export const objectLambdaAccessPointUrl = app.outputs['objectLambdaAccessPointUrl']; +export const bucketName = app.outputs['BucketName']; diff --git a/examples/s3-object-lambda/src/s3-object-lambda-stack.ts b/examples/s3-object-lambda/src/s3-object-lambda-stack.ts index 84398f3d..ccf009be 100644 --- a/examples/s3-object-lambda/src/s3-object-lambda-stack.ts +++ b/examples/s3-object-lambda/src/s3-object-lambda-stack.ts @@ -17,8 +17,8 @@ export class S3ObjectLambdaStack extends pulumicdk.Stack { objectLambdaAccessPointArn: pulumi.Output; objectLambdaAccessPointUrl: pulumi.Output; - constructor(id: string) { - super(id); + constructor(app: pulumicdk.App, id: string) { + super(app, id); const accessPoint = `arn:aws:s3:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:accesspoint/${S3_ACCESS_POINT_NAME}`; @@ -108,13 +108,12 @@ export class S3ObjectLambdaStack extends pulumicdk.Stack { }, }); + new cdk.CfnOutput(this, 'BucketName', { value: bucket.bucketName }); this.exampleBucketArn = this.asOutput(bucket.bucketArn); this.objectLambdaArn = this.asOutput(retrieveTransformedObjectLambda.functionArn); this.objectLambdaAccessPointArn = this.asOutput(objectLambdaAP.attrArn); this.objectLambdaAccessPointUrl = this.asOutput( `https://console.aws.amazon.com/s3/olap/${cdk.Aws.ACCOUNT_ID}/${OBJECT_LAMBDA_ACCESS_POINT_NAME}?region=${cdk.Aws.REGION}`, ); - - this.synth(); } } diff --git a/package.json b/package.json index 2752adec..ff2caa76 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "constructs": "^10.0.111" }, "dependencies": { + "@aws-cdk/cli-lib-alpha": "^2.161.1-alpha.0", "@types/glob": "^8.1.0", "archiver": "^7.0.1", "cdk-assets": "^2.154.8", diff --git a/src/converters/app-converter.ts b/src/converters/app-converter.ts index df42a0e4..05b15d26 100644 --- a/src/converters/app-converter.ts +++ b/src/converters/app-converter.ts @@ -1,8 +1,8 @@ import * as pulumi from '@pulumi/pulumi'; import { AssemblyManifestReader, StackManifest } from '../assembly'; import { ConstructInfo, GraphBuilder } from '../graph'; -import { StackComponentResource, lift, Mapping } from '../types'; import { ArtifactConverter } from './artifact-converter'; +import { lift, Mapping, AppComponent, PulumiStack } from '../types'; import { CdkConstruct, ResourceMapping } from '../interop'; import { debug } from '@pulumi/pulumi/log'; import { @@ -30,7 +30,7 @@ export class AppConverter { public readonly manifestReader: AssemblyManifestReader; - constructor(readonly host: StackComponentResource) { + constructor(readonly host: AppComponent) { this.manifestReader = AssemblyManifestReader.fromDirectory(host.assemblyDir); } @@ -90,6 +90,7 @@ export class StackConverter extends ArtifactConverter { readonly parameters = new Map(); readonly resources = new Map>(); readonly constructs = new Map(); + private readonly cdkStack: PulumiStack; private _stackResource?: CdkConstruct; @@ -100,8 +101,9 @@ export class StackConverter extends ArtifactConverter { return this._stackResource; } - constructor(private readonly host: StackComponentResource, readonly stack: StackManifest) { + constructor(host: AppComponent, readonly stack: StackManifest) { super(host); + this.cdkStack = host.stacks[stack.id]; } public convert(dependencies: Set) { @@ -114,18 +116,14 @@ export class StackConverter extends ArtifactConverter { for (const n of dependencyGraphNodes) { if (n.construct.id === this.stack.id) { - this._stackResource = new CdkConstruct( - `${this.stackComponent.name}/${n.construct.path}`, - n.construct.id, - { - parent: this.stackComponent.component, - // NOTE: Currently we make the stack depend on all the assets and then all resources - // have the parent as the stack. This means we deploy all assets before we deploy any resources - // we might be able better and have individual resources depend on individual assets, but CDK - // doesn't track asset dependencies at that level - dependsOn: this.stackDependsOn(dependencies), - }, - ); + this._stackResource = new CdkConstruct(`${this.app.name}/${n.construct.path}`, n.construct.id, { + parent: this.app, + // NOTE: Currently we make the stack depend on all the assets and then all resources + // have the parent as the stack. This means we deploy all assets before we deploy any resources + // we might be able better and have individual resources depend on individual assets, but CDK + // doesn't track asset dependencies at that level + dependsOn: this.stackDependsOn(dependencies), + }); this.constructs.set(n.construct, this._stackResource); continue; } @@ -155,18 +153,13 @@ export class StackConverter extends ArtifactConverter { // // Do something with the condition // } } else { - const r = new CdkConstruct(`${this.stackComponent.name}/${n.construct.path}`, n.construct.type, { + const r = new CdkConstruct(`${this.app.name}/${n.construct.path}`, n.construct.type, { parent, }); this.constructs.set(n.construct, r); } } - // Register the outputs as outputs of the component resource. - for (const [outputId, args] of Object.entries(this.stack.outputs ?? {})) { - this.stackComponent.registerOutput(outputId, this.processIntrinsics(args.Value)); - } - for (let i = dependencyGraphNodes.length - 1; i >= 0; i--) { const n = dependencyGraphNodes[i]; if (!n.resource) { @@ -209,7 +202,7 @@ export class StackConverter extends ArtifactConverter { return key; } - this.parameters.set(logicalId, parameterValue(this.stackComponent.component)); + this.parameters.set(logicalId, parameterValue(this.app)); } private mapResource( @@ -218,8 +211,8 @@ export class StackConverter extends ArtifactConverter { props: any, options: pulumi.ResourceOptions, ): ResourceMapping[] { - if (this.stackComponent.options?.remapCloudControlResource !== undefined) { - const res = this.stackComponent.options.remapCloudControlResource(logicalId, typeName, props, options); + if (this.app.appOptions?.remapCloudControlResource !== undefined) { + const res = this.app.appOptions.remapCloudControlResource(logicalId, typeName, props, options); if (res !== undefined) { debug(`remapped ${logicalId}`); return res; @@ -247,11 +240,11 @@ export class StackConverter extends ArtifactConverter { /** @internal */ asOutputValue(v: T): T { - const value = this.stackComponent.stack.resolve(v); + const value = this.cdkStack.resolve(v); return this.processIntrinsics(value) as T; } - private processIntrinsics(obj: any): any { + public processIntrinsics(obj: any): any { try { debug(`Processing intrinsics for ${JSON.stringify(obj)}`); } catch { @@ -370,15 +363,15 @@ export class StackConverter extends ArtifactConverter { switch (target) { case 'AWS::AccountId': - return getAccountId({ parent: this.stackComponent.component }).then((r) => r.accountId); + return getAccountId({ parent: this.app }).then((r) => r.accountId); case 'AWS::NoValue': return undefined; case 'AWS::Partition': - return getPartition({ parent: this.stackComponent.component }).then((p) => p.partition); + return getPartition({ parent: this.app }).then((p) => p.partition); case 'AWS::Region': - return getRegion({ parent: this.stackComponent.component }).then((r) => r.region); + return getRegion({ parent: this.app }).then((r) => r.region); case 'AWS::URLSuffix': - return getUrlSuffix({ parent: this.stackComponent.component }).then((r) => r.urlSuffix); + return getUrlSuffix({ parent: this.app }).then((r) => r.urlSuffix); case 'AWS::NotificationARNs': case 'AWS::StackId': case 'AWS::StackName': diff --git a/src/converters/artifact-converter.ts b/src/converters/artifact-converter.ts index 0725ed0d..0bc2bee6 100644 --- a/src/converters/artifact-converter.ts +++ b/src/converters/artifact-converter.ts @@ -6,7 +6,7 @@ import { StackComponentResource } from '../types'; * ArtifactConverter */ export abstract class ArtifactConverter { - constructor(protected readonly stackComponent: StackComponentResource) {} + constructor(protected readonly app: AppComponent) {} /** * Takes a string and resolves any CDK environment placeholders (e.g. accountId, region, partition) @@ -15,7 +15,7 @@ export abstract class ArtifactConverter { * @returns The string with the placeholders fully resolved */ protected resolvePlaceholders(s: string): Promise { - const host = this.stackComponent; + const host = this.app; return cx.EnvironmentPlaceholders.replaceAsync(s, { async region(): Promise { return getRegion({ parent: host.component }).then((r) => r.region); diff --git a/src/stack.ts b/src/stack.ts index 90fb7d79..99379c0b 100644 --- a/src/stack.ts +++ b/src/stack.ts @@ -14,23 +14,23 @@ import * as cdk from 'aws-cdk-lib'; import * as cx from 'aws-cdk-lib/cx-api'; import * as pulumi from '@pulumi/pulumi'; -import { debug } from '@pulumi/pulumi/log'; -import { StackComponentResource, StackOptions } from './types'; +import { AppComponent, AppOptions, PulumiStack } from './types'; import { AppConverter, StackConverter } from './converters/app-converter'; import { PulumiSynthesizer } from './synthesizer'; import { CdkConstruct } from './interop'; +import { AwsCdkCli, ICloudAssemblyDirectoryProducer } from '@aws-cdk/cli-lib-alpha'; +import { error } from '@pulumi/pulumi/log'; -/** - * StackComponentResource is the underlying pulumi ComponentResource for each pulumicdk.Stack - * This exists because pulumicdk.Stack needs to extend cdk.Stack, but we also want it to represent a - * pulumi ComponentResource so we create this `StackComponentResource` to hold the pulumi logic - */ -class StackComponent extends pulumi.ComponentResource implements StackComponentResource { - /** @internal */ - name: string; +export type AppOutputs = { [outputId: string]: pulumi.Output }; + +const STACK_SYMBOL = Symbol.for('@pulumi/cdk.Stack'); +export type create = (scope: App) => AppOutputs; + +export class App extends AppComponent implements ICloudAssemblyDirectoryProducer { + public name: string; /** @internal */ - converter: AppConverter; + public converter: Promise; /** * @internal @@ -42,44 +42,110 @@ class StackComponent extends pulumi.ComponentResource implements StackComponentR * @internal */ public assemblyDir: string; + private _app?: cdk.App; /** - * Any stack options that are supplied by the user * @internal */ - public options?: StackOptions; + public appOptions?: AppOptions; + + public get app(): cdk.App { + if (!this._app) { + throw new Error('cdk.App has not been created yet'); + } + return this._app!; + } /** - * @internal + * The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. + * Each CfnOutput defined in the AWS CDK Stack will populate a value in the outputs. */ - public dependencies: CdkConstruct[] = []; + public outputs: { [outputId: string]: pulumi.Output } = {}; + + private readonly createFunc: (scope: App) => AppOutputs | void; + private appProps?: cdk.AppProps; - constructor(public readonly stack: Stack) { - super('cdk:index:Stack', stack.node.id, {}, stack.options); - this.options = stack.options; - this.dependencies.push(stack.pulumiSynthesizer.stagingStack); + constructor(id: string, createFunc: (scope: App) => void | AppOutputs, props?: AppOptions) { + super(id, props); + this.appOptions = props; + this.createFunc = createFunc; - this.name = stack.node.id; + this.name = id; + this.appProps = props?.props; + this.converter = this.getData(); - const assembly = stack.app.synth(); - this.assemblyDir = assembly.directory; - debug(`ASSEMBLY_DIR: ${this.assemblyDir}`); + const outputs = this.converter.then((converter) => { + const stacks = Array.from(converter.stacks.values()); + return stacks.reduce( + (prev, curr) => { + const o: { [outputId: string]: pulumi.Output } = {}; + for (const [outputId, args] of Object.entries(curr.stack.outputs ?? {})) { + o[outputId] = curr.processIntrinsics(args.Value); + } + return { + ...prev, + ...o, + }; + }, + { ...this.outputs } as pulumi.Output<{ [outputId: string]: pulumi.Output }>, + ); + }); + this.outputs = pulumi.output(outputs); + this.registerOutputs(this.outputs); + } - debug(JSON.stringify(debugAssembly(assembly))); + protected async initialize(): Promise { + const cli = AwsCdkCli.fromCloudAssemblyDirectoryProducer(this); + try { + await cli.synth({ quiet: true, lookups: false }); + } catch (e: any) { + if (typeof e.message === 'string' && e.message.includes('Context lookups have been disabled')) { + const message = e.message as string; + const messageParts = message.split('Context lookups have been disabled. '); + const missingParts = messageParts[1].split('Missing context keys: '); + error( + 'Context lookups have been disabled. Make sure all necessary context is already in "cdk.context.json". \n' + + 'Missing context keys: ' + + missingParts[1], + this, + ); + } else { + error(e.message, this); + } + } - this.converter = new AppConverter(this); - this.converter.convert(); + const converter = new AppConverter(this); + converter.convert(); - this.registerOutputs(stack.outputs); - this.component = this; + return converter; } - /** @internal */ - registerOutput(outputId: string, output: any) { - this.stack.outputs[outputId] = pulumi.output(output); + async produce(context: Record): Promise { + const app = new cdk.App({ + ...(this.appProps ?? {}), + autoSynth: false, + analyticsReporting: false, + context, + }); + this._app = app; + this.assemblyDir = app.outdir; + const outputs = this.createFunc(this); + this.outputs = outputs ?? {}; + + app.node.children.forEach((child) => { + if (Stack.isPulumiStack(child)) { + this.stacks[child.artifactId] = child; + } + }); + + return app.synth().directory; } } +export interface StackOptions extends pulumi.ComponentResourceOptions { + props: cdk.StackProps; +} + /** * A Construct that represents an AWS CDK stack deployed with Pulumi. * @@ -87,31 +153,40 @@ class StackComponent extends pulumi.ComponentResource implements StackComponentR * all CDK resources have been defined in order to deploy the stack (usually, this is done as the last line of the * subclass's constructor). */ -export class Stack extends cdk.Stack { - // The URN of the underlying Pulumi component. - urn!: pulumi.Output; - resolveURN!: (urn: pulumi.Output) => void; - rejectURN!: (error: any) => void; - +export class Stack extends PulumiStack { /** - * The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. - * Each CfnOutput defined in the AWS CDK Stack will populate a value in the outputs. + * Return whether the given object is a Stack. + * + * We do attribute detection since we can't reliably use 'instanceof'. + * @internal */ - outputs: { [outputId: string]: pulumi.Output } = {}; + public static isPulumiStack(x: any): x is Stack { + return x !== null && typeof x === 'object' && STACK_SYMBOL in x; + } - /** @internal */ - app: cdk.App; + // // The URN of the underlying Pulumi component. + // urn!: pulumi.Output; + // resolveURN!: (urn: pulumi.Output) => void; + // rejectURN!: (error: any) => void; /** @internal */ - options: StackOptions | undefined; + app: cdk.App; // The stack's converter. This is used by asOutput in order to convert CDK values to Pulumi Outputs. This is a // Promise so users are able to call asOutput before they've called synth. Note that this _does_ make forgetting // to call synth a sharper edge: calling asOutput without calling synth will create outputs that never resolve // and the program will hang. converter!: Promise; - resolveConverter!: (converter: StackConverter) => void; - rejectConverter!: (error: any) => void; + + /** + * @internal + */ + public options?: StackOptions; + + /** @internal */ + public outdir: string; + + private pulumiApp: App; /** * @internal @@ -124,59 +199,17 @@ export class Stack extends cdk.Stack { * @param name The _unique_ name of the resource. * @param options A bag of options that control this resource's behavior. */ - constructor(name: string, options?: StackOptions) { - const appId = options?.appId ?? generateAppId(); + constructor(app: App, name: string, options?: StackOptions) { + super(app.app, name, options?.props); + Object.defineProperty(this, STACK_SYMBOL, { value: true }); - // TODO: allow the user to customize this https://github.com/pulumi/pulumi-cdk/issues/180 - const synthesizer = new PulumiSynthesizer({ - appId, - }); - const app = new cdk.App({ - defaultStackSynthesizer: synthesizer, - context: { - // Ask CDK to attach 'aws:asset:*' metadata to resources in generated stack templates. Although this - // metadata is not currently used, it may be useful in the future to map between assets and the - // resources with which they are associated. For example, the lambda.Function L2 construct attaches - // metadata for its Code asset (if any) to its generated CFN resource. - [cx.ASSET_RESOURCE_METADATA_ENABLED_CONTEXT]: true, - - // Ask CDK to embed 'aws:cdk:path' metadata in resources in generated stack templates. Although this - // metadata is not currently used, it provides an aditional mechanism by which we can map between - // constructs and the resources they emit in the CFN template. - [cx.PATH_METADATA_ENABLE_CONTEXT]: true, - }, - }); - - super(app, name, options?.props); - this.pulumiSynthesizer = synthesizer; - - this.app = app; + this.pulumiApp = app; this.options = options; - const urnPromise = new Promise((resolve, reject) => { - this.resolveURN = resolve; - this.rejectURN = reject; - }); - this.urn = pulumi.output(urnPromise); + this.outdir = app.assemblyDir; + this.app = app.app; - this.converter = new Promise((resolve, reject) => { - this.resolveConverter = resolve; - this.rejectConverter = reject; - }); - } - - /** - * Finalize the stack and deploy its resources. - */ - protected synth() { - try { - const component = new StackComponent(this); - this.resolveURN(component.urn); - this.resolveConverter(component.converter.stacks.get(this.artifactId)!); - } catch (e) { - this.rejectURN(e); - this.rejectConverter(e); - } + this.converter = this.pulumiApp.converter.then((converter) => converter.stacks.get(this.artifactId)!); } /** diff --git a/src/types.ts b/src/types.ts index 9785ef7c..1ff12951 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,55 @@ import * as pulumi from '@pulumi/pulumi'; import { Stack, StackProps } from 'aws-cdk-lib/core'; import { CdkConstruct, ResourceMapping } from './interop'; +import { Stack, StackProps, AppProps, App } from 'aws-cdk-lib/core'; +import { ResourceMapping } from './interop'; + +export abstract class PulumiStack extends Stack { + /** + * The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. + * Each CfnOutput defined in the AWS CDK Stack will populate a value in the outputs. + */ + public readonly outputs: { [outputId: string]: pulumi.Output } = {}; + + constructor(app: App, name: string, options?: StackProps) { + super(app, name, options); + } + /** @internal */ + registerOutput(outputId: string, output: any) { + this.outputs[outputId] = pulumi.output(output); + } +} +/** + * Options specific to the Stack component. + */ +export interface AppOptions extends pulumi.ComponentResourceOptions { + /** + * Specify the CDK Stack properties to asociate with the stack. + */ + props?: AppProps; + + /** + * Defines a mapping to override and/or provide an implementation for a CloudFormation resource + * type that is not (yet) implemented in the AWS Cloud Control API (and thus not yet available in + * the Pulumi AWS Native provider). Pulumi code can override this method to provide a custom mapping + * of CloudFormation elements and their properties into Pulumi CustomResources, commonly by using the + * AWS Classic provider to implement the missing resource. + * + * @param logicalId The logical ID of the resource being mapped. + * @param typeName The CloudFormation type name of the resource being mapped. + * @param props The bag of input properties to the CloudFormation resource being mapped. + * @param options The set of Pulumi ResourceOptions to apply to the resource being mapped. + * @returns An object containing one or more logical IDs mapped to Pulumi resources that must be + * created to implement the mapped CloudFormation resource, or else undefined if no mapping is + * implemented. + */ + remapCloudControlResource?( + logicalId: string, + typeName: string, + props: any, + options: pulumi.ResourceOptions, + ): ResourceMapping | undefined; +} /** * Options specific to the Stack component. */ @@ -59,12 +108,8 @@ export enum PulumiProvider { * This exists because pulumicdk.Stack needs to extend cdk.Stack, but we also want it to represent a * pulumi ComponentResource so we create this `StackComponentResource` to hold the pulumi logic */ -export interface StackComponentResource { - /** - * The name of the component resource - * @internal - */ - name: string; +export abstract class AppComponent extends pulumi.ComponentResource { + public abstract name: string; /** * The directory to which cdk synthesizes the CloudAssembly @@ -75,31 +120,16 @@ export interface StackComponentResource { /** * The CDK stack associated with the component resource */ - readonly stack: Stack; + public readonly stacks: { [artifactId: string]: PulumiStack } = {}; /** - * Any stack options that are supplied by the user * @internal */ - options?: StackOptions; + public abstract appOptions?: AppOptions; - /** - * The Resources that the component resource depends on - * This will typically be the staging resources - * - * @internal - */ - readonly dependencies: CdkConstruct[]; - - /** - * @internal - */ - readonly component: pulumi.ComponentResource; - - /** - * @internal - */ - registerOutput(outputId: string, outupt: any): void; + constructor(id: string, options?: AppOptions) { + super('cdk:index:App', id, options?.props, options); + } } export type Mapping = { diff --git a/tests/assembly/manifest.test.ts b/tests/assembly/manifest.test.ts index a935ee4b..dd500d05 100644 --- a/tests/assembly/manifest.test.ts +++ b/tests/assembly/manifest.test.ts @@ -10,7 +10,13 @@ describe('cloud assembly manifest reader', () => { beforeEach(() => { mockfs({ // Recursively loads all node_modules - node_modules: mockfs.load(path.resolve(__dirname, '../../node_modules')), + node_modules: { + 'aws-cdk-lib': mockfs.load(path.resolve(__dirname, '../../node_modules/aws-cdk-lib')), + '@pulumi': { + aws: mockfs.load(path.resolve(__dirname, '../../node_modules/@pulumi/aws')), + 'aws-native': mockfs.load(path.resolve(__dirname, '../../node_modules/@pulumi/aws-native')), + }, + }, [manifestAssets]: JSON.stringify({ version: '36.0.0', files: { @@ -141,7 +147,7 @@ describe('cloud assembly manifest reader', () => { 'test-stack/MyFunction1/Resource': 'MyFunction12A744C2E', 'test-stack/MyFunction1/ServiceRole/Resource': 'MyFunction1ServiceRole9852B06B', }, - outputs: undefined, + outputs: {}, parameters: undefined, resources: { MyFunction12A744C2E: { Properties: {}, Type: 'AWS::Lambda::Function' }, diff --git a/tests/basic.test.ts b/tests/basic.test.ts index 3c788a02..bbb1cc4e 100644 --- a/tests/basic.test.ts +++ b/tests/basic.test.ts @@ -13,50 +13,23 @@ // limitations under the License. import * as pulumi from '@pulumi/pulumi'; import * as s3 from 'aws-cdk-lib/aws-s3'; -import { Stack } from '../src/stack'; -import { Construct } from 'constructs'; +import { App, Stack } from '../src/stack'; import * as output from '../src/output'; import { promiseOf, setMocks } from './mocks'; import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import { Vpc } from 'aws-cdk-lib/aws-ec2'; import { aws_ssm } from 'aws-cdk-lib'; -function testStack(id: string, fn: (scope: Construct) => void): Stack { - class TestStack extends Stack { - constructor(id: string) { - super(id); - - fn(this); - - this.synth(); - } - } - - const s = new TestStack(id); - return s; -} - -beforeAll(() => { - setMocks(); -}); - describe('Basic tests', () => { test('Checking single resource registration', async () => { - const stack = testStack('test1', (adapter) => { - new s3.Bucket(adapter, 'MyFirstBucket', { versioned: true }); + const app = new App('testapp', (scope: App) => { + const s = new Stack(scope, 'teststack'); + new s3.Bucket(s, 'MyFirstBucket', { versioned: true }); }); - const urn = await promiseOf(stack.urn); - expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:Stack::test1'); + const urn = await promiseOf(app.urn); + expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:App::testapp'); }); - test('Supports Output', async () => { - const o = pulumi.output('the-bucket-name'); - const stack = testStack('test2', (adapter) => { - new s3.Bucket(adapter, 'MyFirstBucket', { bucketName: output.asString(o) }); - }); - const urn = await promiseOf(stack.urn); - expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:Stack::test2'); - }); test('LoadBalancer dnsName attribute does not throw', async () => { const stack = testStack('test3', (scope) => { const vpc = new Vpc(scope, 'vpc'); @@ -74,4 +47,13 @@ describe('Basic tests', () => { const urn = await promiseOf(stack.urn); expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:Stack::test3'); }); + test('Supports Output', async () => { + const o = pulumi.output('the-bucket-name'); + const app = new App('testapp', (scope: App) => { + const s = new Stack(scope, 'teststack'); + new s3.Bucket(s, 'MyFirstBucket', { bucketName: output.asString(o) }); + }); + const urn = await promiseOf(app.urn); + expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:App::testapp'); + }); }); diff --git a/tests/cdk-resource.test.ts b/tests/cdk-resource.test.ts index 6e4ad78f..fdfa6523 100644 --- a/tests/cdk-resource.test.ts +++ b/tests/cdk-resource.test.ts @@ -3,6 +3,12 @@ import { TableArgs } from '@pulumi/aws-native/dynamodb'; import { Key } from 'aws-cdk-lib/aws-kms'; import { setMocks, testStack } from './mocks'; import { MockResourceArgs } from '@pulumi/pulumi/runtime'; +import { App, Stack } from '../src/stack'; +import { Key } from 'aws-cdk-lib/aws-kms'; +import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import { Vpc } from 'aws-cdk-lib/aws-ec2'; +import { aws_ssm } from 'aws-cdk-lib'; +import { promiseOf, setMocks } from './mocks'; describe('CDK Construct tests', () => { // DynamoDB table was previously mapped to the `aws` provider @@ -12,9 +18,58 @@ describe('CDK Construct tests', () => { const resources: MockResourceArgs[] = []; setMocks(resources); - await testStack((scope) => { - const key = Key.fromKeyArn(scope, 'key', 'arn:aws:kms:us-west-2:123456789012:key/abcdefg'); - const table = new dynamodb.Table(scope, 'Table', { + setMocks((args) => { + if (args.type === 'aws-native:dynamodb:Table') { + expect(args.inputs).toEqual({ + keySchema: [ + { attributeName: 'pk', keyType: 'HASH' }, + { attributeName: 'sort', keyType: 'RANGE' }, + ], + sseSpecification: { + kmsMasterKeyId: 'arn:aws:kms:us-west-2:123456789012:key/abcdefg', + sseEnabled: true, + sseType: 'KMS', + }, + attributeDefinitions: [ + { attributeName: 'pk', attributeType: 'S' }, + { attributeName: 'sort', attributeType: 'S' }, + { attributeName: 'lsiSort', attributeType: 'S' }, + { attributeName: 'gsiKey', attributeType: 'S' }, + ], + provisionedThroughput: { + readCapacityUnits: 5, + writeCapacityUnits: 5, + }, + globalSecondaryIndexes: [ + { + provisionedThroughput: { + readCapacityUnits: 5, + writeCapacityUnits: 5, + }, + indexName: 'gsi', + keySchema: [{ attributeName: 'gsiKey', keyType: 'HASH' }], + projection: { + projectionType: 'ALL', + }, + }, + ], + localSecondaryIndexes: [ + { + projection: { projectionType: 'ALL' }, + keySchema: [ + { attributeName: 'pk', keyType: 'HASH' }, + { attributeName: 'lsiSort', keyType: 'RANGE' }, + ], + indexName: 'lsi', + }, + ], + } as TableArgs); + } + }); + const app = new App('testapp', (scope) => { + const stack = new Stack(scope, 'teststack'); + const key = Key.fromKeyArn(stack, 'key', 'arn:aws:kms:us-west-2:123456789012:key/abcdefg'); + const table = new dynamodb.Table(stack, 'Table', { encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED, encryptionKey: key, sortKey: { @@ -41,52 +96,27 @@ describe('CDK Construct tests', () => { }, }); }); + const urn = await promiseOf(app.urn); + expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:App::testapp'); + }); - const db = resources.find((res) => res.type === 'aws-native:dynamodb:Table'); - expect(db).toBeDefined(); - expect(db!.inputs).toEqual({ - keySchema: [ - { attributeName: 'pk', keyType: 'HASH' }, - { attributeName: 'sort', keyType: 'RANGE' }, - ], - sseSpecification: { - kmsMasterKeyId: 'arn:aws:kms:us-west-2:123456789012:key/abcdefg', - sseEnabled: true, - sseType: 'KMS', - }, - attributeDefinitions: [ - { attributeName: 'pk', attributeType: 'S' }, - { attributeName: 'sort', attributeType: 'S' }, - { attributeName: 'lsiSort', attributeType: 'S' }, - { attributeName: 'gsiKey', attributeType: 'S' }, - ], - provisionedThroughput: { - readCapacityUnits: 5, - writeCapacityUnits: 5, - }, - globalSecondaryIndexes: [ - { - provisionedThroughput: { - readCapacityUnits: 5, - writeCapacityUnits: 5, - }, - indexName: 'gsi', - keySchema: [{ attributeName: 'gsiKey', keyType: 'HASH' }], - projection: { - projectionType: 'ALL', - }, - }, - ], - localSecondaryIndexes: [ - { - projection: { projectionType: 'ALL' }, - keySchema: [ - { attributeName: 'pk', keyType: 'HASH' }, - { attributeName: 'lsiSort', keyType: 'RANGE' }, - ], - indexName: 'lsi', - }, - ], - } as TableArgs); + test('LoadBalancer dnsName attribute does not throw', async () => { + setMocks((_args) => {}); + const app = new App('testapp', (scope) => { + const stack = new Stack(scope, 'teststack'); + const vpc = new Vpc(stack, 'vpc'); + const alb = new ApplicationLoadBalancer(stack, 'alb', { + vpc, + }); + + new aws_ssm.StringParameter(stack, 'param', { + // Referencing the `dnsName` attribute of the LoadBalancer resource. + // This tests that the reference is correctly mapped, otherwise this test + // throws an error + stringValue: alb.loadBalancerDnsName, + }); + }); + const urn = await promiseOf(app.urn); + expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:App::testapp'); }); }); diff --git a/tests/converters/app-converter.test.ts b/tests/converters/app-converter.test.ts index 23377f20..5a140086 100644 --- a/tests/converters/app-converter.test.ts +++ b/tests/converters/app-converter.test.ts @@ -1,28 +1,24 @@ import { AppConverter, StackConverter } from '../../src/converters/app-converter'; import { Stack } from 'aws-cdk-lib/core'; -import { StackComponentResource, StackOptions } from '../../src/types'; +import { AppComponent, AppOptions } from '../../src/types'; import * as path from 'path'; import * as mockfs from 'mock-fs'; import * as pulumi from '@pulumi/pulumi'; import { BucketPolicy } from '@pulumi/aws-native/s3'; import { createStackManifest } from '../utils'; import { promiseOf, setMocks } from '../mocks'; -import { CdkConstruct } from '../../src/interop'; -class MockStackComponent extends pulumi.ComponentResource implements StackComponentResource { +class MockStackComponent extends AppComponent { public readonly name = 'stack'; public readonly assemblyDir: string; component: pulumi.ComponentResource; public stack: Stack; - public options?: StackOptions | undefined; - public dependencies: CdkConstruct[] = []; + public appOptions?: AppOptions | undefined; constructor(dir: string) { - super('cdk:index:Stack', 'stack', {}, {}); + super('stack'); this.assemblyDir = dir; this.registerOutputs(); } - - registerOutput(outputId: string, output: any): void {} } beforeAll(() => { @@ -37,7 +33,13 @@ describe('App Converter', () => { beforeEach(() => { mockfs({ // Recursively loads all node_modules - node_modules: mockfs.load(path.resolve(__dirname, '../../node_modules')), + node_modules: { + 'aws-cdk-lib': mockfs.load(path.resolve(__dirname, '../../node_modules/aws-cdk-lib')), + '@pulumi': { + aws: mockfs.load(path.resolve(__dirname, '../../node_modules/@pulumi/aws')), + 'aws-native': mockfs.load(path.resolve(__dirname, '../../node_modules/@pulumi/aws-native')), + }, + }, [manifestAssets]: JSON.stringify({ version: '36.0.0', files: { diff --git a/tests/converters/artifact-converter.test.ts b/tests/converters/artifact-converter.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/tests/mocks.ts b/tests/mocks.ts index 9fdcd7bd..792ea2cd 100644 --- a/tests/mocks.ts +++ b/tests/mocks.ts @@ -1,36 +1,11 @@ import * as pulumi from '@pulumi/pulumi'; -import { Stack } from '../src/stack'; -import { Construct } from 'constructs'; -import { MockCallArgs, MockResourceArgs } from '@pulumi/pulumi/runtime'; +import { MockCallArgs, MockResourceArgs, setMockOptions } from '@pulumi/pulumi/runtime'; +import { MockMonitor } from '@pulumi/pulumi/runtime/mocks'; // Convert a pulumi.Output to a promise of the same type. export function promiseOf(output: pulumi.Output): Promise { return new Promise((resolve) => output.apply(resolve)); } -export async function testStack(fn: (scope: Construct) => void) { - class TestStack extends Stack { - constructor(id: string) { - super(id, { - props: { - env: { - region: 'us-east-1', - account: '12345678912', - }, - }, - }); - - fn(this); - - this.synth(); - } - } - - const s = new TestStack('teststack'); - const converter = await s.converter; - await Promise.all(Array.from(converter.constructs.values()).flatMap((v) => promiseOf(v.urn))); - await promiseOf(s.urn); - await promiseOf(s.pulumiSynthesizer.stagingStack.urn); -} export function setMocks(resources?: MockResourceArgs[]) { const mocks: pulumi.runtime.Mocks = { diff --git a/yarn.lock b/yarn.lock index 532d08d0..9fbaa233 100644 --- a/yarn.lock +++ b/yarn.lock @@ -30,6 +30,11 @@ resolved "https://registry.yarnpkg.com/@aws-cdk/aws-apprunner-alpha/-/aws-apprunner-alpha-2.20.0-alpha.0.tgz#66ae8b2795281bf46163872f450d9163cf4beb39" integrity sha512-Eno+FXxa7k0Irx9ssl0ML44rlBg2THo8WMqxO3dKZpAZeZbfd8s8T3/UjP1Fq22TCKn+psDJ+wiUAd9r/BI2ig== +"@aws-cdk/cli-lib-alpha@^2.161.1-alpha.0": + version "2.161.1-alpha.0" + resolved "https://registry.yarnpkg.com/@aws-cdk/cli-lib-alpha/-/cli-lib-alpha-2.161.1-alpha.0.tgz#f00f5190f7da2e8f62807c5a01fb629298c767f4" + integrity sha512-HCokBr85Msv0tXiKth/3ZJZaQLzMmydk3NNEEA9fD/tzBh1zUcnlsBQnclOBmd0uKMNSZQertrroJmZv3mBOeg== + "@aws-cdk/cloud-assembly-schema@^38.0.1": version "38.0.1" resolved "https://registry.yarnpkg.com/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-38.0.1.tgz#cdf4684ae8778459e039cd44082ea644a3504ca9" From 2395e3db4ffbcb4b6dd196f531bc2c2fd905943e Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:20:38 -0400 Subject: [PATCH 02/12] Fixing rebase conflicts --- src/converters/app-converter.ts | 14 +-- src/converters/artifact-converter.ts | 2 +- src/stack.ts | 100 +++++++++------ src/synthesizer.ts | 20 ++- src/types.ts | 82 ++++++------ tests/assembly/manifest.test.ts | 2 +- tests/basic.test.ts | 23 ++-- tests/cdk-resource.test.ts | 132 ++++++++------------ tests/converters/app-converter.test.ts | 14 ++- tests/converters/artifact-converter.test.ts | 0 tests/mocks.ts | 38 +++++- tests/synthesizer.test.ts | 8 +- 12 files changed, 227 insertions(+), 208 deletions(-) delete mode 100644 tests/converters/artifact-converter.test.ts diff --git a/src/converters/app-converter.ts b/src/converters/app-converter.ts index 05b15d26..2b66ab5b 100644 --- a/src/converters/app-converter.ts +++ b/src/converters/app-converter.ts @@ -117,7 +117,7 @@ export class StackConverter extends ArtifactConverter { for (const n of dependencyGraphNodes) { if (n.construct.id === this.stack.id) { this._stackResource = new CdkConstruct(`${this.app.name}/${n.construct.path}`, n.construct.id, { - parent: this.app, + parent: this.app.component, // NOTE: Currently we make the stack depend on all the assets and then all resources // have the parent as the stack. This means we deploy all assets before we deploy any resources // we might be able better and have individual resources depend on individual assets, but CDK @@ -170,7 +170,7 @@ export class StackConverter extends ArtifactConverter { private stackDependsOn(dependencies: Set): pulumi.Resource[] { const dependsOn: pulumi.Resource[] = []; - dependsOn.push(...this.host.dependencies); + dependsOn.push(...this.app.dependencies); for (const d of dependencies) { if (d instanceof StackConverter) { dependsOn.push(d.stackResource); @@ -202,7 +202,7 @@ export class StackConverter extends ArtifactConverter { return key; } - this.parameters.set(logicalId, parameterValue(this.app)); + this.parameters.set(logicalId, parameterValue(this.app.component)); } private mapResource( @@ -363,15 +363,15 @@ export class StackConverter extends ArtifactConverter { switch (target) { case 'AWS::AccountId': - return getAccountId({ parent: this.app }).then((r) => r.accountId); + return getAccountId({ parent: this.app.component }).then((r) => r.accountId); case 'AWS::NoValue': return undefined; case 'AWS::Partition': - return getPartition({ parent: this.app }).then((p) => p.partition); + return getPartition({ parent: this.app.component }).then((p) => p.partition); case 'AWS::Region': - return getRegion({ parent: this.app }).then((r) => r.region); + return getRegion({ parent: this.app.component }).then((r) => r.region); case 'AWS::URLSuffix': - return getUrlSuffix({ parent: this.app }).then((r) => r.urlSuffix); + return getUrlSuffix({ parent: this.app.component }).then((r) => r.urlSuffix); case 'AWS::NotificationARNs': case 'AWS::StackId': case 'AWS::StackName': diff --git a/src/converters/artifact-converter.ts b/src/converters/artifact-converter.ts index 0bc2bee6..1415f9e9 100644 --- a/src/converters/artifact-converter.ts +++ b/src/converters/artifact-converter.ts @@ -1,6 +1,6 @@ import * as cx from 'aws-cdk-lib/cx-api'; import { getAccountId, getPartition, getRegion } from '@pulumi/aws-native'; -import { StackComponentResource } from '../types'; +import { AppComponent } from '../types'; /** * ArtifactConverter diff --git a/src/stack.ts b/src/stack.ts index 99379c0b..34c47018 100644 --- a/src/stack.ts +++ b/src/stack.ts @@ -14,65 +14,64 @@ import * as cdk from 'aws-cdk-lib'; import * as cx from 'aws-cdk-lib/cx-api'; import * as pulumi from '@pulumi/pulumi'; -import { AppComponent, AppOptions, PulumiStack } from './types'; +import { AppComponent, AppOptions, AppResourceOptions, PulumiStack } from './types'; import { AppConverter, StackConverter } from './converters/app-converter'; -import { PulumiSynthesizer } from './synthesizer'; -import { CdkConstruct } from './interop'; +import { PulumiSynthesizer, PulumiSynthesizerBase } from './synthesizer'; import { AwsCdkCli, ICloudAssemblyDirectoryProducer } from '@aws-cdk/cli-lib-alpha'; import { error } from '@pulumi/pulumi/log'; +import { CdkConstruct } from './interop'; export type AppOutputs = { [outputId: string]: pulumi.Output }; const STACK_SYMBOL = Symbol.for('@pulumi/cdk.Stack'); -export type create = (scope: App) => AppOutputs; -export class App extends AppComponent implements ICloudAssemblyDirectoryProducer { - public name: string; +interface AppResource { + converter: AppConverter; +} - /** @internal */ - public converter: Promise; +export class App + extends pulumi.ComponentResource + implements ICloudAssemblyDirectoryProducer, AppComponent +{ + public readonly name: string; + public readonly component: pulumi.ComponentResource; + public readonly stacks: { [artifactId: string]: PulumiStack } = {}; /** - * @internal + * The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. + * Each CfnOutput defined in the AWS CDK Stack will populate a value in the outputs. */ - readonly component: pulumi.ComponentResource; + public outputs: { [outputId: string]: pulumi.Output } = {}; - /** - * The directory to which cdk synthesizes the CloudAssembly - * @internal - */ - public assemblyDir: string; - private _app?: cdk.App; + /** @internal */ + public converter: Promise; /** * @internal */ public appOptions?: AppOptions; - public get app(): cdk.App { - if (!this._app) { - throw new Error('cdk.App has not been created yet'); - } - return this._app!; - } - /** - * The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. - * Each CfnOutput defined in the AWS CDK Stack will populate a value in the outputs. + * The directory to which cdk synthesizes the CloudAssembly + * @internal */ - public outputs: { [outputId: string]: pulumi.Output } = {}; + public assemblyDir!: string; + readonly dependencies: CdkConstruct[] = []; private readonly createFunc: (scope: App) => AppOutputs | void; + private _app?: cdk.App; private appProps?: cdk.AppProps; - constructor(id: string, createFunc: (scope: App) => void | AppOutputs, props?: AppOptions) { - super(id, props); - this.appOptions = props; + constructor(id: string, createFunc: (scope: App) => void | AppOutputs, props?: AppResourceOptions) { + super('cdk:index:App', id, props?.appOptions, props); + this.appOptions = props?.appOptions; this.createFunc = createFunc; + this.component = this; this.name = id; - this.appProps = props?.props; - this.converter = this.getData(); + this.appProps = props?.appOptions?.props; + const data = this.getData(); + this.converter = data.then((d) => d.converter); const outputs = this.converter.then((converter) => { const stacks = Array.from(converter.stacks.values()); @@ -94,8 +93,21 @@ export class App extends AppComponent implements ICloudAssemblyDir this.registerOutputs(this.outputs); } - protected async initialize(): Promise { + public get app(): cdk.App { + if (!this._app) { + throw new Error('cdk.App has not been created yet'); + } + return this._app!; + } + + protected async initialize(props: { + name: string; + args?: AppOptions; + opts?: pulumi.ComponentResourceOptions; + }): Promise { const cli = AwsCdkCli.fromCloudAssemblyDirectoryProducer(this); + this.appProps = props.args?.props; + this.appOptions = props.args; try { await cli.synth({ quiet: true, lookups: false }); } catch (e: any) { @@ -110,25 +122,34 @@ export class App extends AppComponent implements ICloudAssemblyDir this, ); } else { - error(e.message, this); + error(e, this); } } const converter = new AppConverter(this); converter.convert(); - return converter; + return { + converter, + }; } async produce(context: Record): Promise { + const appId = this.appOptions?.appId ?? generateAppId(); + const synthesizer = this.appProps?.defaultStackSynthesizer ?? new PulumiSynthesizer({ appId, parent: this }); + + if (synthesizer instanceof PulumiSynthesizerBase) { + this.dependencies.push(synthesizer.stagingStack); + } + const app = new cdk.App({ ...(this.appProps ?? {}), autoSynth: false, analyticsReporting: false, context, + defaultStackSynthesizer: synthesizer, }); this._app = app; - this.assemblyDir = app.outdir; const outputs = this.createFunc(this); this.outputs = outputs ?? {}; @@ -138,7 +159,9 @@ export class App extends AppComponent implements ICloudAssemblyDir } }); - return app.synth().directory; + const dir = app.synth().directory; + this.assemblyDir = dir; + return dir; } } @@ -188,11 +211,6 @@ export class Stack extends PulumiStack { private pulumiApp: App; - /** - * @internal - */ - public readonly pulumiSynthesizer: PulumiSynthesizer; - /** * Create and register an AWS CDK stack deployed with Pulumi. * diff --git a/src/synthesizer.ts b/src/synthesizer.ts index 9a58b71c..f27a27e1 100644 --- a/src/synthesizer.ts +++ b/src/synthesizer.ts @@ -68,6 +68,17 @@ export interface PulumiSynthesizerOptions { * @default true */ readonly autoDeleteStagingAssets?: boolean; + + readonly parent?: pulumi.ComponentResource; +} + +export abstract class PulumiSynthesizerBase extends cdk.StackSynthesizer { + /** + * The Pulumi ComponentResource wrapper which contains all of the + * staging resources. This can be added to the `dependsOn` of the main + * stack to ensure the staging assets are created first + */ + public abstract readonly stagingStack: CdkConstruct; } /** @@ -83,7 +94,7 @@ export interface PulumiSynthesizerOptions { * @see Recommended reading https://github.com/aws/aws-cdk/wiki/Security-And-Safety-Dev-Guide#controlling-the-permissions-used-by-cdk-deployments * @see https://docs.aws.amazon.com/cdk/api/v2/docs/app-staging-synthesizer-alpha-readme.html */ -export class PulumiSynthesizer extends cdk.StackSynthesizer implements cdk.IReusableStackSynthesizer { +export class PulumiSynthesizer extends PulumiSynthesizerBase implements cdk.IReusableStackSynthesizer { /** * The Pulumi ComponentResource wrapper which contains all of the * staging resources. This can be added to the `dependsOn` of the main @@ -166,14 +177,13 @@ export class PulumiSynthesizer extends cdk.StackSynthesizer implements cdk.IReus this.autoDeleteStagingAssets = props.autoDeleteStagingAssets ?? true; this.appId = this.validateAppId(props.appId); - // TODO: inherit the provider from the app component https://github.com/pulumi/pulumi-cdk/issues/181 - const account = aws.getCallerIdentity().then((id) => id.accountId); + const account = aws.getCallerIdentity({}, { parent: props.parent }).then((id) => id.accountId); this.pulumiAccount = pulumi.output(account); - const region = aws.getRegion().then((r) => r.name); + const region = aws.getRegion({}, { parent: props.parent }).then((r) => r.name); this.pulumiRegion = pulumi.output(region); const id = `${stackPrefix}-${this.appId}`; // create a wrapper component resource that we can depend on - this.stagingStack = new CdkConstruct(id, 'StagingStack', {}); + this.stagingStack = new CdkConstruct(id, 'StagingStack', { parent: props.parent }); this.stagingStack.done(); } diff --git a/src/types.ts b/src/types.ts index 1ff12951..7d0c3bf0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,10 +1,18 @@ import * as pulumi from '@pulumi/pulumi'; -import { Stack, StackProps } from 'aws-cdk-lib/core'; -import { CdkConstruct, ResourceMapping } from './interop'; import { Stack, StackProps, AppProps, App } from 'aws-cdk-lib/core'; -import { ResourceMapping } from './interop'; +import { CdkConstruct, ResourceMapping } from './interop'; +const STACK_SYMBOL = Symbol.for('@pulumi/cdk.Stack'); export abstract class PulumiStack extends Stack { + /** + * Return whether the given object is a Stack. + * + * We do attribute detection since we can't reliably use 'instanceof'. + * @internal + */ + public static isPulumiStack(x: any): x is Stack { + return x !== null && typeof x === 'object' && STACK_SYMBOL in x; + } /** * The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. * Each CfnOutput defined in the AWS CDK Stack will populate a value in the outputs. @@ -13,51 +21,19 @@ export abstract class PulumiStack extends Stack { constructor(app: App, name: string, options?: StackProps) { super(app, name, options); + Object.defineProperty(this, STACK_SYMBOL, { value: true }); } /** @internal */ registerOutput(outputId: string, output: any) { this.outputs[outputId] = pulumi.output(output); } } -/** - * Options specific to the Stack component. - */ -export interface AppOptions extends pulumi.ComponentResourceOptions { - /** - * Specify the CDK Stack properties to asociate with the stack. - */ - props?: AppProps; - /** - * Defines a mapping to override and/or provide an implementation for a CloudFormation resource - * type that is not (yet) implemented in the AWS Cloud Control API (and thus not yet available in - * the Pulumi AWS Native provider). Pulumi code can override this method to provide a custom mapping - * of CloudFormation elements and their properties into Pulumi CustomResources, commonly by using the - * AWS Classic provider to implement the missing resource. - * - * @param logicalId The logical ID of the resource being mapped. - * @param typeName The CloudFormation type name of the resource being mapped. - * @param props The bag of input properties to the CloudFormation resource being mapped. - * @param options The set of Pulumi ResourceOptions to apply to the resource being mapped. - * @returns An object containing one or more logical IDs mapped to Pulumi resources that must be - * created to implement the mapped CloudFormation resource, or else undefined if no mapping is - * implemented. - */ - remapCloudControlResource?( - logicalId: string, - typeName: string, - props: any, - options: pulumi.ResourceOptions, - ): ResourceMapping | undefined; -} -/** - * Options specific to the Stack component. - */ -export interface StackOptions extends pulumi.ComponentResourceOptions { +export interface AppOptions { /** * Specify the CDK Stack properties to asociate with the stack. */ - props?: StackProps; + props?: AppProps; /** * A unique identifier for the application that the asset staging stack belongs to. @@ -94,6 +70,12 @@ export interface StackOptions extends pulumi.ComponentResourceOptions { options: pulumi.ResourceOptions, ): ResourceMapping[] | undefined; } +/** + * Options specific to the Stack component. + */ +export interface AppResourceOptions extends pulumi.ComponentResourceOptions { + appOptions?: AppOptions; +} /** * The pulumi provider to read the schema from @@ -108,9 +90,8 @@ export enum PulumiProvider { * This exists because pulumicdk.Stack needs to extend cdk.Stack, but we also want it to represent a * pulumi ComponentResource so we create this `StackComponentResource` to hold the pulumi logic */ -export abstract class AppComponent extends pulumi.ComponentResource { - public abstract name: string; - +export interface AppComponent { + readonly name: string; /** * The directory to which cdk synthesizes the CloudAssembly * @internal @@ -120,16 +101,25 @@ export abstract class AppComponent extends pulumi.ComponentResource /** * The CDK stack associated with the component resource */ - public readonly stacks: { [artifactId: string]: PulumiStack } = {}; + readonly stacks: { [artifactId: string]: PulumiStack }; /** * @internal */ - public abstract appOptions?: AppOptions; + readonly component: pulumi.ComponentResource; - constructor(id: string, options?: AppOptions) { - super('cdk:index:App', id, options?.props, options); - } + /** + * @internal + */ + appOptions?: AppOptions; + + /** + * The Resources that the component resource depends on + * This will typically be the staging resources + * + * @internal + */ + readonly dependencies: CdkConstruct[]; } export type Mapping = { diff --git a/tests/assembly/manifest.test.ts b/tests/assembly/manifest.test.ts index dd500d05..ccce3fd0 100644 --- a/tests/assembly/manifest.test.ts +++ b/tests/assembly/manifest.test.ts @@ -147,7 +147,7 @@ describe('cloud assembly manifest reader', () => { 'test-stack/MyFunction1/Resource': 'MyFunction12A744C2E', 'test-stack/MyFunction1/ServiceRole/Resource': 'MyFunction1ServiceRole9852B06B', }, - outputs: {}, + outputs: undefined, parameters: undefined, resources: { MyFunction12A744C2E: { Properties: {}, Type: 'AWS::Lambda::Function' }, diff --git a/tests/basic.test.ts b/tests/basic.test.ts index bbb1cc4e..1970347d 100644 --- a/tests/basic.test.ts +++ b/tests/basic.test.ts @@ -13,25 +13,23 @@ // limitations under the License. import * as pulumi from '@pulumi/pulumi'; import * as s3 from 'aws-cdk-lib/aws-s3'; -import { App, Stack } from '../src/stack'; import * as output from '../src/output'; -import { promiseOf, setMocks } from './mocks'; +import { setMocks, testApp } from './mocks'; import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import { Vpc } from 'aws-cdk-lib/aws-ec2'; import { aws_ssm } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; describe('Basic tests', () => { + setMocks(); test('Checking single resource registration', async () => { - const app = new App('testapp', (scope: App) => { - const s = new Stack(scope, 'teststack'); - new s3.Bucket(s, 'MyFirstBucket', { versioned: true }); + await testApp((scope: Construct) => { + new s3.Bucket(scope, 'MyFirstBucket', { versioned: true }); }); - const urn = await promiseOf(app.urn); - expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:App::testapp'); }); test('LoadBalancer dnsName attribute does not throw', async () => { - const stack = testStack('test3', (scope) => { + await testApp((scope: Construct) => { const vpc = new Vpc(scope, 'vpc'); const alb = new ApplicationLoadBalancer(scope, 'alb', { vpc, @@ -44,16 +42,11 @@ describe('Basic tests', () => { stringValue: alb.loadBalancerDnsName, }); }); - const urn = await promiseOf(stack.urn); - expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:Stack::test3'); }); test('Supports Output', async () => { const o = pulumi.output('the-bucket-name'); - const app = new App('testapp', (scope: App) => { - const s = new Stack(scope, 'teststack'); - new s3.Bucket(s, 'MyFirstBucket', { bucketName: output.asString(o) }); + await testApp((scope: Construct) => { + new s3.Bucket(scope, 'MyFirstBucket', { bucketName: output.asString(o) }); }); - const urn = await promiseOf(app.urn); - expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:App::testapp'); }); }); diff --git a/tests/cdk-resource.test.ts b/tests/cdk-resource.test.ts index fdfa6523..9eb520da 100644 --- a/tests/cdk-resource.test.ts +++ b/tests/cdk-resource.test.ts @@ -1,14 +1,9 @@ import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; import { TableArgs } from '@pulumi/aws-native/dynamodb'; import { Key } from 'aws-cdk-lib/aws-kms'; -import { setMocks, testStack } from './mocks'; +import { setMocks, testApp } from './mocks'; import { MockResourceArgs } from '@pulumi/pulumi/runtime'; -import { App, Stack } from '../src/stack'; -import { Key } from 'aws-cdk-lib/aws-kms'; -import { ApplicationLoadBalancer } from 'aws-cdk-lib/aws-elasticloadbalancingv2'; -import { Vpc } from 'aws-cdk-lib/aws-ec2'; -import { aws_ssm } from 'aws-cdk-lib'; -import { promiseOf, setMocks } from './mocks'; +import { Construct } from 'constructs'; describe('CDK Construct tests', () => { // DynamoDB table was previously mapped to the `aws` provider @@ -18,58 +13,9 @@ describe('CDK Construct tests', () => { const resources: MockResourceArgs[] = []; setMocks(resources); - setMocks((args) => { - if (args.type === 'aws-native:dynamodb:Table') { - expect(args.inputs).toEqual({ - keySchema: [ - { attributeName: 'pk', keyType: 'HASH' }, - { attributeName: 'sort', keyType: 'RANGE' }, - ], - sseSpecification: { - kmsMasterKeyId: 'arn:aws:kms:us-west-2:123456789012:key/abcdefg', - sseEnabled: true, - sseType: 'KMS', - }, - attributeDefinitions: [ - { attributeName: 'pk', attributeType: 'S' }, - { attributeName: 'sort', attributeType: 'S' }, - { attributeName: 'lsiSort', attributeType: 'S' }, - { attributeName: 'gsiKey', attributeType: 'S' }, - ], - provisionedThroughput: { - readCapacityUnits: 5, - writeCapacityUnits: 5, - }, - globalSecondaryIndexes: [ - { - provisionedThroughput: { - readCapacityUnits: 5, - writeCapacityUnits: 5, - }, - indexName: 'gsi', - keySchema: [{ attributeName: 'gsiKey', keyType: 'HASH' }], - projection: { - projectionType: 'ALL', - }, - }, - ], - localSecondaryIndexes: [ - { - projection: { projectionType: 'ALL' }, - keySchema: [ - { attributeName: 'pk', keyType: 'HASH' }, - { attributeName: 'lsiSort', keyType: 'RANGE' }, - ], - indexName: 'lsi', - }, - ], - } as TableArgs); - } - }); - const app = new App('testapp', (scope) => { - const stack = new Stack(scope, 'teststack'); - const key = Key.fromKeyArn(stack, 'key', 'arn:aws:kms:us-west-2:123456789012:key/abcdefg'); - const table = new dynamodb.Table(stack, 'Table', { + await testApp((scope: Construct) => { + const key = Key.fromKeyArn(scope, 'key', 'arn:aws:kms:us-west-2:123456789012:key/abcdefg'); + const table = new dynamodb.Table(scope, 'Table', { encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED, encryptionKey: key, sortKey: { @@ -96,27 +42,51 @@ describe('CDK Construct tests', () => { }, }); }); - const urn = await promiseOf(app.urn); - expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:App::testapp'); - }); - - test('LoadBalancer dnsName attribute does not throw', async () => { - setMocks((_args) => {}); - const app = new App('testapp', (scope) => { - const stack = new Stack(scope, 'teststack'); - const vpc = new Vpc(stack, 'vpc'); - const alb = new ApplicationLoadBalancer(stack, 'alb', { - vpc, - }); - - new aws_ssm.StringParameter(stack, 'param', { - // Referencing the `dnsName` attribute of the LoadBalancer resource. - // This tests that the reference is correctly mapped, otherwise this test - // throws an error - stringValue: alb.loadBalancerDnsName, - }); - }); - const urn = await promiseOf(app.urn); - expect(urn).toEqual('urn:pulumi:stack::project::cdk:index:App::testapp'); + const db = resources.find((res) => res.type === 'aws-native:dynamodb:Table'); + expect(db).toBeDefined(); + expect(db!.inputs).toEqual({ + keySchema: [ + { attributeName: 'pk', keyType: 'HASH' }, + { attributeName: 'sort', keyType: 'RANGE' }, + ], + sseSpecification: { + kmsMasterKeyId: 'arn:aws:kms:us-west-2:123456789012:key/abcdefg', + sseEnabled: true, + sseType: 'KMS', + }, + attributeDefinitions: [ + { attributeName: 'pk', attributeType: 'S' }, + { attributeName: 'sort', attributeType: 'S' }, + { attributeName: 'lsiSort', attributeType: 'S' }, + { attributeName: 'gsiKey', attributeType: 'S' }, + ], + provisionedThroughput: { + readCapacityUnits: 5, + writeCapacityUnits: 5, + }, + globalSecondaryIndexes: [ + { + provisionedThroughput: { + readCapacityUnits: 5, + writeCapacityUnits: 5, + }, + indexName: 'gsi', + keySchema: [{ attributeName: 'gsiKey', keyType: 'HASH' }], + projection: { + projectionType: 'ALL', + }, + }, + ], + localSecondaryIndexes: [ + { + projection: { projectionType: 'ALL' }, + keySchema: [ + { attributeName: 'pk', keyType: 'HASH' }, + { attributeName: 'lsiSort', keyType: 'RANGE' }, + ], + indexName: 'lsi', + }, + ], + } as TableArgs); }); }); diff --git a/tests/converters/app-converter.test.ts b/tests/converters/app-converter.test.ts index 5a140086..e76c8bbf 100644 --- a/tests/converters/app-converter.test.ts +++ b/tests/converters/app-converter.test.ts @@ -1,21 +1,25 @@ import { AppConverter, StackConverter } from '../../src/converters/app-converter'; import { Stack } from 'aws-cdk-lib/core'; -import { AppComponent, AppOptions } from '../../src/types'; +import { AppComponent, AppOptions, PulumiStack } from '../../src/types'; import * as path from 'path'; import * as mockfs from 'mock-fs'; import * as pulumi from '@pulumi/pulumi'; import { BucketPolicy } from '@pulumi/aws-native/s3'; import { createStackManifest } from '../utils'; import { promiseOf, setMocks } from '../mocks'; +import { CdkConstruct } from '../../src/interop'; -class MockStackComponent extends AppComponent { +class MockAppComponent extends pulumi.ComponentResource implements AppComponent { public readonly name = 'stack'; public readonly assemblyDir: string; + stacks: { [artifactId: string]: PulumiStack } = {}; + dependencies: CdkConstruct[] = []; + component: pulumi.ComponentResource; public stack: Stack; public appOptions?: AppOptions | undefined; constructor(dir: string) { - super('stack'); + super('cdk:index:App', 'stack'); this.assemblyDir = dir; this.registerOutputs(); } @@ -178,7 +182,7 @@ describe('App Converter', () => { mockfs.restore(); }); test('can convert', async () => { - const mockStackComponent = new MockStackComponent('/tmp/foo/bar/does/not/exist'); + const mockStackComponent = new MockAppComponent('/tmp/foo/bar/does/not/exist'); const converter = new AppConverter(mockStackComponent); converter.convert(); const stacks = Array.from(converter.stacks.values()); @@ -246,7 +250,7 @@ describe('App Converter', () => { ])( 'intrinsics %s', async (_name, stackManifest, expected) => { - const mockStackComponent = new MockStackComponent('/tmp/foo/bar/does/not/exist'); + const mockStackComponent = new MockAppComponent('/tmp/foo/bar/does/not/exist'); const converter = new StackConverter(mockStackComponent, stackManifest); converter.convert(new Set()); const promises = Array.from(converter.resources.values()).flatMap((res) => promiseOf(res.resource.urn)); diff --git a/tests/converters/artifact-converter.test.ts b/tests/converters/artifact-converter.test.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/mocks.ts b/tests/mocks.ts index 792ea2cd..50048113 100644 --- a/tests/mocks.ts +++ b/tests/mocks.ts @@ -1,12 +1,46 @@ import * as pulumi from '@pulumi/pulumi'; -import { MockCallArgs, MockResourceArgs, setMockOptions } from '@pulumi/pulumi/runtime'; -import { MockMonitor } from '@pulumi/pulumi/runtime/mocks'; +import { MockCallArgs, MockResourceArgs } from '@pulumi/pulumi/runtime'; +import { Construct } from 'constructs'; +import { App, Stack } from '../src/stack'; // Convert a pulumi.Output to a promise of the same type. export function promiseOf(output: pulumi.Output): Promise { return new Promise((resolve) => output.apply(resolve)); } +export async function testApp(fn: (scope: Construct) => void) { + class TestStack extends Stack { + constructor(app: App, id: string) { + super(app, id, { + props: { + env: { + region: 'us-east-1', + account: '12345678912', + }, + }, + }); + + fn(this); + } + + get availabilityZones(): string[] { + return ['us-east-1a', 'us-east-1b']; + } + } + + const app = new App('testapp', (scope: App) => { + new TestStack(scope, 'teststack'); + }); + const converter = await app.converter; + await Promise.all( + Array.from(converter.stacks.values()).flatMap((stackConverter) => { + return Array.from(stackConverter.constructs.values()).flatMap((v) => promiseOf(v.urn)); + }), + ); + await promiseOf(app.urn); + await Promise.all(app.dependencies.flatMap((d) => promiseOf(d.urn))); +} + export function setMocks(resources?: MockResourceArgs[]) { const mocks: pulumi.runtime.Mocks = { call: (args: MockCallArgs): { [id: string]: any } => { diff --git a/tests/synthesizer.test.ts b/tests/synthesizer.test.ts index b9082868..4ef2d54c 100644 --- a/tests/synthesizer.test.ts +++ b/tests/synthesizer.test.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import { Asset } from 'aws-cdk-lib/aws-s3-assets'; -import { setMocks, testStack } from './mocks'; +import { setMocks, testApp } from './mocks'; import { MockResourceArgs } from '@pulumi/pulumi/runtime'; import { CfnBucket } from 'aws-cdk-lib/aws-s3'; @@ -9,7 +9,7 @@ describe('Synthesizer', () => { const resources: MockResourceArgs[] = []; setMocks(resources); - await testStack((scope) => { + await testApp((scope) => { new CfnBucket(scope, 'Bucket'); }); expect(resources).toEqual([ @@ -28,7 +28,7 @@ describe('Synthesizer', () => { const resources: MockResourceArgs[] = []; setMocks(resources); - await testStack((scope) => { + await testApp((scope) => { new CfnBucket(scope, 'Bucket'); new Asset(scope, 'asset', { path: path.join(__dirname, 'synthesizer.test.ts'), @@ -74,7 +74,7 @@ describe('Synthesizer', () => { const resources: MockResourceArgs[] = []; setMocks(resources); - await testStack((scope) => { + await testApp((scope) => { new CfnBucket(scope, 'Bucket'); new Asset(scope, 'deploy-time-asset', { deployTime: true, From 1b78a9edd2609795c937dbea3f7a44888ec5266f Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 29 Oct 2024 08:58:54 -0400 Subject: [PATCH 03/12] fixing some stuff --- src/index.ts | 4 +++- src/synthesizer.ts | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2fa2557c..b3846dbf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,4 +17,6 @@ export * from './output'; import * as interop from './interop'; export { interop }; -export { StackOptions } from './types'; +export { AppResourceOptions, AppOptions } from './types'; +import * as synthesizer from './synthesizer'; +export { synthesizer }; diff --git a/src/synthesizer.ts b/src/synthesizer.ts index f27a27e1..56479cd7 100644 --- a/src/synthesizer.ts +++ b/src/synthesizer.ts @@ -69,9 +69,16 @@ export interface PulumiSynthesizerOptions { */ readonly autoDeleteStagingAssets?: boolean; - readonly parent?: pulumi.ComponentResource; + /** + * The parent resource for any Pulumi resources created by the Synthesizer + */ + readonly parent?: pulumi.Resource; } +/** + * Base Synthesizer class. If you want to implement your own Pulumi Synthesizer which + * creates Pulumi resources then you should extend this class. + */ export abstract class PulumiSynthesizerBase extends cdk.StackSynthesizer { /** * The Pulumi ComponentResource wrapper which contains all of the @@ -436,7 +443,7 @@ export class PulumiSynthesizer extends PulumiSynthesizerBase implements cdk.IReu * * This replaces the ! assertions we would need everywhere otherwise. */ -export function assertBound(x: A | undefined): asserts x is NonNullable { +function assertBound(x: A | undefined): asserts x is NonNullable { if (x === null && x === undefined) { throw new Error('You must call bindStack() first'); } From 24cd053c76f2ef5fa6098285eb89ed4a1ee04054 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 29 Oct 2024 09:50:58 -0400 Subject: [PATCH 04/12] fixing build issues --- examples/cloudfront-lambda-urls/index.ts | 23 +++++++----- examples/eventbridge-sns/index.ts | 15 +++++--- examples/lookups/index.ts | 46 ++++++++++++++---------- 3 files changed, 52 insertions(+), 32 deletions(-) diff --git a/examples/cloudfront-lambda-urls/index.ts b/examples/cloudfront-lambda-urls/index.ts index cdee38e2..cef39243 100644 --- a/examples/cloudfront-lambda-urls/index.ts +++ b/examples/cloudfront-lambda-urls/index.ts @@ -1,15 +1,13 @@ import * as pulumi from '@pulumi/pulumi'; import * as pulumicdk from '@pulumi/cdk'; -import { Code, FunctionUrlAuthType, Runtime } from 'aws-cdk-lib/aws-lambda'; +import { FunctionUrlAuthType, Runtime } from 'aws-cdk-lib/aws-lambda'; import { Distribution, - experimental, Function, FunctionCode, FunctionEventType, FunctionRuntime, KeyValueStore, - LambdaEdgeEventType, } from 'aws-cdk-lib/aws-cloudfront'; import { FunctionUrlOrigin, S3Origin } from 'aws-cdk-lib/aws-cloudfront-origins'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; @@ -17,8 +15,8 @@ import { Bucket } from 'aws-cdk-lib/aws-s3'; class CloudFrontAppStack extends pulumicdk.Stack { public cloudFrontUrl: pulumi.Output; - constructor(id: string) { - super(id); + constructor(scope: pulumicdk.App, id: string) { + super(scope, id); const handler = new NodejsFunction(this, 'handler', { runtime: Runtime.NODEJS_LATEST, @@ -52,10 +50,17 @@ class CloudFrontAppStack extends pulumicdk.Stack { new KeyValueStore(this, 'KVStore'); this.cloudFrontUrl = this.asOutput(distro.distributionDomainName); - - this.synth(); } } -const stack = new CloudFrontAppStack('cloudfront-app'); -export const url = pulumi.interpolate`https://${stack.cloudFrontUrl}`; +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new CloudFrontAppStack(scope, 'cloudfront-app'); + return { url: stack.cloudFrontUrl }; + }); + } +} +const app = new MyApp(); +const output = app.outputs['url']; +export const url = pulumi.interpolate`https://${output}`; diff --git a/examples/eventbridge-sns/index.ts b/examples/eventbridge-sns/index.ts index 511056b9..41a70080 100644 --- a/examples/eventbridge-sns/index.ts +++ b/examples/eventbridge-sns/index.ts @@ -10,8 +10,8 @@ import { } from 'aws-cdk-lib'; class EventBridgeSnsStack extends pulumicdk.Stack { - constructor(id: string) { - super(id); + constructor(scope: pulumicdk.App, id: string) { + super(scope, id); const eventBus = new aws_events.EventBus(this, 'Bus'); const handler = new aws_lambda_nodejs.NodejsFunction(this, 'handler', { @@ -72,8 +72,15 @@ class EventBridgeSnsStack extends pulumicdk.Stack { rawMessageDelivery: true, }), ); - this.synth(); } } -new EventBridgeSnsStack('eventbridge-sns-stack'); +class MyApp extends pulumicdk.App { + constructor() { + super('app', (scope: pulumicdk.App) => { + new EventBridgeSnsStack(scope, 'eventbridge-sns-stack'); + }); + } +} + +new MyApp(); diff --git a/examples/lookups/index.ts b/examples/lookups/index.ts index fef0aed2..7b819a9a 100644 --- a/examples/lookups/index.ts +++ b/examples/lookups/index.ts @@ -120,25 +120,33 @@ const app = new pulumicdk.App( new Ec2CdkStack(scope, 'teststack'); }, { - remapCloudControlResource(logicalId, typeName, props, options) { - switch (typeName) { - case 'AWS::Route53::RecordSet': - return new aws.route53.Record(logicalId, { - zoneId: props.HostedZoneId, - aliases: [ - { - name: props.AliasTarget.DNSName, - zoneId: props.AliasTarget.HostedZoneId, - evaluateTargetHealth: props.AliasTarget.EvaluateTargetHealth ?? false, - }, - ], - name: props.Name, - type: props.Type, - records: props.ResourceRecords, - }); - default: - return undefined; - } + appOptions: { + remapCloudControlResource(logicalId, typeName, props, options) { + switch (typeName) { + case 'AWS::Route53::RecordSet': + return [ + new aws.route53.Record( + logicalId, + { + zoneId: props.HostedZoneId, + aliases: [ + { + name: props.AliasTarget.DNSName, + zoneId: props.AliasTarget.HostedZoneId, + evaluateTargetHealth: props.AliasTarget.EvaluateTargetHealth ?? false, + }, + ], + name: props.Name, + type: props.Type, + records: props.ResourceRecords, + }, + options, + ), + ]; + default: + return undefined; + } + }, }, }, ); From 2f7814300293c13f8c246cc918bc6b6c427a3f65 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:02:42 -0400 Subject: [PATCH 05/12] fixing deps --- examples/lookups/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/lookups/package.json b/examples/lookups/package.json index ae1d6b3f..c94e85f3 100644 --- a/examples/lookups/package.json +++ b/examples/lookups/package.json @@ -5,6 +5,7 @@ }, "dependencies": { "@pulumi/aws-native": "^1.0.2", + "@pulumi/aws": "^6.0.0", "@pulumi/cdk": "^0.5.0", "aws-cdk-lib": "2.149.0", "constructs": "^10.0.111" From 9eec89892917a7b92a28678c7b2044176589052a Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:27:16 -0400 Subject: [PATCH 06/12] fixing lookup --- .github/workflows/run-acceptance-tests.yml | 4 ++-- examples/lookups/index.ts | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-acceptance-tests.yml b/.github/workflows/run-acceptance-tests.yml index c6d2bb71..45d6400a 100644 --- a/.github/workflows/run-acceptance-tests.yml +++ b/.github/workflows/run-acceptance-tests.yml @@ -44,8 +44,8 @@ jobs: test: name: acceptance-test concurrency: - group: acceptance-test-${{ matrix.index }} # TODO: concurrent tests across PRs can cause problems - cancel-in-progress: false + group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.index }} # TODO: concurrent tests across PRs can cause problems + cancel-in-progress: true runs-on: ubuntu-latest steps: - name: Checkout Repo diff --git a/examples/lookups/index.ts b/examples/lookups/index.ts index 7b819a9a..281df631 100644 --- a/examples/lookups/index.ts +++ b/examples/lookups/index.ts @@ -77,6 +77,9 @@ export class Ec2CdkStack extends pulumicdk.Stack { const zone = aws.route53.getZoneOutput( { name: zoneName, + tags: { + Purpose: 'Lookups', + }, }, { parent: app }, ); From 5b0862943f7096225f9e52e1acb290343a7b5398 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 29 Oct 2024 11:05:06 -0400 Subject: [PATCH 07/12] Add adr --- .gitignore | 1 + adr/assets/cdk_synth.png | Bin 0 -> 202023 bytes adr/cdk-cli-lib.md | 414 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 415 insertions(+) create mode 100644 adr/assets/cdk_synth.png create mode 100644 adr/cdk-cli-lib.md diff --git a/.gitignore b/.gitignore index 17be668a..233eb5e6 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,4 @@ test-reports/ lib/ **/cdk.out +**/cdk.context.json diff --git a/adr/assets/cdk_synth.png b/adr/assets/cdk_synth.png new file mode 100644 index 0000000000000000000000000000000000000000..f8b29ea6c1fd36c7bb0d9fe35e48b8bc1b615104 GIT binary patch literal 202023 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�yiFeLf7s+ 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

07kDq*c^51Up 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 literal 0 HcmV?d00001 diff --git a/adr/cdk-cli-lib.md b/adr/cdk-cli-lib.md new file mode 100644 index 00000000..7d0cb9ee --- /dev/null +++ b/adr/cdk-cli-lib.md @@ -0,0 +1,414 @@ +# CDK CLI Library + +Updating to use the +[cdk-cli-lib](https://docs.aws.amazon.com/cdk/api/v2/docs/cli-lib-alpha-readme.html#cloud-assembly-directory-producer) +in order to support CDK Context requires us to make some user-facing API +changes. + +## Context + +AWS CDK is split between the “framework” and the “cli”. In order to have all of +the features of CDK you need to support both. An example of this is how +[context](https://docs.aws.amazon.com/cdk/v2/guide/context.html) and lookups +work in CDK. When `cdk synth` is called via the CLI and there are lookups to be +performed it will roughly follow the following steps: + +1. CLI executes the framework synth (i.e. `node bin/app.js`) +2. Lookup method called in the “framework” (e.g. `Vpc.fromLookup()`) +3. The “framework” looks up the value in the `cdk.context.json` file. + 1. If the value does not exist then it registers the context as “missing” in + the Cloud Assembly. + 2. If it does exist then it uses that value and is done +4. Once the framework is done with `synth`, the CLI reads the Cloud Assembly + and looks for missing context. + 1. If there are no missing context then the CLI is done and it exits. + 2. If there is missing context then it continues. +5. For each missing context it executes the corresponding context lookup + function in the CLI to perform AWS SDK calls and gather the data +6. The data is stored in `cdk.context.json` +7. The CLI executes framework synth again + +![][./assets/cdk_synth.png] + + +### Currently supported CDK Context Providers + +CDK does not support very many resource lookups. This is the current list and +I’ve included whether it should be possible to use a Pulumi lookup instead. + +- AMIs (Yes) +- Availability Zones (Kind of. It’s not possible for the defining VPC AZs) +- Route53 Hosted Zones (Yes) +- KMS Keys (Yes) +- SSM Parameters +- Security Groups (Yes, mostly) +- Endpoint Service Availability Zones (Yes) +- VPCs (No) +- Load Balancers (No, requires VPC) + + +## Constraints + +1. We can use the + [cdk-cli-lib](https://docs.aws.amazon.com/cdk/api/v2/docs/cli-lib-alpha-readme.html#cloud-assembly-directory-producer) + to handle gathering context and handle the multiple passes of executing the + framework, but we have to create the `cdk.App` within an async method. + +```javascript +class MyProducer implements ICloudAssemblyDirectoryProducer { + async produce(context: Record) { + const app = new cdk.App({ context }); + const stack = new cdk.Stack(app); + return app.synth().directory; + } +} + +const cli = AwsCdkCli.fromCloudAssemblyDirectoryProducer(new MyProducer()); +await cli.synth(); +``` + +This library requires us to create the `cdk.App` within the `produce` method of +`ICloudAssemblyDirectoryProducer`. This is because the `App` and all constructs +within it must be constructed with the full `context` value. It is not possible +to add context after a construct has been constructed. + +2. Because the constructs can be called multiple times, any Pulumi resources + which are created inside a construct class will also be constructed multiple + times. This means we need a way of knowing when the final call happens and + it is safe to construct the Pulumi resources. + 1. One thing to note: CDK lookups are infrequently performed. Typically they + are performed once when they are added and then they are cached in the + `cdk.context.json` file. This means that the multiple pass execution will + happen infrequently. + + +## Decision + +### App with callback argument + +We will introduce a new `App` class which accepts a callback function as the +second argument. The user would have to create resources within this call back +function. The API that looks something like this for the end user. + +This would allow us to create the `App` inside a Pulumi `ComponentResource` and +then pass the created `App` to the `(scope: pulumicdk.App) => {}` function. + +```javascript +const app = new pulumicdk.App('app', (scope: pulumicdk.App) => { + new Ec2CdkStack(scope, 'teststack'); +}); + +// with app outputs +const app = new pulumicdk.App('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new Ec2CdkStack(scope, 'teststack'); + return { + instanceId: stack.instanceId, + }; +}); +export const instanceId = app.outputs['instanceId']; +``` + +### User retry + +In order to handle #2 above where resources will be called multiple times, we +will require the user to run the Pulumi command two times. Context +lookups are different from Pulumi data calls since they are meant to be called +one time when they are initially added and then from then on the data is cached +in `cdk.context.json` and the lookup doesn’t need to be performed again. The +user experience would look something like this: + +1. User adds a lookup `Vpc.fromLookup()` +2. `pulumi up` +3. Lookups are performed, stored in `cdk.context.json` and an error is thrown. +4. `pulumi up` succeeds from then on. + +While this option is not ideal, it will offer the best compromise which still +allows users to perform all CDK lookups. We will call it out in documentation +and recommend users to use Pulumi native lookups for most things and only fall +back to CDK lookups for `VPC` and `Load Balancers`. + +## Alternatives + +### Mock resource calls + +In order to handle #2 above where resources will be called multiple times, we +could handle it by somehow mocking the resource calls until we are done. I’m +not sure if this is even possible and would probably require some new features +in core. At a very high level it could look something like this. + +```javascript +export class App extends pulumi.ComponentResource { + async initialize() { + const cli = AwsCdkCli.fromCloudAssemblyDirectoryProducer(this); + // set mocks before we synth + pulumi.runtime.setMocks(); + // multiple passes will occur within this. Once we are done + // it will proceed past this. + await cli.synth(); + // restore + pulumi.runtime.resetMocks(); + // create resources 1 last time with all context available. + await cli.synth(); + } +} +``` + +### Disable context lookups (but still support static context) + +In this case we would simply disable context lookups. If the application needed +to perform lookups it would throw an error like the example below. + +We could offer partial support for simple lookups. The user could get the value +and populate `cdk.context.json` manually. Alternatively, for simple lookups +they could switch to using Pulumi data resources. The downside to this, since +it wouldn’t be possible to support all lookups, is that it would add a lot of +extra friction having to know what is/is not supported and how to do it the +Pulumi way. + +``` +Diagnostics: + pulumi:pulumi:Stack (pulumi-lookups-dev): + error: Context lookups have been disabled. Make sure all necessary context is already in "cdk.context.json". + Missing context keys: 'availability-zones:account=12345678910:region=us-east-2, ami:account=12345678910:filters.image-type.0=machine:filters.name.0=al2023-ami-2023.*.*.*.*-arm64:filters.state.0=available:region=us-east-2' +``` + +**Examples** + +**VPC Lookup** + +In order to import a VPC you need all of this information. The logic for VPC +lookup is pretty complicated and I’m not sure it would be a good idea to try +and replicate it in Pulumi. + +```javascript +aws_ec2.Vpc.fromVpcAttributes(this, 'Vpc', { + region: '', + vpcId: '', + availabilityZones: [], + vpcCidrBlock: '', + vpnGatewayId: '', + publicSubnetIds: [], + privateSubnetIds: [], + isolatedSubnetIds: [], + publicSubnetNames: [], + privateSubnetNames: [], + isolatedSubnetNames: [], + publicSubnetRouteTableIds: [], + privateSubnetRouteTableIds: [], + publicSubnetIpv4CidrBlocks: [], + isolatedSubnetRouteTableIds: [], + privateSubnetIpv4CidrBlocks: [], + isolatedSubnetIpv4CidrBlocks: [], +}); +``` + +**SecurityGroup With CDK Lookups** + +```javascript +aws_ec2.SecurityGroup.fromLookupByName(this, 'sg', 'sg-name', vpc); +``` + +**SecurityGroup Without CDK Lookups** + +```javascript +const sg = aws.ec2.getSecurityGroupOutput({ + vpcId: this.asOutput(vpc.vpcId), + name: 'sg-name', +}); + +aws_ec2.SecurityGroup.fromSecurityGroupId(this, 'sg', pulumicdk.asString(sg.id), { + // CDK fromLookup will figure this out for you + allowAllOutbound: false, +}); +``` + +**KMS Key With CDK Lookups** + +```javascript +const key = aws_kms.Key.fromLookup(this, 'key', { + aliasName: 'alias', +}); +``` + +**KMS Key Without CDK Lookups** + +```javascript +const alias = aws.kms.getAliasOutput({ + name: 'alias/somealias', +}); + +const key = aws_kms.Key.fromKeyArn(this, 'key', pulumicdk.asString(alias.targetKeyArn)) +``` + +**Route53 With CDK Lookups** + +```javascript +const hostedZone = aws_route53.HostedZone.fromLookup(this, 'hosted-zone', { + domainName: 'pulumi-demos.net', +}); + +new aws_route53.AaaaRecord(this, 'record', { + zone: hostedZone, + target: aws_route53.RecordTarget.fromAlias(new aws_route53_targets.LoadBalancerTarget(lb)), +}); + +``` + +**Route53 Without CDK Lookups** + +```javascript + +const zone = aws.route53.getZoneOutput({ + name: 'pulumi-demos.net', +}); +const hostedZone = aws_route53.HostedZone.fromHostedZoneAttributes(this, 'hosted-zone', { + zoneName: pulumicdk.asString(zone.name), + hostedZoneId: pulumicdk.asString(zone.zoneId), +}); + +new aws_route53.AaaaRecord(this, 'record', { + zone: hostedZone, + target: aws_route53.RecordTarget.fromAlias(new aws_route53_targets.LoadBalancerTarget(lb)), +}); +``` + +**AMI / AZs With CDK lookups** + +```javascript +export class Ec2CdkStack extends pulumicdk.Stack { + constructor(app: pulumicdk.App, id: string) { + super(app, id, { + props: { + // env must be specified to enable lookups + env: { region: process.env.AWS_REGION, account: process.env.AWS_ACCOUNT }, + }, + }); + + // Create new VPC with 2 Subnets + const vpc = new ec2.Vpc(this, 'VPC', { + natGateways: 0, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'asterisk', + subnetType: ec2.SubnetType.PUBLIC, + }, + ], + }); + // This performs the CDK lookup + const machineImage = new ec2.LookupMachineImage({ + name: 'al2023-ami-2023.*.*.*.*-arm64', + }); + + const instance = new ec2.Instance(this, 'Instance', { + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MICRO), + machineImage, + }); + } +} + +const app = new pulumicdk.App('app', (scope: pulumicdk.App) => { + new Ec2CdkStack(scope, 'teststack'); +}); +``` + +**AMI / AZs Without CDK lookups** + +```javascript +export class Ec2CdkStack extends pulumicdk.Stack { + constructor(app: pulumicdk.App, id: string) { + super(app, id, { + props: { + env: { region: process.env.AWS_REGION, account: process.env.AWS_ACCOUNT }, + }, + }); + + // Create new VPC with 2 Subnets + const vpc = new ec2.Vpc(this, 'VPC', { + natGateways: 0, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'asterisk', + subnetType: ec2.SubnetType.PUBLIC, + }, + ], + }); + // Use a pulumi lookup to get the AMI + const ami = aws.ec2.getAmiOutput({ + owners: ['amazon'], + mostRecent: true, + filters: [ + { + name: 'name', + values: ['al2023-ami-2023.*.*.*.*-arm64'], + }, + ], + }); + + const machineImage = ec2.MachineImage.genericLinux({ + 'us-east-2': pulumicdk.asString(ami.imageId), + }); + + const instance = new ec2.Instance(this, 'Instance', { + vpc, + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MICRO), + machineImage, + }); + } + + // Since the stack cannot lookup the availability zone you have to provide this method + get availabilityZones(): string[] { + return ['us-east-2a', 'us-east-2b']; + } +} + +const app = new pulumicdk.App('app', (scope: pulumicdk.App) => { + new Ec2CdkStack(scope, 'teststack'); +}); + + +``` + +### Do not support Context + +The other alternative would be to not change the API which would mean that we +do not support CDK Context. The user would have to supply any context directly +to the `pulumi-cdk.Stack` + +```javascript +new pulumicdk.Stack('Stack', { + context: { + // optionally provide lookup context data directly in the correct format + "availability-zones:account=12345678910:region=us-east-2": [ + "us-east-2a", + "us-east-2b", + "us-east-2c" + ], + // CDK feature flags + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": "true", + // Arbitrary user context + "my-context-key": "my-context-value", + } +}); +``` + +## Consequences + +Supporting CDK Context would have the following impact on users. + +1. Support for sourcing context values natively (from `cdk.json`, + `cdk.context.json`, environment variables, etc) + 1. There is a workaround where users could directly supply the context to + the `Stack` itself. This would only be viable for static context like + feature flags (example in + [appendix](https://docs.google.com/document/d/1TFO0RJ4CtynBW8p4vKapy8v4L2AjUxldmrjxS60Tx4o/edit#heading=h.b1s6g8uq0ou7) + ) +2. Allowing resource lookups + 1. Resource lookups are not used in any of the core CDK constructs, instead + they are something that the end user would use to get an object that + would be an input to a core construct. + 2. There is no data on how frequently these are used by end users, but they + are definitely used more frequently in migration cases since this allows + you to reference resources created elsewhere. From b6133473005f3c34644ed6c170099f38b67efcd7 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:00:30 -0400 Subject: [PATCH 08/12] cleaning up the api and updating README --- README.md | 141 +++++++++++++++++++------------- src/converters/app-converter.ts | 5 +- src/stack.ts | 120 +++++++++++++++------------ src/types.ts | 50 ++++------- 4 files changed, 168 insertions(+), 148 deletions(-) diff --git a/README.md b/README.md index c2af643e..8197195c 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,22 @@ # Pulumi CDK Adapter (preview) -The Pulumi CDK Adapter is a library that enables [Pulumi](https://github.com/pulumi/pulumi) programs to use [AWS CDK](https://github.com/aws/aws-cdk) constructs. +The Pulumi CDK Adapter is a library that enables +[Pulumi](https://github.com/pulumi/pulumi) programs to use [AWS +CDK](https://github.com/aws/aws-cdk) constructs. -The adapter allows writing AWS CDK code as part of an AWS CDK Stack inside a Pulumi program, and having the resulting AWS resources be deployed and managed via Pulumi. Outputs of resources defined in a Pulumi program can be passed into AWS CDK Constructs, and outputs from AWS CDK stacks can be used as inputs to other Pulumi resources. +The adapter allows writing AWS CDK code as part of an AWS CDK Stack inside a +Pulumi program, and having the resulting AWS resources be deployed and managed +via Pulumi. Outputs of resources defined in a Pulumi program can be passed +into AWS CDK Constructs, and outputs from AWS CDK stacks can be used as inputs +to other Pulumi resources. -> Note: Currently, the Pulumi CDK Adapter preview is available only for TypeScript/JavaScript users. +> Note: Currently, the Pulumi CDK Adapter preview is available only for +> TypeScript/JavaScript users. -For example, to construct an [AWS AppRunner `Service` resource](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-apprunner-alpha-readme.html) from within a Pulumi program, and export the resulting service's URL as as Pulumi Stack Output you write the following: +For example, to construct an [AWS AppRunner `Service` +resource](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-apprunner-alpha-readme.html) +from within a Pulumi program, and export the resulting service's URL as as +Pulumi Stack Output you write the following: ```ts import * as pulumi from '@pulumi/pulumi'; @@ -27,18 +37,21 @@ class AppRunnerStack extends pulumicdk.Stack { }); this.url = this.asOutput(service.serviceUrl); - - this.synth(); } } -const stack = new AppRunnerStack('teststack'); -export const url = stack.url; +const app = new pulumicdk.App('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + const stack = new AppRunnerStack('teststack'); + return { + url: stack.url, + }; +}); +export const url = app.outputs['url']; ``` And then deploy with `pulumi update`: -``` +```console > pulumi up Updating (dev) @@ -60,7 +73,7 @@ Resources: And curl the endpoint: -``` +```console > curl https://$(pulumi stack output url) ______ __ __ __ _ __ @@ -81,99 +94,111 @@ Try the workshop at https://apprunnerworkshop.com Read the docs at https://docs.aws.amazon.com/apprunner ``` -## Getting Started - -### Bootstrapping - -AWS CDK requires that your AWS account and target region are "bootstrapped" for use with CDK, so in order to use CDK on Pulumi, you'll need to do that first. We recommend using the latest bootstrap template (v2, as of this writing). [See the AWS documentation](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html) for details. (Note that resources deployed with Pulumi on CDK are deployed and managed with Pulumi, not CloudFormation; the bootstrapping step is required by CDK for additional runtime support.) - ## API -### `Stack` +### `App` -A Construct that represents an AWS CDK stack deployed with Pulumi. In order to deploy a CDK stack with Pulumi, it must derive from this class. The `synth` method must be called after all CDK resources have been defined in order to deploy the stack (usually, this is done as the last line of the subclass's constructor). +A Pulumi CDK App component. This is the entrypoint to your Pulumi CDK application. +In order to deploy a CDK application with Pulumi, you must start with this class. #### `constructor` -Create and register an AWS CDK stack deployed with Pulumi. +Create and register an AWS CDK app deployed with Pulumi. ```ts -constructor(name: string, options?: StackOptions) +constructor(id: string, createFunc: (scope: App) => void | AppOutputs, props?: AppResourceOptions) ``` Parameters: -* `name`: The _unique_ name of the resource. -* `options`: A bag of options that control this resource's behavior. - -### `urn` - -The URN of the underlying Pulumi component. +* `id`: The _unique_ name of the app +* `createFunc`: A callback function where all CDK Stacks must be created +* `options`: A bag of options that control the resource's behavior #### `outputs` -The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. Each `CfnOutput` defined in the AWS CDK Stack will populate a value in the outputs. +The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. +Each `CfnOutput` defined in the AWS CDK Stack will populate a value in the +outputs. ```ts outputs: { [outputId: string]: pulumi.Output } ``` -#### `synth` - -Finalize the stack and deploy its resources. - -```ts -protected synth() -``` - -#### `asOutput` - -Convert a CDK value to a Pulumi output. +#### `AppResourceOptions` -Parameters: -* `v`: A CDK value. +Options specific to the App Component ```ts -asOutput(v: T): pulumi.Output -``` - -### `StackOptions` - -Options specific to the Stack component. - -```ts -interface StackOptions +interface AppResourceOptions ``` #### `remapCloudControlResource` -This optional method can be implemented to define a mapping to override and/or provide an implementation for a CloudFormation resource type that is not (yet) implemented in the AWS Cloud Control API (and thus not yet available in the Pulumi AWS Native provider). Pulumi code can override this method to provide a custom mapping of CloudFormation elements and their properties into Pulumi CustomResources, commonly by using the AWS Classic provider to implement the missing resource. +This optional method can be implemented to define a mapping to override and/or +provide an implementation for a CloudFormation resource type that is not (yet) +implemented in the AWS Cloud Control API (and thus not yet available in the +Pulumi AWS Native provider). Pulumi code can override this method to provide a +custom mapping of CloudFormation elements and their properties into Pulumi +CustomResources, commonly by using the AWS Classic provider to implement the +missing resource. ```ts -remapCloudControlResource(element: CfnElement, logicalId: string, typeName: string, props: any, options: pulumi.ResourceOptions): { [key: string]: pulumi.CustomResource } | undefined +remapCloudControlResource(logicalId: string, typeName: string, props: any, options: pulumi.ResourceOptions): { [key: string]: pulumi.CustomResource } | undefined ``` Parameters: -* `element`: The full CloudFormation element object being mapped. * `logicalId`: The logical ID of the resource being mapped. * `typeName`: The CloudFormation type name of the resource being mapped. * `props`: The bag of input properties to the CloudFormation resource being mapped. * `options`: The set of Pulumi ResourceOptions to apply to the resource being mapped. -Returns an object containing one or more logical IDs mapped to Pulumi resources that must be created to implement the mapped CloudFormation resource, or else undefined if no mapping is implemented. +Returns an object containing one or more logical IDs mapped to Pulumi resources +that must be created to implement the mapped CloudFormation resource, or else +undefined if no mapping is implemented. + +#### `appId` + +This is a unique identifier for the application. It will be used in the names of the +staging resources created for the application. This `appId` should be unique across apps. + +### `Stack` + +A Construct that represents an AWS CDK stack deployed with Pulumi. In order to +deploy a CDK stack with Pulumi, it must derive from this class. -#### `create` +#### `constructor` Create and register an AWS CDK stack deployed with Pulumi. ```ts -create(name: string, ctor: typeof Stack, opts?: pulumi.CustomResourceOptions): StackComponent +constructor(app: App, name: string, options?: StackOptions) ``` Parameters: +* `app`: The Pulumi CDK App * `name`: The _unique_ name of the resource. -* `stack`: The CDK Stack subclass to create. -* `parent`: The Pulumi CDKStackComponent parent resource. -* `opts`: A bag of options that control this resource's behavior. +* `options`: A bag of options that control this resource's behavior. + + +#### `asOutput` + +Convert a CDK value to a Pulumi output. + +Parameters: +* `v`: A CDK value. + +```ts +asOutput(v: T): pulumi.Output +``` + +### `StackOptions` + +Options specific to the Stack component. + +```ts +interface StackOptions +``` + ### `asString` diff --git a/src/converters/app-converter.ts b/src/converters/app-converter.ts index 2b66ab5b..c426bdd8 100644 --- a/src/converters/app-converter.ts +++ b/src/converters/app-converter.ts @@ -1,8 +1,9 @@ +import * as cdk from 'aws-cdk-lib/core'; import * as pulumi from '@pulumi/pulumi'; import { AssemblyManifestReader, StackManifest } from '../assembly'; import { ConstructInfo, GraphBuilder } from '../graph'; import { ArtifactConverter } from './artifact-converter'; -import { lift, Mapping, AppComponent, PulumiStack } from '../types'; +import { lift, Mapping, AppComponent } from '../types'; import { CdkConstruct, ResourceMapping } from '../interop'; import { debug } from '@pulumi/pulumi/log'; import { @@ -90,7 +91,7 @@ export class StackConverter extends ArtifactConverter { readonly parameters = new Map(); readonly resources = new Map>(); readonly constructs = new Map(); - private readonly cdkStack: PulumiStack; + private readonly cdkStack: cdk.Stack; private _stackResource?: CdkConstruct; diff --git a/src/stack.ts b/src/stack.ts index 34c47018..6231cb6f 100644 --- a/src/stack.ts +++ b/src/stack.ts @@ -11,10 +11,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import * as cdk from 'aws-cdk-lib'; -import * as cx from 'aws-cdk-lib/cx-api'; +import * as cdk from 'aws-cdk-lib/core'; import * as pulumi from '@pulumi/pulumi'; -import { AppComponent, AppOptions, AppResourceOptions, PulumiStack } from './types'; +import { AppComponent, AppOptions, AppResourceOptions } from './types'; import { AppConverter, StackConverter } from './converters/app-converter'; import { PulumiSynthesizer, PulumiSynthesizerBase } from './synthesizer'; import { AwsCdkCli, ICloudAssemblyDirectoryProducer } from '@aws-cdk/cli-lib-alpha'; @@ -29,13 +28,30 @@ interface AppResource { converter: AppConverter; } +/** + * A Pulumi CDK App component. This is the entrypoint to your Pulumi CDK application. + * The second argument is a callback function where all CDK resources must be created. + * + * @example + * import * as s3 from 'aws-cdk-lib/aws-s3'; + * import * as pulumicdk from '@pulumi/cdk'; + * + * const app = new pulumicdk.App('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => { + * // All resources must be created within a Pulumi Stack + * const stack = new pulumicdk.Stack(scope, 'pulumi-stack'); + * const bucket = new s3.Bucket(stack, 'my-bucket'); + * return { + * bucket: stack.asOutput(bucket.bucketName), + * }; + * }); + * + * export const bucket = app.outputs['bucket']; + */ export class App extends pulumi.ComponentResource implements ICloudAssemblyDirectoryProducer, AppComponent { public readonly name: string; - public readonly component: pulumi.ComponentResource; - public readonly stacks: { [artifactId: string]: PulumiStack } = {}; /** * The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. @@ -43,6 +59,16 @@ export class App */ public outputs: { [outputId: string]: pulumi.Output } = {}; + /** + * @internal + */ + public readonly component: pulumi.ComponentResource; + + /** + * @internal + */ + public readonly stacks: { [artifactId: string]: cdk.Stack } = {}; + /** @internal */ public converter: Promise; @@ -56,6 +82,10 @@ export class App * @internal */ public assemblyDir!: string; + + /** + * @internal + */ readonly dependencies: CdkConstruct[] = []; private readonly createFunc: (scope: App) => AppOutputs | void; @@ -73,6 +103,9 @@ export class App const data = this.getData(); this.converter = data.then((d) => d.converter); + // This grabs the outputs off of the stacks themselves after they + // have been converted. This allows us to present the outputs property + // as a plain value instead of an Output value. const outputs = this.converter.then((converter) => { const stacks = Array.from(converter.stacks.values()); return stacks.reduce( @@ -93,6 +126,9 @@ export class App this.registerOutputs(this.outputs); } + /** + * @internal + */ public get app(): cdk.App { if (!this._app) { throw new Error('cdk.App has not been created yet'); @@ -109,6 +145,7 @@ export class App this.appProps = props.args?.props; this.appOptions = props.args; try { + // TODO: support lookups https://github.com/pulumi/pulumi-cdk/issues/184 await cli.synth({ quiet: true, lookups: false }); } catch (e: any) { if (typeof e.message === 'string' && e.message.includes('Context lookups have been disabled')) { @@ -134,6 +171,13 @@ export class App }; } + /** + * produce is called by `AwsCdkCli` as part of the `synth` operation. It will potentially + * be called multiple times if there is any missing context values. + * + * @param context The CDK context collected by the CLI that needs to be passed to the cdk.App + * @returns the path to the CDK Assembly directory + */ async produce(context: Record): Promise { const appId = this.appOptions?.appId ?? generateAppId(); const synthesizer = this.appProps?.defaultStackSynthesizer ?? new PulumiSynthesizer({ appId, parent: this }); @@ -146,6 +190,8 @@ export class App ...(this.appProps ?? {}), autoSynth: false, analyticsReporting: false, + // We require tree metadata to walk the construct tree + treeMetadata: true, context, defaultStackSynthesizer: synthesizer, }); @@ -165,18 +211,22 @@ export class App } } +/** + * Options for creating a Pulumi CDK Stack + */ export interface StackOptions extends pulumi.ComponentResourceOptions { - props: cdk.StackProps; + /** + * The CDK Stack props + */ + props?: cdk.StackProps; } /** * A Construct that represents an AWS CDK stack deployed with Pulumi. * - * In order to deploy a CDK stack with Pulumi, it must derive from this class. The `synth` method must be called after - * all CDK resources have been defined in order to deploy the stack (usually, this is done as the last line of the - * subclass's constructor). + * In order to deploy a CDK stack with Pulumi, it must derive from this class. */ -export class Stack extends PulumiStack { +export class Stack extends cdk.Stack { /** * Return whether the given object is a Stack. * @@ -187,29 +237,14 @@ export class Stack extends PulumiStack { return x !== null && typeof x === 'object' && STACK_SYMBOL in x; } - // // The URN of the underlying Pulumi component. - // urn!: pulumi.Output; - // resolveURN!: (urn: pulumi.Output) => void; - // rejectURN!: (error: any) => void; - - /** @internal */ - app: cdk.App; - - // The stack's converter. This is used by asOutput in order to convert CDK values to Pulumi Outputs. This is a - // Promise so users are able to call asOutput before they've called synth. Note that this _does_ make forgetting - // to call synth a sharper edge: calling asOutput without calling synth will create outputs that never resolve - // and the program will hang. - converter!: Promise; - /** + * The stack's converter. This is used by asOutput in order to convert CDK + * values to Pulumi Outputs. This is a Promise so users are able to call + * asOutput before they've called synth. + * * @internal */ - public options?: StackOptions; - - /** @internal */ - public outdir: string; - - private pulumiApp: App; + public converter: Promise; /** * Create and register an AWS CDK stack deployed with Pulumi. @@ -221,13 +256,7 @@ export class Stack extends PulumiStack { super(app.app, name, options?.props); Object.defineProperty(this, STACK_SYMBOL, { value: true }); - this.pulumiApp = app; - this.options = options; - - this.outdir = app.assemblyDir; - this.app = app.app; - - this.converter = this.pulumiApp.converter.then((converter) => converter.stacks.get(this.artifactId)!); + this.converter = app.converter.then((converter) => converter.stacks.get(this.artifactId)!); } /** @@ -251,23 +280,6 @@ export class Stack extends PulumiStack { } } -function debugAssembly(assembly: cx.CloudAssembly): any { - return { - version: assembly.version, - directory: assembly.directory, - runtime: assembly.runtime, - artifacts: assembly.artifacts.map(debugArtifact), - }; -} - -function debugArtifact(artifact: cx.CloudArtifact): any { - return { - dependencies: artifact.dependencies.map((artifact) => artifact.id), - manifest: artifact.manifest, - messages: artifact.messages, - }; -} - /** * Generate a unique app id based on the project and stack. We need some uniqueness * in case multiple stacks/projects are deployed to the same AWS environment. diff --git a/src/types.ts b/src/types.ts index 7d0c3bf0..de58d1a0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,34 +1,10 @@ import * as pulumi from '@pulumi/pulumi'; -import { Stack, StackProps, AppProps, App } from 'aws-cdk-lib/core'; +import { Stack, AppProps } from 'aws-cdk-lib/core'; import { CdkConstruct, ResourceMapping } from './interop'; -const STACK_SYMBOL = Symbol.for('@pulumi/cdk.Stack'); - -export abstract class PulumiStack extends Stack { - /** - * Return whether the given object is a Stack. - * - * We do attribute detection since we can't reliably use 'instanceof'. - * @internal - */ - public static isPulumiStack(x: any): x is Stack { - return x !== null && typeof x === 'object' && STACK_SYMBOL in x; - } - /** - * The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. - * Each CfnOutput defined in the AWS CDK Stack will populate a value in the outputs. - */ - public readonly outputs: { [outputId: string]: pulumi.Output } = {}; - - constructor(app: App, name: string, options?: StackProps) { - super(app, name, options); - Object.defineProperty(this, STACK_SYMBOL, { value: true }); - } - /** @internal */ - registerOutput(outputId: string, output: any) { - this.outputs[outputId] = pulumi.output(output); - } -} +/** + * Options for creating a Pulumi CDK App Component + */ export interface AppOptions { /** * Specify the CDK Stack properties to asociate with the stack. @@ -70,15 +46,16 @@ export interface AppOptions { options: pulumi.ResourceOptions, ): ResourceMapping[] | undefined; } + /** - * Options specific to the Stack component. + * Options specific to the Pulumi CDK App component. */ export interface AppResourceOptions extends pulumi.ComponentResourceOptions { appOptions?: AppOptions; } /** - * The pulumi provider to read the schema from + * The Pulumi provider to read the schema from */ export enum PulumiProvider { // We currently only support aws-native provider resources @@ -86,12 +63,14 @@ export enum PulumiProvider { } /** - * StackComponentResource is the underlying pulumi ComponentResource for each pulumicdk.Stack - * This exists because pulumicdk.Stack needs to extend cdk.Stack, but we also want it to represent a - * pulumi ComponentResource so we create this `StackComponentResource` to hold the pulumi logic + * AppComponent is the interface representing the Pulumi CDK App Component Resource */ export interface AppComponent { + /** + * The name of the component + */ readonly name: string; + /** * The directory to which cdk synthesizes the CloudAssembly * @internal @@ -100,15 +79,18 @@ export interface AppComponent { /** * The CDK stack associated with the component resource + * @internal */ - readonly stacks: { [artifactId: string]: PulumiStack }; + readonly stacks: { [artifactId: string]: Stack }; /** + * The underlying ComponentResource * @internal */ readonly component: pulumi.ComponentResource; /** + * The AppOptions for this component * @internal */ appOptions?: AppOptions; From 176d9b209f774d2e47f88a3fe273b5881767295c Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:12:14 -0400 Subject: [PATCH 09/12] fixing dep --- examples/lookups/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/lookups/package.json b/examples/lookups/package.json index c94e85f3..82f6ffeb 100644 --- a/examples/lookups/package.json +++ b/examples/lookups/package.json @@ -8,6 +8,6 @@ "@pulumi/aws": "^6.0.0", "@pulumi/cdk": "^0.5.0", "aws-cdk-lib": "2.149.0", - "constructs": "^10.0.111" + "constructs": "10.3.0" } } From 42df90ad3d669a4570c9f9074876d47f1caa8426 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:16:30 -0400 Subject: [PATCH 10/12] rebasing upstream examples --- examples/eventbridge-atm/index.ts | 10 +++++----- examples/scalable-webhook/index.ts | 10 +++++----- examples/the-big-fan/index.ts | 11 ++++++----- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/examples/eventbridge-atm/index.ts b/examples/eventbridge-atm/index.ts index 2bcd5c9b..23f6ea10 100644 --- a/examples/eventbridge-atm/index.ts +++ b/examples/eventbridge-atm/index.ts @@ -10,8 +10,8 @@ import { } from 'aws-cdk-lib'; class EventbridgeAtmStack extends pulumicdk.Stack { - constructor(id: string) { - super(id); + constructor(app: pulumicdk.App, id: string) { + super(app, id); this.node.setContext('@aws-cdk/aws-apigateway:disableCloudWatchRole', 'true'); /** @@ -110,9 +110,9 @@ class EventbridgeAtmStack extends pulumicdk.Stack { new apigw.LambdaRestApi(this, 'Endpoint', { handler: atmProducerLambda, }); - - this.synth(); } } -new EventbridgeAtmStack('eventbridge-sns-stack'); +new pulumicdk.App('app', (scope: pulumicdk.App) => { + new EventbridgeAtmStack(scope, 'eventbridge-sns-stack'); +}); diff --git a/examples/scalable-webhook/index.ts b/examples/scalable-webhook/index.ts index d6fffb53..23091ff1 100644 --- a/examples/scalable-webhook/index.ts +++ b/examples/scalable-webhook/index.ts @@ -11,8 +11,8 @@ import { } from 'aws-cdk-lib'; class ScalableWebhookStack extends pulumicdk.Stack { - constructor(id: string) { - super(id); + constructor(app: pulumicdk.App, id: string) { + super(app, id); this.node.setContext('@aws-cdk/aws-apigateway:disableCloudWatchRole', 'true'); /** @@ -77,9 +77,9 @@ class ScalableWebhookStack extends pulumicdk.Stack { new apigw.LambdaRestApi(this, 'Endpoint', { handler: sqsPublishLambda, }); - - this.synth(); } } -new ScalableWebhookStack('eventbridge-sns-stack'); +new pulumicdk.App('app', (scope: pulumicdk.App) => { + new ScalableWebhookStack(scope, 'eventbridge-sns-stack'); +}); diff --git a/examples/the-big-fan/index.ts b/examples/the-big-fan/index.ts index 4771b5d2..181d1ff1 100644 --- a/examples/the-big-fan/index.ts +++ b/examples/the-big-fan/index.ts @@ -11,8 +11,8 @@ import * as pulumicdk from '@pulumi/cdk'; import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources'; class TheBigFanStack extends pulumicdk.Stack { - constructor(id: string) { - super(id); + constructor(app: pulumicdk.App, id: string) { + super(app, id); this.node.setContext('@aws-cdk/aws-apigateway:disableCloudWatchRole', 'true'); /** @@ -221,8 +221,9 @@ class TheBigFanStack extends pulumicdk.Stack { ], }, ); - - this.synth(); } } -new TheBigFanStack('TheBigFanStack'); + +new pulumicdk.App('app', (scope: pulumicdk.App) => { + new TheBigFanStack(scope, 'TheBigFanStack'); +}); From fd02d8111be0c0719358f39afd7eb87d650c0ab5 Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:44:07 -0400 Subject: [PATCH 11/12] revert workflow changes --- .github/workflows/run-acceptance-tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-acceptance-tests.yml b/.github/workflows/run-acceptance-tests.yml index 45d6400a..71b214da 100644 --- a/.github/workflows/run-acceptance-tests.yml +++ b/.github/workflows/run-acceptance-tests.yml @@ -44,8 +44,10 @@ jobs: test: name: acceptance-test concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-${{ matrix.index }} # TODO: concurrent tests across PRs can cause problems - cancel-in-progress: true + group: acceptance-test-${{ matrix.index }} # TODO: concurrent tests across PRs can cause problems + # TODO: https://github.com/pulumi/pulumi-cdk/issues/152 means that some resources in tests will have a static name + # which if the test does not complete, will not be cleaned up causing the next run to fail with resource already exists + cancel-in-progress: false runs-on: ubuntu-latest steps: - name: Checkout Repo From 37dcabbbbae43c061d10ebfbf43bcce16a56c34c Mon Sep 17 00:00:00 2001 From: corymhall <43035978+corymhall@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:28:01 -0400 Subject: [PATCH 12/12] updating based on review comments --- .github/workflows/run-acceptance-tests.yml | 2 +- README.md | 28 ++++++++++++++++++++-- src/stack.ts | 2 ++ 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.github/workflows/run-acceptance-tests.yml b/.github/workflows/run-acceptance-tests.yml index 71b214da..5ddd5ad9 100644 --- a/.github/workflows/run-acceptance-tests.yml +++ b/.github/workflows/run-acceptance-tests.yml @@ -45,7 +45,7 @@ jobs: name: acceptance-test concurrency: group: acceptance-test-${{ matrix.index }} # TODO: concurrent tests across PRs can cause problems - # TODO: https://github.com/pulumi/pulumi-cdk/issues/152 means that some resources in tests will have a static name + # TODO[pulumi/pulumi-cdk#152]: means that some resources in tests will have a static name # which if the test does not complete, will not be cleaned up causing the next run to fail with resource already exists cancel-in-progress: false runs-on: ubuntu-latest diff --git a/README.md b/README.md index 8197195c..3502fbdd 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,30 @@ Try the workshop at https://apprunnerworkshop.com Read the docs at https://docs.aws.amazon.com/apprunner ``` +## Bootstrapping + +CDK has the concept of [bootstrapping](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html) +which requires you to first bootstrap your account with certain AWS resources +that CDK needs to exist. With Pulumi CDK this is not required! Pulumi CDK will +automatically and dynamically create the bootstrap resources as needed. + +### S3 Resources + +When any file assets are added to your application, CDK will automatically +create the following staging resources. + +1. [aws.s3.BucketV2](https://www.pulumi.com/registry/packages/aws/api-docs/s3/bucketv2/) + - `forceDestroy`: true +2. [aws.s3.BucketServerSideEncryptionConfigurationV2](https://www.pulumi.com/registry/packages/aws/api-docs/s3/bucketserversideencryptionconfigurationv2/) + - `AES256` +3. [aws.s3.BucketVersioningV2](https://www.pulumi.com/registry/packages/aws/api-docs/s3/bucketversioningv2/) + - `Enabled` +4. [aws.s3.BucketLifecycleConfigurationV2](https://www.pulumi.com/registry/packages/aws/api-docs/s3/bucketlifecycleconfigurationv2/) + - Expire old versions > 365 days + - Expire deploy-time assets > 30 days +5. [aws.s3.BucketPolicy](https://www.pulumi.com/registry/packages/aws/api-docs/s3/bucketpolicy/) + - Require SSL + ## API ### `App` @@ -116,8 +140,8 @@ Parameters: #### `outputs` -The collection of outputs from the AWS CDK Stack represented as Pulumi Outputs. -Each `CfnOutput` defined in the AWS CDK Stack will populate a value in the +The collection of outputs from the Pulumi CDK App represented as Pulumi Outputs. +Each `CfnOutput` defined in each AWS CDK Stack will populate a value in the outputs. ```ts diff --git a/src/stack.ts b/src/stack.ts index 6231cb6f..4a70fab4 100644 --- a/src/stack.ts +++ b/src/stack.ts @@ -175,6 +175,8 @@ export class App * produce is called by `AwsCdkCli` as part of the `synth` operation. It will potentially * be called multiple times if there is any missing context values. * + * Note: currently lookups are disabled so this will only be executed once + * * @param context The CDK context collected by the CLI that needs to be passed to the cdk.App * @returns the path to the CDK Assembly directory */