Repository: pulumi/pulumi-cdk
Pull request #163 (2024-09-06 12:37:31)

Remove mappings to aws classic resources
This PR removes the manual mappings to aws classic resources that now
exist in aws-native.

BREAKING CHANGE: resources that were previously deployed with the `aws`
provider are now deployed with the `aws-native` provider. This will
cause resource replacement

re #142


Patch:
diff --git a/examples/alb/index.ts b/examples/alb/index.ts
index 39242e3..78d95c6 100644
--- a/examples/alb/index.ts
+++ b/examples/alb/index.ts
@@ -29,11 +29,15 @@ class AlbStack extends pulumicdk.Stack {
             port: 80,
         });
 
-        listener.addTargets('Target', {
+        const tg = listener.addTargets('Target', {
             port: 80,
             targets: [asg],
         });
 
+        // workaround for https://github.com/pulumi/pulumi-cdk/issues/62
+        const cfnTargetGroup = tg.node.defaultChild as elbv2.CfnTargetGroup;
+        cfnTargetGroup.overrideLogicalId('LBListenerTG');
+
         listener.connections.allowDefaultPortFromAnyIpv4('Open to the world');
 
         asg.scaleOnRequestCount('AModestLoad', {
diff --git a/examples/cron-lambda/index.ts b/examples/cron-lambda/index.ts
index 76df88a..644fe82 100644
--- a/examples/cron-lambda/index.ts
+++ b/examples/cron-lambda/index.ts
@@ -5,13 +5,12 @@ import * as aws_lambda from 'aws-cdk-lib/aws-lambda';
 import { Duration } from 'aws-cdk-lib';
 import * as pulumi from '@pulumi/pulumi';
 import * as pulumicdk from '@pulumi/cdk';
-import { remapCloudControlResource } from './adapter';
 
 class LambdaStack extends pulumicdk.Stack {
     lambdaArn: pulumi.Output<string>;
 
     constructor(id: string, options?: pulumicdk.StackOptions) {
-        super(id, { ...options, remapCloudControlResource });
+        super(id, options);
 
         // Use the AWS CDK Lambda Function API directly.
         const lambdaFn = new aws_lambda.Function(this, 'lambda', {
diff --git a/examples/fargate/adapter.ts b/examples/fargate/adapter.ts
deleted file mode 100644
index 2bc9f70..0000000
--- a/examples/fargate/adapter.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { CfnElement } from 'aws-cdk-lib';
-import * as pulumi from '@pulumi/pulumi';
-import * as pulumicdk from '@pulumi/cdk';
-import * as aws from '@pulumi/aws';
-import { debug } from '@pulumi/pulumi/log';
-
-export function remapCloudControlResource(
-    element: CfnElement,
-    logicalId: string,
-    typeName: string,
-    rawProps: any,
-    options: pulumi.ResourceOptions,
-): pulumi.CustomResource | undefined {
-    // Lower-case the raw cloudformation resource schema property keys.
-    const props = pulumicdk.interop.normalize(rawProps);
-
-    switch (typeName) {
-        case 'AWS::ApplicationAutoScaling::ScalingPolicy':
-            debug(`AWS::ApplicationAutoScaling::ScalingPolicy props: ${JSON.stringify(props)}`);
-            return new aws.appautoscaling.Policy(
-                logicalId,
-                {
-                    resourceId: props.resourceId ?? props.scalingTargetId,
-                    scalableDimension: props.scalableDimension ?? 'ecs:service:DesiredCount',
-                    serviceNamespace: props.serviceNamespace ?? 'ecs',
-                    policyType: props.policyType,
-                    stepScalingPolicyConfiguration: props.stepScalingPolicyConfiguration,
-                    name: props.policyName,
-                    targetTrackingScalingPolicyConfiguration: props.targetTrackingScalingPolicyConfiguration,
-                },
-                options,
-            );
-        default:
-            return undefined;
-    }
-}
diff --git a/examples/fargate/index.ts b/examples/fargate/index.ts
index e2489ac..bcbc98c 100644
--- a/examples/fargate/index.ts
+++ b/examples/fargate/index.ts
@@ -5,15 +5,13 @@ import { Duration } from 'aws-cdk-lib';
 import * as ec2 from 'aws-cdk-lib/aws-ec2';
 import * as ecs from 'aws-cdk-lib/aws-ecs';
 import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns';
-
-import { remapCloudControlResource } from './adapter';
+import { CfnTargetGroup } from 'aws-cdk-lib/aws-elasticloadbalancingv2';
 
 class FargateStack extends pulumicdk.Stack {
-
     loadBalancerDNS: pulumi.Output<string>;
 
     constructor(id: string, options?: pulumicdk.StackOptions) {
-        super(id, { ...options, remapCloudControlResource });
+        super(id, options);
 
         // Create VPC and Fargate Cluster
         // NOTE: Limit AZs to avoid reaching resource quotas
@@ -24,15 +22,19 @@ class FargateStack extends pulumicdk.Stack {
         const fargateService = new ecs_patterns.NetworkLoadBalancedFargateService(this, 'sample-app', {
             cluster,
             taskImageOptions: {
-                image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample")
+                image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'),
             },
         });
 
+        // workaround for https://github.com/pulumi/pulumi-cdk/issues/62
+        const cfnTargetGroup = fargateService.targetGroup.node.defaultChild as CfnTargetGroup;
+        cfnTargetGroup.overrideLogicalId('LBListenerTG');
+
         // Open port 80 inbound to IPs within VPC to allow network load balancer to connect to the service
         fargateService.service.connections.securityGroups[0].addIngressRule(
             ec2.Peer.ipv4(vpc.vpcCidrBlock),
             ec2.Port.tcp(80),
-            "allow http inbound from vpc",
+            'allow http inbound from vpc',
         );
 
         // Setup AutoScaling policy
@@ -40,7 +42,7 @@ class FargateStack extends pulumicdk.Stack {
         scaling.scaleOnCpuUtilization('CpuScaling', {
             targetUtilizationPercent: 50,
             scaleInCooldown: Duration.seconds(60),
-            scaleOutCooldown: Duration.seconds(60)
+            scaleOutCooldown: Duration.seconds(60),
         });
 
         this.loadBalancerDNS = this.asOutput(fargateService.loadBalancer.loadBalancerDnsName);
@@ -48,8 +50,7 @@ class FargateStack extends pulumicdk.Stack {
         // Finalize the stack and deploy its resources.
         this.synth();
     }
-};
+}
 
 const stack = new FargateStack('fargatestack');
 export const loadBalancerURL = stack.loadBalancerDNS;
-
diff --git a/src/aws-resource-mappings.ts b/src/aws-resource-mappings.ts
index b955800..7af6c7d 100644
--- a/src/aws-resource-mappings.ts
+++ b/src/aws-resource-mappings.ts
@@ -16,7 +16,6 @@ import * as pulumi from '@pulumi/pulumi';
 import * as aws from '@pulumi/aws';
 import { CfnElement } from 'aws-cdk-lib';
 import { ResourceMapping, normalize } from './interop';
-import { debug } from '@pulumi/pulumi/log';
 
 function maybe<T, U>(v: T | undefined, fn: (t: T) => U): U | undefined {
     if (v === undefined) {
@@ -40,6 +39,9 @@ function tags(tags: pulumi.Input<pulumi.Input<CfnTags>[]> | undefined): AwsTags
     );
 }
 
+/**
+ * Any resource that does not currently exist in CCAPI can be mapped to an aws classic resource.
+ */
 export function mapToAwsResource(
     element: CfnElement,
     logicalId: string,
@@ -50,10 +52,6 @@ export function mapToAwsResource(
     const props = normalize(rawProps);
     switch (typeName) {
         // ApiGatewayV2
-        case 'AWS::ApiGatewayV2::Api':
-            return new aws.apigatewayv2.Api(logicalId, props, options);
-        case 'AWS::ApiGatewayV2::Deployment':
-            return new aws.apigatewayv2.Deployment(logicalId, props, options);
         case 'AWS::ApiGatewayV2::Integration':
             return new aws.apigatewayv2.Integration(
                 logicalId,
@@ -66,16 +64,6 @@ export function mapToAwsResource(
                 },
                 options,
             );
-        case 'AWS::ApiGatewayV2::Route':
-            return new aws.apigatewayv2.Route(
-                logicalId,
-                {
-                    ...props,
-                    requestModels: rawProps.RequestModels,
-                    requestParameters: rawProps.RequestParameters,
-                },
-                options,
-            );
         case 'AWS::ApiGatewayV2::Stage':
             return new aws.apigatewayv2.Stage(
                 logicalId,
@@ -95,350 +83,6 @@ export function mapToAwsResource(
                 options,
             );
 
-        // DynamoDB
-        case 'AWS::DynamoDB::Table':
-            return mapDynamoDBTable(element, logicalId, typeName, rawProps, props, options);
-
-        // EC2
-        case 'AWS::EC2::EIP':
-            return new aws.ec2.Eip(
-                logicalId,
-                {
-                    instance: props.instanceId,
-                    publicIpv4Pool: props.publicIpv4Pool,
-                    tags: tags(props.tags),
-                    vpc: props.domain ? pulumi.output(props.domain).apply((domain) => domain === 'vpc') : undefined,
-                },
-                options,
-            );
-        case 'AWS::EC2::SecurityGroup': {
-            debug(`AWS::EC2::SecurityGroup props: ${JSON.stringify(props)}`);
-            const securityGroup = new aws.ec2.SecurityGroup(
-                logicalId,
-                {
-                    description: props.groupDescription,
-                    egress: (props.securityGroupEgress || []).map((e: any) => {
-                        const egress = {
-                            description: e.description,
-                            protocol: e.ipProtocol,
-                            fromPort: e.fromPort,
-                            toPort: e.toPort,
-                            cidrBlocks: e.cidrIp ? [e.cidrIp] : undefined,
-                            ipv6CidrBlocks: e.cidrIpv6 ? [e.cidrIpv6] : undefined,
-                            prefixListIds: e.destinationPrefixListId ? [e.destinationPrefixListId] : undefined,
-                            securityGroups: e.destinationSecurityGroupId || [],
-                        };
-                        if (egress.fromPort === undefined && egress.toPort === undefined && egress.protocol == '-1') {
-                            egress.fromPort = 0;
-                            egress.toPort = 0;
-                        }
-                        return egress;
-                    }),
-                    ingress: (props.securityGroupIngress || []).map((i: any) => {
-                        return {
-                            description: i.description,
-                            protocol: i.ipProtocol,
-                            fromPort: i.fromPort,
-                            toPort: i.toPort,
-                            cidrBlocks: i.cidrIp ? [i.cidrIp] : undefined,
-                            ipv6CidrBlocks: i.cidrIpv6 ? [i.cidrIpv6] : undefined,
-                            prefixListIds: i.destinationPrefixListId ? [i.destinationPrefixListId] : undefined,
-                            securityGroups: (i.sourceSecurityGroupId || []).concat(
-                                ...(i.sourceSecurityGroupName || []),
-                            ),
-                        };
-                    }),
-                    tags: tags(props.tags),
-                    vpcId: props.vpcId,
-                    revokeRulesOnDelete: true, // FIXME: is this right?
-                },
-                options,
-            );
-            return {
-                resource: securityGroup,
-                attributes: { groupId: securityGroup.id },
-            };
-        }
-        case 'AWS::EC2::SecurityGroupEgress':
-            debug(`AWS::EC2::SecurityGroupEgress props: ${JSON.stringify(props)}`);
-            return new aws.ec2.SecurityGroupRule(
-                logicalId,
-                {
-                    protocol: props.ipProtocol,
-                    fromPort: props.fromPort,
-                    toPort: props.toPort,
-                    sourceSecurityGroupId: props.destinationSecurityGroupId,
-                    securityGroupId: props.groupId,
-                    prefixListIds: props.destinationPrefixListId,
-                    cidrBlocks: props.cidrIp ? [props.cidrIp] : undefined,
-                    ipv6CidrBlocks: props.cidrIpv6 ? [props.cidrIpv6] : undefined,
-                    type: 'egress',
-                },
-                options,
-            );
-        case 'AWS::EC2::SecurityGroupIngress':
-            debug(`AWS::EC2::SecurityGroupIngress props: ${JSON.stringify(props)}: cidr_blocks: ${props.cidrIp}`);
-            return new aws.ec2.SecurityGroupRule(
-                logicalId,
-                {
-                    protocol: props.ipProtocol,
-                    fromPort: props.fromPort,
-                    toPort: props.toPort,
-                    securityGroupId: props.groupId,
-                    prefixListIds: props.sourcePrefixListId,
-                    sourceSecurityGroupId: props.sourceSecurityGroupId,
-                    cidrBlocks: props.cidrIp ? [props.cidrIp] : undefined,
-                    ipv6CidrBlocks: props.cidrIpv6 ? [props.cidrIpv6] : undefined,
-                    type: 'ingress',
-                },
-                options,
-            );
-        case 'AWS::EC2::VPCGatewayAttachment':
-            // Create either an internet gateway attachment or a VPC gateway attachment
-            // depending on the payload.
-            if (props.vpnGatewayId === undefined) {
-                return new aws.ec2.InternetGatewayAttachment(
-                    logicalId,
-                    {
-                        internetGatewayId: props.internetGatewayId,
-                        vpcId: props.vpcId,
-                    },
-                    options,
-                );
-            }
-            return new aws.ec2.VpnGatewayAttachment(
-                logicalId,
-                {
-                    vpcId: props.vpcId,
-                    vpnGatewayId: props.vpnGatewayId,
-                },
-                options,
-            );
-        case 'AWS::ElasticLoadBalancingV2::LoadBalancer': {
-            debug(`AWS::ElasticLoadBalancingV2::LoadBalancer props: ${JSON.stringify(props)}`);
-            const lb = new aws.lb.LoadBalancer(
-                logicalId,
-                {
-                    ipAddressType: props.ipAddressType,
-                    loadBalancerType: props.type,
-                    securityGroups: props.securityGroups,
-                    subnets: props.subnets,
-                    subnetMappings: props.subnetMappings?.map(
-                        (m: any) =>
-                            <aws.types.input.lb.LoadBalancerSubnetMapping>{
-                                allocationId: m.allocationId,
-                                ipv6Address: m.iPv6Address,
-                                privateIpv4Address: m.privateIPv4Address,
-                                subnetId: m.subnetId,
-                            },
-                    ),
-                    tags: tags(props.tags),
-                    internal: props.scheme ? props.scheme == 'internal' : false,
-                },
-                options,
-            );
-            return {
-                resource: lb,
-                attributes: { dNSName: lb.dnsName },
-            };
-        }
-        case 'AWS::ElasticLoadBalancingV2::TargetGroup': {
-            debug(`AWS::ElasticLoadBalancingV2::TargetGroup props: ${JSON.stringify(props)}`);
-            const tgAttributes = targetGroupAttributesMap(props.targetGroupAttributes);
-            debug(`${logicalId} tgAttributes ${JSON.stringify(tgAttributes)}`);
-            const tg = new aws.lb.TargetGroup(
-                logicalId,
-                {
-                    healthCheck: {
-                        enabled: props.healthCheckEnabled,
-                        interval: props.healthCheckIntervalSeconds,
-                        path: props.healthCheckPath,
-                        port: props.healthCheckPort,
-                        protocol: props.healthCheckProtocol,
-                        timeout: props.healthCheckTimeoutSeconds,
-                        matcher: props.matcher ? props.matcher.httpCode || props.matcher.grpcCode : undefined,
-                        healthyThreshold: props.healthyThresholdCount,
-                    },
-                    // logicalId can be too big and cause autonaming to spill beyond 32 char limit for names
-                    name: props.name ?? (logicalId.length > 24 ? logicalId.slice(-32) : undefined),
-                    port: props.port,
-                    protocol: props.protocol,
-                    protocolVersion: props.protocolVersion,
-                    vpcId: props.vpcId,
-                    tags: tags(props.tags),
-                    targetType: props.targetType,
-                    stickiness: stickiness(tgAttributes),
-                    deregistrationDelay: maybeTargetGroupAttribute(
-                        tgAttributes,
-                        'deregistration_delay.timeout_seconds',
-                    ),
-                    connectionTermination: maybeTargetGroupAttribute(
-                        tgAttributes,
-                        'deregistration_delay.connection_termination.enabled',
-                    ),
-                    proxyProtocolV2: maybeTargetGroupAttribute(tgAttributes, 'proxy_protocol_v2.enabled'),
-                    preserveClientIp: maybeTargetGroupAttribute(tgAttributes, 'preserve_client_ip.enabled'),
-                    lambdaMultiValueHeadersEnabled: maybeTargetGroupAttribute(
-                        tgAttributes,
-                        'lambda.multi_value_headers.enabled',
-                    ),
-                    slowStart: maybeTargetGroupAttribute(tgAttributes, 'slow_start.duration_seconds'),
-                    loadBalancingAlgorithmType: maybeTargetGroupAttribute(
-                        tgAttributes,
-                        'load_balancing.algorithm.type',
-                    ),
-                },
-                options,
-            );
-            return {
-                resource: tg,
-                attributes: {
-                    targetGroupFullName: tg.arnSuffix,
-                    targetGroupName: tg.name,
-                },
-            };
-        }
-        case 'AWS::AutoScaling::AutoScalingGroup': {
-            debug(`AWS::AutoScaling::AutoScalingGroup props: ${JSON.stringify(props)}`);
-            return new aws.autoscaling.Group(
-                logicalId,
-                {
-                    availabilityZones: props.availabilityZones,
-                    maxSize: parseInt(props.maxSize),
-                    minSize: parseInt(props.minSize),
-                    capacityRebalance: props.capacityRebalance ? JSON.parse(props.capacityRebalance) : undefined,
-                    defaultCooldown: props.cooldown ? parseInt(props.cooldown) : undefined,
-                    desiredCapacity: props.desiredCapacity ? parseInt(props.desiredCapacity) : undefined,
-                    healthCheckGracePeriod: props.healthCheckGracePeriod
-                        ? parseInt(props.healthCheckGracePeriod)
-                        : undefined,
-                    healthCheckType: props.healthCheckType,
-                    launchConfiguration: props.launchConfigurationName,
-                    launchTemplate: props.launchTemplate?.map(
-                        (t: any) =>
-                            <aws.types.input.autoscaling.GroupLaunchTemplate>{
-                                id: t.launchTemplateId,
-                                name: t.launchTemplateName,
-                                version: t.version,
-                            },
-                    ),
-                    initialLifecycleHooks: props.lifecycleHookSpecificationList?.map(
-                        (s: any) =>
-                            <aws.types.input.autoscaling.GroupInitialLifecycleHook>{
-                                defaultResult: s.defaultReason,
-                                heartbeatTimeout: s.heartbeatTimeout,
-                                lifecycleTransition: s.lifecycleTransition,
-                                notificationMetadata: s.notificationMetadata,
-                                notificationTargetArn: s.notificationTargetArn,
-                                roleArn: s.roleArn,
-                                name: s.lifeCycleHookName,
-                            },
-                    ),
-                    loadBalancers: props.loadBalancerNames,
-                    maxInstanceLifetime: props.maxInstanceLifetime,
-                    // mixedInstancesPolicy: FIXME!
-                    protectFromScaleIn: props.newInstancesProtectedFromScaleIn,
-                    placementGroup: props.placementGroup,
-                    serviceLinkedRoleArn: props.serviceLinkedRoleArn,
-                    tags: props.tags?.map(
-                        (m: { key: any; propagateAtLaunch: any; value: any }) =>
-                            <aws.types.input.autoscaling.GroupTag>{
-                                key: m.key,
-                                propagateAtLaunch: m.propagateAtLaunch,
-                                value: m.value,
-                            },
-                    ),
-                    targetGroupArns: props.targetGroupArns,
-                    terminationPolicies: props.terminationPolicies,
-                    vpcZoneIdentifiers: props.vpcZoneIdentifier,
-                },
-                options,
-            );
-        }
-        case 'AWS::AutoScaling::ScalingPolicy': {
-            return new aws.autoscaling.Policy(
-                logicalId,
-                {
-                    adjustmentType: props.adjustmentType,
-                    autoscalingGroupName: props.autoScalingGroupName,
-                    cooldown: props.cooldown ? parseInt(props.cooldown) : undefined,
-                    estimatedInstanceWarmup: props.estimatedInstanceWarmup
-                        ? parseInt(props.estimatedInstanceWarmup)
-                        : undefined,
-                    metricAggregationType: props.metricAggregationType,
-                    minAdjustmentMagnitude: props.minAdjustmentMagnitude,
-                    policyType: props.policyType,
-                    predictiveScalingConfiguration: props.predictiveScalingConfiguration,
-                    scalingAdjustment: props.scalingAdjustment,
-                    stepAdjustments: props.stepAdjustments,
-                    targetTrackingConfiguration: props.targetTrackingConfiguration,
-                },
-                options,
-            );
-        }
-        case 'AWS::EC2::Route':
-            return new aws.ec2.Route(
-                logicalId,
-                {
-                    routeTableId: props.routeTableId,
-                    carrierGatewayId: props.carrierGatewayId,
-                    destinationCidrBlock: props.destinationCidrBlock,
-                    destinationIpv6CidrBlock: props.destinationIpv6CidrBlock,
-                    egressOnlyGatewayId: props.egressOnlyInternetGatewayId,
-                    gatewayId: props.gatewayId,
-                    localGatewayId: props.localGatewayId,
-                    natGatewayId: props.natGatewayId,
-                    networkInterfaceId: props.networkInterfaceId,
-                    transitGatewayId: props.transitGatewayId,
-                    vpcEndpointId: props.vpcEndpointId,
-                    vpcPeeringConnectionId: props.vpcPeeringConnectionId,
-                },
-                options,
-            );
-        case 'AWS::EC2::NatGateway':
-            return new aws.ec2.NatGateway(
-                logicalId,
-                {
-                    subnetId: props.subnetId,
-                    allocationId: props.allocationId,
-                    connectivityType: props.connectivityType,
-                    tags: tags(props.tags),
-                },
-                options,
-            );
-        case 'AWS::ApplicationAutoScaling::ScalableTarget': {
-            const target = new aws.appautoscaling.Target(
-                logicalId,
-                {
-                    maxCapacity: props.maxCapacity,
-                    minCapacity: props.minCapacity,
-                    resourceId: props.resourceId,
-                    roleArn: props.roleArn,
-                    scalableDimension: props.scalableDimension,
-                    serviceNamespace: props.serviceNamespace,
-                },
-                options,
-            );
-            props.ScheduledActions?.map(
-                (action: any) =>
-                    new aws.appautoscaling.ScheduledAction(
-                        logicalId + action.scheduledActionName,
-                        {
-                            resourceId: target.resourceId,
-                            scalableDimension: target.scalableDimension,
-                            scalableTargetAction: action.scalableTargetAction,
-                            schedule: action.schedule,
-                            serviceNamespace: target.serviceNamespace,
-                            startTime: action.startTime,
-                            endTime: action.endTime,
-                            timezone: action.timezone,
-                            name: action.scheduledActionName,
-                        },
-                        options,
-                    ),
-            );
-            return target;
-        }
         // IAM
         case 'AWS::IAM::Policy': {
             const policy = new aws.iam.Policy(
@@ -483,157 +127,7 @@ export function mapToAwsResource(
             return policy;
         }
 
-        // Lambda
-        case 'AWS::Lambda::Permission':
-            // TODO: throw on the presence of functionUrlAuthType / principalOrgId?
-            return new aws.lambda.Permission(
-                logicalId,
-                {
-                    action: props.action,
-                    eventSourceToken: props.eventSourceToken,
-                    function: props.functionName,
-                    principal: props.principal,
-                    sourceAccount: props.sourceAccount,
-                    sourceArn: props.sourceArn,
-                    statementId: logicalId,
-                },
-                options,
-            );
-
-        // S3
-        case 'AWS::S3::BucketPolicy':
-            return new aws.s3.BucketPolicy(
-                logicalId,
-                {
-                    bucket: rawProps.Bucket,
-                    policy: rawProps.PolicyDocument,
-                },
-                options,
-            );
-
         default:
             return undefined;
     }
 }
-
-function mapDynamoDBTable(
-    _element: CfnElement,
-    logicalId: string,
-    _typeName: string,
-    _rawProps: any,
-    props: any,
-    options: pulumi.ResourceOptions,
-): aws.dynamodb.Table {
-    function hashKey(schema: any): string | undefined {
-        return schema.find((k: any) => k.keyType === 'HASH')?.attributeName;
-    }
-
-    function rangeKey(schema: any): string | undefined {
-        return schema.find((k: any) => k.keyType === 'RANGE')?.attributeName;
-    }
-
-    const attributes = props.attributeDefinitions?.map((attr: any) => ({
-        name: attr.attributeName,
-        type: attr.attributeType,
-    }));
-
-    const globalSecondaryIndexes = props.globalSecondaryIndexes?.map((index: any) => ({
-        hashKey: hashKey(index.keySchema),
-        name: index.indexName,
-        nonKeyAttributes: props.projection.nonKeyAttributes,
-        projectionType: props.projection.projectionType,
-        rangeKey: rangeKey(index.keySchema),
-        readCapacity: props.provisionedThroughput?.readCapacityUnits,
-        writeCapacity: props.provisionedThroughput?.writeCapacityUnits,
-    }));
-
-    const localSecondaryIndexes = props.localSecondaryIndexes?.map((index: any) => ({
-        name: index.indexName,
-        nonKeyAttributes: index.projection.nonKeyAttributes,
-        projectionType: index.projection.projectionType,
-        rangeKey: rangeKey(index.keySchema),
-    }));
-
-    const pointInTimeRecovery = maybe(props.pointInTimeRecoverySpecification, (spec) => ({
-        enabled: spec.pointInTimeRecoveryEnabled,
-    }));
-
-    const serverSideEncryption = maybe(props.sSESpecification, (spec) => ({
-        enabled: spec.sSEEnabled,
-        kmsKeyArn: spec.kMSMasterKeyId,
-    }));
-
-    return new aws.dynamodb.Table(
-        logicalId,
-        {
-            attributes: attributes,
-            billingMode: props.billingMode,
-            globalSecondaryIndexes: globalSecondaryIndexes,
-            hashKey: hashKey(props.keySchema),
-            localSecondaryIndexes: localSecondaryIndexes,
-            name: props.tableName,
-            pointInTimeRecovery: pointInTimeRecovery,
-            rangeKey: rangeKey(props.keySchema),
-            readCapacity: props.provisionedThroughput?.readCapacityUnits,
-            serverSideEncryption: serverSideEncryption,
-            streamEnabled: props.streamSpecification !== undefined,
-            streamViewType: props.streamSpecification?.streamViewType,
-            tableClass: props.tableClass,
-            tags: tags(props.tags),
-            ttl: props.timeToLiveSpecification,
-            writeCapacity: props.provisionedThroughput?.writeCapacityUnits,
-        },
-        options,
-    );
-}
-
-function stickiness(targetGroupAttributes: any): pulumi.Input<aws.types.input.lb.TargetGroupStickiness> | undefined {
-    if (targetGroupAttributes === undefined) {
-        return undefined;
-    }
-
-    const enabled = targetGroupAttributes['stickiness.enabled']
-        ? JSON.parse(targetGroupAttributes['stickiness.enabled'])
-        : false;
-    if (!enabled) {
-        return undefined;
-    }
-
-    let cookieDuration = undefined;
-    if ('stickiness.app_cookie.duration_seconds' in targetGroupAttributes) {
-        cookieDuration = targetGroupAttributes['stickiness.app_cookie.duration_seconds'];
-    } else if ('stickiness.lb_cookie.duration_seconds' in targetGroupAttributes) {
-        cookieDuration = targetGroupAttributes['stickiness.lb_cookie.duration_seconds'];
-    }
-    return {
-        enabled: enabled,
-        type: maybeTargetGroupAttribute(targetGroupAttributes, 'stickiness.type'),
-        cookieName: maybeTargetGroupAttribute(targetGroupAttributes, 'stickiness.app_cookie.cookie_name'),
-        cookieDuration: cookieDuration,
-    };
-}
-
-function maybeTargetGroupAttribute(targetGroupAttributes: any, key: string): any {
-    if (targetGroupAttributes === undefined) {
-        return undefined;
-    }
-
-    let val = undefined;
-    if (key in targetGroupAttributes) {
-        val = targetGroupAttributes[key];
-    }
-    return val;
-}
-
-function targetGroupAttributesMap(targetGroupAttributes: any) {
-    if (targetGroupAttributes === undefined) {
-        return undefined;
-    }
-
-    const attrsMap: { [name: string]: any } = {};
-    const attrs = targetGroupAttributes as Array<any>;
-    for (const attr of attrs) {
-        attrsMap[attr.key] = attr.value;
-    }
-    return attrsMap;
-}
diff --git a/src/stack.ts b/src/stack.ts
index 8625a30..fda9d22 100644
--- a/src/stack.ts
+++ b/src/stack.ts
@@ -36,7 +36,7 @@ import { mapToAwsResource } from './aws-resource-mappings';
 import { CloudFormationResource, getDependsOn } from './cfn';
 import { attributePropertyName, mapToCfnResource } from './cfn-resource-mappings';
 import { GraphBuilder } from './graph';
-import { CdkConstruct, JSII_RUNTIME_SYMBOL, ResourceMapping, firstToLower, getFqn } from './interop';
+import { CdkConstruct, JSII_RUNTIME_SYMBOL, ResourceMapping, getFqn } from './interop';
 import { OutputRepr, OutputMap } from './output-map';
 import { parseSub } from './sub';
 import { zipDirectory } from './zip';
@@ -580,8 +580,8 @@ class StackConverter extends ArtifactConverter {
     private resolveIntrinsic(fn: string, params: any) {
         switch (fn) {
             case 'Fn::GetAtt': {
-                debug(`Fn::GetAtt(${params[0]}, ${firstToLower(params[1])})`);
-                return this.resolveAtt(params[0], firstToLower(params[1]));
+                debug(`Fn::GetAtt(${params[0]}, ${params[1]})`);
+                return this.resolveAtt(params[0], params[1]);
             }
 
             case 'Fn::Join':


Test patch:
diff --git a/examples/examples_test.go b/examples/examples_test.go
index 91ceba7..f586d6d 100644
--- a/examples/examples_test.go
+++ b/examples/examples_test.go
@@ -46,6 +46,9 @@ func getBaseOptions(t *testing.T) integration.ProgramTestOptions {
 			"aws:region":        envRegion,
 			"aws-native:region": envRegion,
 		},
+		// some flakiness in some resource creation
+		// @see https://github.com/pulumi/pulumi-aws-native/issues/1714
+		RetryFailedSteps:     true,
 		ExpectRefreshChanges: true,
 		SkipRefresh:          true,
 		Quick:                true,
diff --git a/tests/aws-resource-mappings.test.ts b/tests/aws-resource-mappings.test.ts
index eeced9b..670ae3e 100644
--- a/tests/aws-resource-mappings.test.ts
+++ b/tests/aws-resource-mappings.test.ts
@@ -5,8 +5,22 @@ import * as aws from '@pulumi/aws';
 
 jest.mock('@pulumi/aws', () => {
     return {
-        autoscaling: {
-            Group: jest.fn().mockImplementation(() => {
+        apigatewayv2: {
+            Integration: jest.fn().mockImplementation(() => {
+                return {};
+            }),
+        },
+        iam: {
+            Policy: jest.fn().mockImplementation(() => {
+                return {};
+            }),
+            UserPolicyAttachment: jest.fn().mockImplementation(() => {
+                return {};
+            }),
+            RolePolicyAttachment: jest.fn().mockImplementation(() => {
+                return {};
+            }),
+            GroupPolicyAttachment: jest.fn().mockImplementation(() => {
                 return {};
             }),
         },
@@ -22,13 +36,99 @@ beforeAll(() => {
 });
 
 describe('AWS Resource Mappings', () => {
-    test('maps autoscaling.Group props', () => {
+    test('maps iam.Policy', () => {
+        // GIVEN
+        const cfnType = 'AWS::IAM::Policy';
+        const logicalId = 'my-resource';
+        const cfnProps = {
+            PolicyDocument: {
+                Version: '2012-10-17',
+                Statement: [
+                    {
+                        Effect: 'Allow',
+                        Action: ['cloudformation:Describe*', 'cloudformation:List*', 'cloudformation:Get*'],
+                        Resource: '*',
+                    },
+                ],
+            },
+            Groups: ['my-group'],
+            Roles: ['my-role'],
+            Users: ['my-user'],
+        };
+        // WHEN
+        mapToAwsResource(
+            new CfnResource(new Stack(), logicalId, {
+                type: cfnType,
+                properties: cfnProps,
+            }),
+            logicalId,
+            cfnType,
+            cfnProps,
+            {},
+        );
+        // THEN
+        expect(aws.iam.Policy).toHaveBeenCalledWith(
+            logicalId,
+            expect.objectContaining({
+                policy: {
+                    Version: '2012-10-17',
+                    Statement: [
+                        {
+                            Effect: 'Allow',
+                            Action: ['cloudformation:Describe*', 'cloudformation:List*', 'cloudformation:Get*'],
+                            Resource: '*',
+                        },
+                    ],
+                },
+            }),
+            {},
+        );
+        expect(aws.iam.GroupPolicyAttachment).toHaveBeenCalledWith(
+            `${logicalId}-0`,
+            expect.objectContaining({
+                group: 'my-group',
+                policyArn: undefined,
+            }),
+            {},
+        );
+        expect(aws.iam.RolePolicyAttachment).toHaveBeenCalledWith(
+            `${logicalId}-0`,
+            expect.objectContaining({
+                role: 'my-role',
+                policyArn: undefined,
+            }),
+            {},
+        );
+        expect(aws.iam.UserPolicyAttachment).toHaveBeenCalledWith(
+            `${logicalId}-0`,
+            expect.objectContaining({
+                user: 'my-user',
+                policyArn: undefined,
+            }),
+            {},
+        );
+    });
+    test('maps apigatewayv2.Integration', () => {
         // GIVEN
-        const cfnType = 'AWS::AutoScaling::AutoScalingGroup';
+        const cfnType = 'AWS::ApiGatewayV2::Integration';
         const logicalId = 'my-resource';
         const cfnProps = {
-            TargetGroupARNs: ['arn'],
-            VPCZoneIdentifier: ['ids'],
+            Description: 'Lambda Integration',
+            IntegrationType: 'AWS_PROXY',
+            RequestParameters: {
+                'append:header.header1': '$context.requestId',
+            },
+            ResponseParameters: {
+                '200': {
+                    ResponseParameters: [
+                        {
+                            Source: 'headervalue',
+                            Destination: 'append:header.header2',
+                        },
+                    ],
+                },
+            },
+            TlsConfig: { ServerNameToVerify: 'example.com' },
         };
         // WHEN
         mapToAwsResource(
@@ -42,11 +142,25 @@ describe('AWS Resource Mappings', () => {
             {},
         );
         // THEN
-        expect(aws.autoscaling.Group).toHaveBeenCalledWith(
+        expect(aws.apigatewayv2.Integration).toHaveBeenCalledWith(
             logicalId,
             expect.objectContaining({
-                targetGroupArns: ['arn'],
-                vpcZoneIdentifiers: ['ids'],
+                description: 'Lambda Integration',
+                integrationType: 'AWS_PROXY',
+                requestParameters: {
+                    'append:header.header1': '$context.requestId',
+                },
+                responseParameters: {
+                    '200': {
+                        ResponseParameters: [
+                            {
+                                Source: 'headervalue',
+                                Destination: 'append:header.header2',
+                            },
+                        ],
+                    },
+                },
+                tlsConfig: { insecureSkipVerification: true },
             }),
             {},
         );
diff --git a/tests/cdk-resource.test.ts b/tests/cdk-resource.test.ts
new file mode 100644
index 0000000..436a052
--- /dev/null
+++ b/tests/cdk-resource.test.ts
@@ -0,0 +1,163 @@
+import * as pulumi from '@pulumi/pulumi';
+import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
+import { TableArgs } from '@pulumi/aws-native/dynamodb';
+import { Stack } from '../src/stack';
+import { Construct } from 'constructs';
+import { MockCallArgs, MockResourceArgs } from '@pulumi/pulumi/runtime';
+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';
+
+function setMocks(assertFn: (args: MockResourceArgs) => void) {
+    pulumi.runtime.setMocks(
+        {
+            call: (_args: MockCallArgs) => {
+                return {};
+            },
+            newResource: (args: MockResourceArgs): { id: string; state: any } => {
+                switch (args.type) {
+                    case 'cdk:index:Stack':
+                        return { id: '', state: {} };
+                    case 'cdk:construct:TestStack':
+                        return { id: '', state: {} };
+                    case 'cdk:index:Component':
+                        return { id: '', state: {} };
+                    default:
+                        assertFn(args);
+                        return {
+                            id: args.name + '_id',
+                            state: args.inputs,
+                        };
+                }
+            },
+        },
+        'project',
+        'stack',
+        false,
+    );
+}
+
+function testStack(fn: (scope: Construct) => void, done: any) {
+    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');
+    s.urn.apply(() => done());
+}
+
+describe('CDK Construct tests', () => {
+    // DynamoDB table was previously mapped to the `aws` provider
+    // otherwise this level of testing wouldn't be necessary.
+    // We also don't need to do this type of testing for _every_ resource
+    test('dynamodb table', (done) => {
+        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);
+            }
+        });
+        testStack((scope) => {
+            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: {
+                    name: 'sort',
+                    type: dynamodb.AttributeType.STRING,
+                },
+                partitionKey: {
+                    name: 'pk',
+                    type: dynamodb.AttributeType.STRING,
+                },
+            });
+            table.addLocalSecondaryIndex({
+                indexName: 'lsi',
+                sortKey: {
+                    type: dynamodb.AttributeType.STRING,
+                    name: 'lsiSort',
+                },
+            });
+            table.addGlobalSecondaryIndex({
+                indexName: 'gsi',
+                partitionKey: {
+                    name: 'gsiKey',
+                    type: dynamodb.AttributeType.STRING,
+                },
+            });
+        }, done);
+    });
+
+    test('LoadBalancer dnsName attribute does not throw', (done) => {
+        setMocks((_args) => {});
+        testStack((scope) => {
+            const vpc = new Vpc(scope, 'vpc');
+            const alb = new ApplicationLoadBalancer(scope, 'alb', {
+                vpc,
+            });
+
+            new aws_ssm.StringParameter(scope, '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,
+            });
+        }, done);
+    });
+});