Repository: pulumi/pulumi-cdk (ts)

Issue:
LoadBalancer "name" cannot be longer than 32 characters
Question from Community Slack:

> I have a question about names and length of names. When set up an sample infrastructure with Pulumi CDK I got this error message when running pulumi preview:
> 
> ```
> Diagnostics:
>   aws:lb:LoadBalancer (loadbalancedserviceLB1BE8EDCE):
>     error: aws:lb/loadBalancer:LoadBalancer resource 'loadbalancedserviceLB1BE8EDCE' has a problem: "name" cannot be longer than 32 characters: "loadbalancedserviceLB1BE8EDCE-403d9b7". Examine values at 'LoadBalancer.Name'.
> ```
> 
> The corresponding code looks like this:
> 
> ```ts
> const lbservice = new ApplicationLoadBalancedFargateService(this, 'loadbalanced-service', {
>   cluster,
>   taskDefinition: taskdef,
>   desiredCount: 2,
>   serviceName: 'my-service',
>   circuitBreaker: { rollback: true },
>   publicLoadBalancer: true,
>   listenerPort: webserverPort,
> });
> ```
> 
> The name part is not an issue with regular AWS CDK, and Pulumi generates a different (shorter) name than AWS CDK, but still too short compared to name lengths allowed by CloudFormation, it seems. The question is really what to think and consider in terms of name issues here, what would be preferred approaches besides keeping construct id values short - and how many levels will this work out with?


I suspect that the issue is that we should be passing name through explicitly in the mapping linked below if provided (which it will always be I believe?) so that Pulumi doesn't apply it's own autonaming here.  If you open an issue on this - we can look into whether that is indeed the right resolution.
https://github.com/pulumi/pulumi-cdk/blob/342f7d5bbbc0d272c7e1616ae108b04c478add5d/src/aws-resource-mappings.ts#L210
Long logical ID (>100 characeters) breaks AWS::Lambda::Permission 
### What happened?

My CDK stack creates an AWS::Lambda::Permission resource with a long logical ID:

StockBotApiGatewayGETeventualexecutionsexecutionIdhistoryexecutionseventsintegrationPermission3C1EE9F9

This then results in an error:

```
  aws:lambda:Permission (StockBotApiGatewayGETeventualexecutionsexecutionIdhistoryexecutionseventsintegrationPermission3C1EE9F9):
    error: aws:lambda/permission:Permission resource 'StockBotApiGatewayGETeventualexecutionsexecutionIdhistoryexecutionseventsintegrationPermission3C1EE9F9' has a problem: expected length of statement_id to be in the range (1 - 100), got StockBotApiGatewayGETeventualexecutionsexecutionIdhistoryexecutionseventsintegrationPermission3C1EE9F9. Examine values at 'Permission.StatementId'.
```

### Expected Behavior

Pulumi should probably truncate the name to avoid this problem.

### Steps to reproduce

I'm not 100% sure where my code is creating this. I believe it's for when I configure an API GW v2 to trigger my Lambda:

```ts
import { HttpApi } from "@aws-cdk/aws-apigatewayv2-alpha";
import { HttpIamAuthorizer } from "@aws-cdk/aws-apigatewayv2-authorizers-alpha";
import { HttpLambdaIntegration } from "@aws-cdk/aws-apigatewayv2-integrations-alpha";

const handler= new aws_lambda.Function(this, "F", { .. });

new HttpApi(this, "Gateway", {
  apiName: `my-api`,
  defaultIntegration: new HttpLambdaIntegration("default", handler),
});
```

### Output of `pulumi about`

```
CLI
Version      3.53.1
Go Version   go1.19.5
Go Compiler  gc

Plugins
NAME    VERSION
aws     5.28.0
nodejs  unknown

Host
OS       darwin
Version  13.1
Arch     arm64

This project is written in nodejs: executable='/Users/samgoodwin/.nvm/versions/node/v16.14.2/bin/node' version='v16.14.2'

Current Stack: sam-g/stock-bot/stock-bot

TYPE                 URN
pulumi:pulumi:Stack  urn:pulumi:stock-bot::stock-bot::pulumi:pulumi:Stack::stock-bot-stock-bot


Found no pending operations associated with stock-bot

Backend
Name           pulumi.com
URL            https://app.pulumi.com/sam-g
User           sam-g
Organizations  sam-g
```

### Additional context

_No response_

### Contributing

Vote on this issue by adding a 👍 reaction. 
To contribute a fix for this issue, leave a comment (and link to your pull request, if you've opened one already). 


Pull request:
Enable `autoTrim` behavior by default
This PR enables the aws-native `AutoNaming.autoTrim` feature by default.
It does this by creating an aws-native provider by default if the user
does not provide one.

I've updated tests to remove the workarounds.

**Alternatives**

1. We could require that the user configure this themselves.
2. Is there a way to set config values programatically?

closes #62, closes #70

Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index 2146735..946ae8a 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -12,6 +12,9 @@ module.exports = {
   rules: {
     // disabling this since it is used everywhere in this repo
     "@typescript-eslint/no-explicit-any": "off",
-    "@typescript-eslint/no-unused-vars": "warn"
+    "@typescript-eslint/no-unused-vars": ["warn", {
+        "varsIgnorePattern": "^[_]",
+        "argsIgnorePattern": "^[_]"
+    }]
   }
 };
diff --git a/README.md b/README.md
index faf3b30..a812723 100644
--- a/README.md
+++ b/README.md
@@ -94,6 +94,34 @@ Try the workshop at https://apprunnerworkshop.com
 Read the docs at https://docs.aws.amazon.com/apprunner
 ```
 
+## AWS Cloud Control AutoNaming Config
+
+Sometimes CDK constructs can create resource names that are too long for the
+[AWS Cloud Control provider](https://www.pulumi.com/registry/packages/aws-native/).
+When this happens you can configure the `autoTrim` feature to have the generated
+names be automatically trimmed to fit within the name requirements. If you are
+not configuring your own `aws-native` provider then this feature is enabled by
+default. If you _are_ configuring your own `aws-native` provider then you will
+have to enable this.
+
+```ts
+const nativeProvider = new aws_native.Provider('cdk-native-provider', {
+  region: 'us-east-2',
+  autoNaming: {
+    autoTrim: true,
+    randomSuffixMinLength: 7,
+  },
+});
+const app = new pulumicdk.App('app', (scope: pulumicdk.App): pulumicdk.AppOutputs => {
+    const stack = new AppRunnerStack('teststack');
+    return {
+        url: stack.url,
+    };
+}, {
+  providers: [ nativeProvider ],
+});
+```
+
 ## Bootstrapping
 
 CDK has the concept of [bootstrapping](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html)
diff --git a/examples/alb/index.ts b/examples/alb/index.ts
index 22af2cb..51922be 100644
--- a/examples/alb/index.ts
+++ b/examples/alb/index.ts
@@ -3,7 +3,6 @@ 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<string>;
@@ -28,15 +27,11 @@ class AlbStack extends pulumicdk.Stack {
             port: 80,
         });
 
-        const tg = listener.addTargets('Target', {
+        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/integration/apigateway/sfn-api.ts b/integration/apigateway/sfn-api.ts
index 58ec218..8af44cc 100644
--- a/integration/apigateway/sfn-api.ts
+++ b/integration/apigateway/sfn-api.ts
@@ -1,4 +1,3 @@
-import * as iam from 'aws-cdk-lib/aws-iam';
 import { Construct } from 'constructs';
 import * as sfn from 'aws-cdk-lib/aws-stepfunctions';
 import * as apigw from 'aws-cdk-lib/aws-apigateway';
@@ -15,13 +14,8 @@ export class SfnApi extends Construct {
             stateMachineType: sfn.StateMachineType.EXPRESS,
         });
 
-        // TODO[pulumi/pulumi-cdk#62] The auto created role has too long name
-        const role = new iam.Role(this, 'StartRole', {
-            assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'),
-        });
         const api = new apigw.StepFunctionsRestApi(this, 'StepFunctionsRestApi', {
             deploy: false,
-            role,
             stateMachine: stateMachine,
             headers: true,
             path: false,
diff --git a/src/cfn-resource-mappings.ts b/src/cfn-resource-mappings.ts
index 480135d..b334615 100644
--- a/src/cfn-resource-mappings.ts
+++ b/src/cfn-resource-mappings.ts
@@ -117,18 +117,7 @@ export function mapToCfnResource(
             const pType = pulumiTypeName(typeName);
             const awsModule = aws as any;
 
-            // Workaround until TODO[pulumi/pulumi-aws-native#1816] is resolved.
-            // Not expected to be exposed to users
-            let name = logicalId;
-            const maxNameLength = process.env.PULUMI_CDK_EXPERIMENTAL_MAX_NAME_LENGTH;
-            if (maxNameLength) {
-                const maxLength = parseInt(maxNameLength, 10);
-                if (name.length > maxLength) {
-                    name = name.substring(0, maxLength);
-                }
-            }
-
-            return new awsModule[mName][pType](name, props, options);
+            return new awsModule[mName][pType](logicalId, props, options);
         }
     }
 }
diff --git a/src/converters/app-converter.ts b/src/converters/app-converter.ts
index 25c60c6..22cc65f 100644
--- a/src/converters/app-converter.ts
+++ b/src/converters/app-converter.ts
@@ -18,7 +18,7 @@ import {
 } from '@pulumi/aws-native';
 import { mapToAwsResource } from '../aws-resource-mappings';
 import { attributePropertyName, mapToCfnResource } from '../cfn-resource-mappings';
-import { CloudFormationMapping, CloudFormationResource, getDependsOn } from '../cfn';
+import { CloudFormationResource, getDependsOn } from '../cfn';
 import { OutputMap, OutputRepr } from '../output-map';
 import { parseSub } from '../sub';
 import { getPartition } from '@pulumi/aws-native/getPartition';
@@ -475,7 +475,7 @@ export class StackConverter extends ArtifactConverter {
 
             case 'Fn::Sub':
                 return lift((params) => {
-                    const [template, vars] =
+                    const [template, _vars] =
                         typeof params === 'string' ? [params, undefined] : [params[0] as string, params[1]];
 
                     const parts: string[] = [];
diff --git a/src/interop.ts b/src/interop.ts
index 919fbe9..6e99fb5 100644
--- a/src/interop.ts
+++ b/src/interop.ts
@@ -1,4 +1,4 @@
-// Copyright 2016-2022, Pulumi Corporation.
+// Copyright 2016-2024, Pulumi Corporation.
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -38,18 +38,27 @@ export function firstToLower(str: string) {
 export function normalize(value: any, cfnType?: string, pulumiProvider?: PulumiProvider): any {
     if (!value) return value;
 
+    if (value instanceof Promise) {
+        return pulumi.output(value).apply(v => normalize(v, cfnType, pulumiProvider));
+    }
+
+    if (pulumi.Output.isInstance(value)) {
+        return value.apply(v => normalize(v, cfnType, pulumiProvider));
+    }
+
     if (Array.isArray(value)) {
         const result: any[] = [];
         for (let i = 0; i < value.length; i++) {
-            result[i] = normalize(value[i], cfnType);
+            result[i] = normalize(value[i], cfnType, pulumiProvider);
         }
         return result;
     }
 
-    if (typeof value !== 'object' || pulumi.Output.isInstance(value) || value instanceof Promise) {
+    if (typeof value !== 'object') {
         return value;
     }
 
+    // The remaining case is the object type, representing either Maps or Object types with known field types in Pulumi.
     const result: any = {};
     Object.entries(value).forEach(([k, v]) => {
         result[toSdkName(k)] = normalizeObject([k], v, cfnType, pulumiProvider);
diff --git a/src/pulumi-metadata.ts b/src/pulumi-metadata.ts
index 284f32c..c4ee574 100644
--- a/src/pulumi-metadata.ts
+++ b/src/pulumi-metadata.ts
@@ -1,10 +1,25 @@
+// Copyright 2016-2024, Pulumi Corporation.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// 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 path from 'path';
-import * as pulumi from '@pulumi/pulumi';
 import { toSdkName, typeToken } from './naming';
 // eslint-disable-next-line @typescript-eslint/no-var-requires
 const pulumiMetadata = require(path.join(__dirname, '../schemas/aws-native-metadata.json'));
 import { PulumiProvider } from './types';
 import { debug } from '@pulumi/pulumi/log';
+import * as pulumi from '@pulumi/pulumi';
+
 
 export class UnknownCfnType extends Error {
     constructor(cfnType: string) {
@@ -269,6 +284,15 @@ export function getNativeType(
  */
 export function normalizeObject(key: string[], value: any, cfnType?: string, pulumiProvider?: PulumiProvider): any {
     if (!value) return value;
+
+    if (value instanceof Promise) {
+        return pulumi.output(value).apply(v => normalizeObject(key, v, cfnType, pulumiProvider));
+    }
+
+    if (pulumi.Output.isInstance(value)) {
+        return value.apply(v => normalizeObject(key, v, cfnType, pulumiProvider));
+    }
+
     if (Array.isArray(value)) {
         const result: any[] = [];
         for (let i = 0; i < value.length; i++) {
@@ -277,10 +301,11 @@ export function normalizeObject(key: string[], value: any, cfnType?: string, pul
         return result;
     }
 
-    if (typeof value !== 'object' || pulumi.Output.isInstance(value) || value instanceof Promise) {
+    if (typeof value !== 'object') {
         return value;
     }
 
+    // The remaining case is the actual object type.
     const result: any = {};
     if (cfnType) {
         try {
diff --git a/src/stack.ts b/src/stack.ts
index c6533dd..260067b 100644
--- a/src/stack.ts
+++ b/src/stack.ts
@@ -19,6 +19,7 @@ import { PulumiSynthesizer, PulumiSynthesizerBase } from './synthesizer';
 import { AwsCdkCli, ICloudAssemblyDirectoryProducer } from '@aws-cdk/cli-lib-alpha';
 import { CdkConstruct } from './interop';
 import { makeUniqueId } from './cdk-logical-id';
+import * as native from '@pulumi/aws-native';
 
 export type AppOutputs = { [outputId: string]: pulumi.Output<any> };
 
@@ -93,7 +94,10 @@ export class App
     private appProps?: cdk.AppProps;
 
     constructor(id: string, createFunc: (scope: App) => void | AppOutputs, props?: AppResourceOptions) {
-        super('cdk:index:App', id, props?.appOptions, props);
+        super('cdk:index:App', id, props?.appOptions, {
+            ...props,
+            providers: createDefaultNativeProvider(props?.providers),
+        });
         this.appOptions = props?.appOptions;
         this.createFunc = createFunc;
         this.component = this;
@@ -326,3 +330,29 @@ function generateAppId(): string {
         .replace(/[^a-z0-9-.]/g, '-')
         .slice(-17);
 }
+
+/**
+ * If the user has not provided the aws-native provider, we will create one by default in order
+ * to enable the autoNaming feature.
+ */
+function createDefaultNativeProvider(
+    providers?: pulumi.ProviderResource[] | Record<string, pulumi.ProviderResource>,
+): pulumi.ProviderResource[] {
+    // This matches the logic found in aws-native. If all of these are undefined the provider
+    // will throw an error
+    const region = native.config.region ?? process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION!;
+
+    const newProviders = providers && !Array.isArray(providers) ? Object.values(providers) : providers ?? [];
+    if (!newProviders.find((p) => native.Provider.isInstance(p))) {
+        newProviders.push(
+            new native.Provider('cdk-aws-native', {
+                region: region as native.Region,
+                autoNaming: {
+                    randomSuffixMinLength: 7,
+                    autoTrim: true,
+                },
+            }),
+        );
+    }
+    return newProviders;
+}


Test patch:
diff --git a/integration/examples_nodejs_test.go b/integration/examples_nodejs_test.go
index 5992ac8..77a8262 100644
--- a/integration/examples_nodejs_test.go
+++ b/integration/examples_nodejs_test.go
@@ -148,8 +148,6 @@ func TestCustomResource(t *testing.T) {
 	test := getJSBaseOptions(t).
 		With(integration.ProgramTestOptions{
 			Dir: filepath.Join(getCwd(t), "custom-resource"),
-			// Workaround until TODO[pulumi/pulumi-aws-native#1816] is resolved.
-			Env: []string{"PULUMI_CDK_EXPERIMENTAL_MAX_NAME_LENGTH=56"},
 			ExtraRuntimeValidation: func(t *testing.T, stack integration.RuntimeValidationStackInfo) {
 				t.Logf("Outputs: %v", stack.Outputs)
 				url := stack.Outputs["websiteUrl"].(string)
diff --git a/tests/basic.test.ts b/tests/basic.test.ts
index 1970347..23e57c4 100644
--- a/tests/basic.test.ts
+++ b/tests/basic.test.ts
@@ -12,6 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 import * as pulumi from '@pulumi/pulumi';
+import * as aws from '@pulumi/aws';
+import * as native from '@pulumi/aws-native';
 import * as s3 from 'aws-cdk-lib/aws-s3';
 import * as output from '../src/output';
 import { setMocks, testApp } from './mocks';
@@ -20,15 +22,23 @@ import { Vpc } from 'aws-cdk-lib/aws-ec2';
 import { aws_ssm } from 'aws-cdk-lib';
 import { Construct } from 'constructs';
 
+beforeAll(() => {
+    process.env.AWS_REGION = 'us-east-2';
+});
+afterAll(() => {
+    process.env.AWS_REGION = undefined;
+});
+
 describe('Basic tests', () => {
-    setMocks();
     test('Checking single resource registration', async () => {
+        setMocks();
         await testApp((scope: Construct) => {
             new s3.Bucket(scope, 'MyFirstBucket', { versioned: true });
         });
     });
 
     test('LoadBalancer dnsName attribute does not throw', async () => {
+        setMocks();
         await testApp((scope: Construct) => {
             const vpc = new Vpc(scope, 'vpc');
             const alb = new ApplicationLoadBalancer(scope, 'alb', {
@@ -44,9 +54,163 @@ describe('Basic tests', () => {
         });
     });
     test('Supports Output<T>', async () => {
+        setMocks();
         const o = pulumi.output('the-bucket-name');
         await testApp((scope: Construct) => {
             new s3.Bucket(scope, 'MyFirstBucket', { bucketName: output.asString(o) });
         });
     });
+
+    test('Creates native provider by default', async () => {
+        const resources: pulumi.runtime.MockResourceArgs[] = [];
+        setMocks(resources);
+        await testApp((scope: Construct) => {
+            new s3.Bucket(scope, 'MyFirstBucket', { versioned: true });
+        });
+        const providers = resources.filter((r) => r.type === 'pulumi:providers:aws-native');
+        expect(providers).toHaveLength(1);
+        expect(providers[0]).toEqual(
+            expect.objectContaining({
+                inputs: {
+                    autoNaming: '{"randomSuffixMinLength":7,"autoTrim":true}',
+                    region: 'us-east-2',
+                    skipCredentialsValidation: 'true',
+                    skipGetEc2Platforms: 'true',
+                    skipMetadataApiCheck: 'true',
+                    skipRegionValidation: 'true',
+                },
+                name: 'cdk-aws-native',
+                provider: '',
+                type: 'pulumi:providers:aws-native',
+            }),
+        );
+    });
+
+    test('Creates native provider when classic provided', async () => {
+        const resources: pulumi.runtime.MockResourceArgs[] = [];
+        setMocks(resources);
+        await testApp(
+            (scope: Construct) => {
+                new s3.Bucket(scope, 'MyFirstBucket', { versioned: true });
+            },
+            {
+                providers: [new aws.Provider('test-aws', {})],
+            },
+        );
+        const providers = resources.filter((r) => r.type === 'pulumi:providers:aws-native');
+        expect(providers).toHaveLength(1);
+        expect(providers[0]).toEqual(
+            expect.objectContaining({
+                inputs: {
+                    autoNaming: '{"randomSuffixMinLength":7,"autoTrim":true}',
+                    region: 'us-east-2',
+                    skipCredentialsValidation: 'true',
+                    skipGetEc2Platforms: 'true',
+                    skipMetadataApiCheck: 'true',
+                    skipRegionValidation: 'true',
+                },
+                name: 'cdk-aws-native',
+                provider: '',
+                type: 'pulumi:providers:aws-native',
+            }),
+        );
+    });
+
+    test('Creates native provider when classic provided object', async () => {
+        const resources: pulumi.runtime.MockResourceArgs[] = [];
+        setMocks(resources);
+        await testApp(
+            (scope: Construct) => {
+                new s3.Bucket(scope, 'MyFirstBucket', { versioned: true });
+            },
+            {
+                providers: {
+                    aws: new aws.Provider('test-aws', {}),
+                },
+            },
+        );
+        const providers = resources.filter((r) => r.type === 'pulumi:providers:aws-native');
+        expect(providers).toHaveLength(1);
+        expect(providers[0]).toEqual(
+            expect.objectContaining({
+                inputs: {
+                    autoNaming: '{"randomSuffixMinLength":7,"autoTrim":true}',
+                    region: 'us-east-2',
+                    skipCredentialsValidation: 'true',
+                    skipGetEc2Platforms: 'true',
+                    skipMetadataApiCheck: 'true',
+                    skipRegionValidation: 'true',
+                },
+                name: 'cdk-aws-native',
+                provider: '',
+                type: 'pulumi:providers:aws-native',
+            }),
+        );
+    });
+
+    test('does not create native provider when one is provided', async () => {
+        const resources: pulumi.runtime.MockResourceArgs[] = [];
+        setMocks(resources);
+        await testApp(
+            (scope: Construct) => {
+                new s3.Bucket(scope, 'MyFirstBucket', { versioned: true });
+            },
+            {
+                providers: [
+                    new native.Provider('test-native', {
+                        region: 'us-west-2',
+                    }),
+                ],
+            },
+        );
+        const providers = resources.filter((r) => r.type === 'pulumi:providers:aws-native');
+        expect(providers).toHaveLength(1);
+        expect(providers[0]).toEqual(
+            expect.objectContaining({
+                inputs: {
+                    region: 'us-west-2',
+                    skipCredentialsValidation: 'true',
+                    skipGetEc2Platforms: 'true',
+                    skipMetadataApiCheck: 'true',
+                    skipRegionValidation: 'true',
+                },
+                name: 'test-native',
+                provider: '',
+                type: 'pulumi:providers:aws-native',
+            }),
+        );
+    });
+
+    test('does not create native provider when one is provided object', async () => {
+        const resources: pulumi.runtime.MockResourceArgs[] = [];
+        setMocks(resources);
+        await testApp(
+            (scope: Construct) => {
+                new s3.Bucket(scope, 'MyFirstBucket', { versioned: true });
+            },
+            {
+                providers: {
+                    'aws-native': new native.Provider('test-native', {
+                        region: 'us-west-2',
+                    }),
+                },
+            },
+        );
+        const providers = resources.filter((r) => r.type === 'pulumi:providers:aws-native');
+        expect(providers).toHaveLength(1);
+        expect(providers[0]).toEqual(
+            expect.objectContaining({
+                inputs: {
+                    region: 'us-west-2',
+                    skipCredentialsValidation: 'true',
+                    skipGetEc2Platforms: 'true',
+                    skipMetadataApiCheck: 'true',
+                    skipRegionValidation: 'true',
+                },
+                name: 'test-native',
+                provider: '',
+                type: 'pulumi:providers:aws-native',
+            }),
+        );
+    });
 });
diff --git a/tests/cdk-resource.test.ts b/tests/cdk-resource.test.ts
index 19d00cf..c26ec91 100644
--- a/tests/cdk-resource.test.ts
+++ b/tests/cdk-resource.test.ts
@@ -13,9 +13,13 @@ import { Construct } from 'constructs';
 describe('CDK Construct tests', () => {
     let resources: MockResourceArgs[] = [];
     beforeAll(() => {
+        process.env.AWS_REGION = 'us-east-2';
         resources = [];
         setMocks(resources);
     });
+    afterAll(() => {
+        process.env.AWS_REGION = undefined;
+    });
     // 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
diff --git a/tests/interop.test.ts b/tests/interop.test.ts
index 9609de3..b022c82 100644
--- a/tests/interop.test.ts
+++ b/tests/interop.test.ts
@@ -1,4 +1,5 @@
 import { normalize } from '../src/interop';
+import * as pulumi from '@pulumi/pulumi';
 
 beforeEach(() => {
     jest.resetAllMocks();
@@ -339,4 +340,31 @@ describe('normalize', () => {
             },
         });
     });
+
+    test('normalize changes the case of nested properties through an Output eventual type', async () => {
+        const normalized = normalize({
+            'StreamEncryption': pulumi.output({
+                EncryptionType: 'KMS',
+                KeyId: 'alias/aws/kinesis'
+            })
+        });
+
+        const finalValue = await awaitOutput(pulumi.output(normalized));
+
+        expect(finalValue).toEqual({
+            streamEncryption: {
+                encryptionType: 'KMS',
+                keyId: 'alias/aws/kinesis',
+            }
+        });
+    });
 });
+
+function awaitOutput<T>(out: pulumi.Output<T>): Promise<T> {
+    return new Promise((resolve, _reject) => {
+        out.apply(v => {
+            resolve(v);
+            return v;
+        })
+    });
+}
diff --git a/tests/mocks.ts b/tests/mocks.ts
index 802867e..c97a21d 100644
--- a/tests/mocks.ts
+++ b/tests/mocks.ts
@@ -1,6 +1,13 @@
 import * as pulumi from '@pulumi/pulumi';
 import { CdkConstruct } from '../src/interop';
-import { Stack as CdkStack, DockerImageAssetLocation, DockerImageAssetSource, FileAssetLocation, FileAssetSource, ISynthesisSession } from 'aws-cdk-lib/core';
+import {
+    Stack as CdkStack,
+    DockerImageAssetLocation,
+    DockerImageAssetSource,
+    FileAssetLocation,
+    FileAssetSource,
+    ISynthesisSession,
+} from 'aws-cdk-lib/core';
 import { AppComponent, AppOptions } from '../src/types';
 import { MockCallArgs, MockResourceArgs } from '@pulumi/pulumi/runtime';
 import { Construct } from 'constructs';
@@ -28,7 +35,7 @@ export class MockAppComponent extends pulumi.ComponentResource implements AppCom
     }
 }
 
-export async function testApp(fn: (scope: Construct) => void) {
+export async function testApp(fn: (scope: Construct) => void, options?: pulumi.ComponentResourceOptions) {
     class TestStack extends Stack {
         constructor(app: App, id: string) {
             super(app, id, {
@@ -48,9 +55,15 @@ export async function testApp(fn: (scope: Construct) => void) {
         }
     }
 
-    const app = new App('testapp', (scope: App) => {
-        new TestStack(scope, 'teststack');
-    });
+    const app = new App(
+        'testapp',
+        (scope: App) => {
+            new TestStack(scope, 'teststack');
+        },
+        {
+            ...options,
+        },
+    );
     const converter = await app.converter;
     await Promise.all(
         Array.from(converter.stacks.values()).flatMap((stackConverter) => {
@@ -151,8 +164,8 @@ export function setMocks(resources?: MockResourceArgs[]) {
                             ...args.inputs,
                             id: args.inputs.logicalId + '_id',
                             data: {
-                                "DestinationBucketArn": `arn:aws:s3:::${args.inputs.bucketName}`
-                            }
+                                DestinationBucketArn: `arn:aws:s3:::${args.inputs.bucketName}`,
+                            },
                         },
                     };
                 default:
@@ -173,7 +186,6 @@ export function setMocks(resources?: MockResourceArgs[]) {
 }
 
 export class MockSynth extends PulumiSynthesizerBase {
-
     constructor(readonly bucket: string, readonly prefix: string) {
         super();
     }
@@ -195,4 +207,3 @@ export class MockSynth extends PulumiSynthesizerBase {
         throw new Error('Method not implemented.');
     }
 }
-
diff --git a/tests/synthesizer.test.ts b/tests/synthesizer.test.ts
index 6fd6ca4..4539068 100644
--- a/tests/synthesizer.test.ts
+++ b/tests/synthesizer.test.ts
@@ -7,6 +7,13 @@ import { asNetworkMode, asPlatforms } from '../src/synthesizer';
 import { NetworkMode, Platform as DockerPlatform } from '@pulumi/docker-build';
 import { DockerImageAsset, Platform, NetworkMode as Network } from 'aws-cdk-lib/aws-ecr-assets';
 
+beforeAll(() => {
+    process.env.AWS_REGION = 'us-east-2';
+});
+afterAll(() => {
+    process.env.AWS_REGION = undefined;
+});
+
 describe('Synthesizer File Assets', () => {
     test('no assets = no staging resources', async () => {
         const resources: MockResourceArgs[] = [];
@@ -16,6 +23,19 @@ describe('Synthesizer File Assets', () => {
             new CfnBucket(scope, 'Bucket');
         });
         expect(resources).toEqual([
+            expect.objectContaining({
+                inputs: {
+                    autoNaming: '{"randomSuffixMinLength":7,"autoTrim":true}',
+                    region: 'us-east-2',
+                    skipCredentialsValidation: 'true',
+                    skipGetEc2Platforms: 'true',
+                    skipMetadataApiCheck: 'true',
+                    skipRegionValidation: 'true',
+                },
+                name: 'cdk-aws-native',
+                provider: '',
+                type: 'pulumi:providers:aws-native',
+            }),
             expect.objectContaining({
                 name: 'staging-stack-project-stack',
                 type: 'cdk:construct:StagingStack',