Repository: awslabs/aws-solutions-constructs (language: python)

Issue:
aws-alb-lambda: Does not allow ApplicationLoadBalancerProps
description of the bug: 

I am creating a ALbToLambda Stack using the aws-alb-lambda.

I need to specify what subnets the ALB will be created in. 

On the [aws-alb-lambda](https://docs.aws.amazon.com/solutions/latest/constructs/aws-alb-lambda.html) documentation, it states you can specify `ApplicationLoadBalancerProps`, but also states the following `This cannot specify a VPC, it will use the VPC in existingVpc or the VPC created by the construct. .`

I tried to add ApplicationLoadBalancerProps without a vpc so I can specify what subnets I want the ALB to be created in, but I get an error, saying `'vpc' is a required field.`

When I add a vpc, the same as the existing_vpc property. I also get an error: `Specify any existing VPC at the construct level, not within loadBalancerProps.`

My Code:

```
alb_to_lambda = AlbToLambda(
            self,
            f"{STACK_PREFIX}{self.stack_config.function_name}AlbToLambda",
            lambda_function_props=FunctionProps(
                runtime=Runtime.PYTHON_3_9,
                handler=self.stack_config.function_handler,
                code=get_lambda_code(self.stack_config.lambda_code_path),
                function_name=f"{STACK_PREFIX}{self.stack_config.function_name}",
                description=f"{STACK_PREFIX}{self.stack_config.function_description}",
                log_retention=RetentionDays.FIVE_DAYS,
                timeout=Duration.seconds(30),
                environment=self.stack_config.environment,
                dead_letter_queue=dlq,
                memory_size=self.stack_config.memory_size,
            ),
            target_props=elbv2.ApplicationTargetGroupProps(
                target_group_name=f"{STACK_PREFIX}{self.stack_config.function_name}TG",
                health_check=elbv2.HealthCheck(
                    path="/healthcheck", interval=Duration.minutes(5), enabled=True
                ),
            ),
            existing_vpc=vpc,
            listener_props=elbv2.BaseApplicationListenerProps(
                certificates=[certificate],
            ),
            load_balancer_props=elbv2.ApplicationLoadBalancerProps(
                vpc_subnets=subnets,
            ),
            public_api=False,
        )

```



### Reproduction Steps

Try to deploy an AlbToLambda construct using ApplicationLoadBalancerProps as a property




### Error Log

Specify any existing VPC at the construct level, not within loadBalancerProps.




### Environment

  - **CDK CLI Version      :**
  - **CDK Framework Version: 2.143
  - **AWS Solutions Constructs Version     :**
  - **OS                   :**
  - **Language             : Python

### Other

<!-- e.g. detailed explanation, stacktraces, related issues, suggestions on how to fix, links for us to have context, eg. associated pull-request, stackoverflow, gitter, etc -->




--- 

This is :bug: Bug Report



Discussion / hints:
Thanks, we'll take a look
We've got a fix for this, hope to have it pushed out sometime this week.

Fix patch:
diff --git a/source/patterns/@aws-solutions-constructs/core/lib/alb-helper.ts b/source/patterns/@aws-solutions-constructs/core/lib/alb-helper.ts
index 604edddef..de97a00ab 100644
--- a/source/patterns/@aws-solutions-constructs/core/lib/alb-helper.ts
+++ b/source/patterns/@aws-solutions-constructs/core/lib/alb-helper.ts
@@ -264,8 +264,12 @@ export function CheckAlbProps(props: any) {
   }
 
   if (props.loadBalancerProps?.vpc) {
-    errorFound = true;
-    errorMessages += 'Specify any existing VPC at the construct level, not within loadBalancerProps.\n';
+    // Only reason this should exist is to enable specifying ALB network configuration. There must still
+    // be a construct VPC and  they must match
+    if ((!props.existingVpc) || (props.existingVpc.vpcId !== props.loadBalancerProps?.vpc.vpcId)) {
+      errorFound = true;
+      errorMessages += 'Any existing VPC must be defined in the construct props (props.existingVpc). A VPC specified in the loadBalancerProps must be the same VPC';
+    }
   }
 
   if (props.existingLoadBalancerObj) {


Test patch:
diff --git a/source/patterns/@aws-solutions-constructs/aws-alb-fargate/test/alb-fargate.test.ts b/source/patterns/@aws-solutions-constructs/aws-alb-fargate/test/alb-fargate.test.ts
index 3bd238d69..77ce7c93a 100644
--- a/source/patterns/@aws-solutions-constructs/aws-alb-fargate/test/alb-fargate.test.ts
+++ b/source/patterns/@aws-solutions-constructs/aws-alb-fargate/test/alb-fargate.test.ts
@@ -519,3 +519,35 @@ test('Confirm that CheckAlbProps is called', () => {
   // Assertion
   expect(app).toThrowError('Error - Either provide loadBalancerProps or existingLoadBalancerObj, but not both.\n');
 });
+
+test('Test sending VPC in loadBalancerProps error', () => {
+  const props = {
+    loadBalancerProps: {
+      vpc: { val: 'placeholder' }
+    }
+  };
+
+  const app = () => {
+    defaults.CheckAlbProps(props);
+  };
+
+  expect(app).toThrowError("Any existing VPC must be defined in the construct props (props.existingVpc). A VPC specified in the loadBalancerProps must be the same VPC");
+});
+
+test('WHen providing VPC in construct and resource props, the vpcId must match', () => {
+  const fakeVpcOne = {vpcId: 'one'};
+  const fakeVpcTwo = {vpcId: 'two'};
+
+  const props = {
+    existingVpc: fakeVpcOne,
+    loadBalancerProps: {
+      vpc: fakeVpcTwo
+    }
+  };
+
+  const app = () => {
+    defaults.CheckAlbProps(props);
+  };
+
+  expect(app).toThrowError("Any existing VPC must be defined in the construct props (props.existingVpc). A VPC specified in the loadBalancerProps must be the same VPC");
+});
diff --git a/source/patterns/@aws-solutions-constructs/aws-alb-lambda/test/alb-lambda.test.ts b/source/patterns/@aws-solutions-constructs/aws-alb-lambda/test/alb-lambda.test.ts
index 71a15e0be..b2dab4028 100644
--- a/source/patterns/@aws-solutions-constructs/aws-alb-lambda/test/alb-lambda.test.ts
+++ b/source/patterns/@aws-solutions-constructs/aws-alb-lambda/test/alb-lambda.test.ts
@@ -1050,3 +1050,35 @@ test('Confirm that CheckAlbProps is called', () => {
   // Assertion
   expect(app).toThrowError('Error - Either provide loadBalancerProps or existingLoadBalancerObj, but not both.\n');
 });
+
+test('Test sending VPC in loadBalancerProps error', () => {
+  const props = {
+    loadBalancerProps: {
+      vpc: { val: 'placeholder' }
+    }
+  };
+
+  const app = () => {
+    defaults.CheckAlbProps(props);
+  };
+
+  expect(app).toThrowError("Any existing VPC must be defined in the construct props (props.existingVpc). A VPC specified in the loadBalancerProps must be the same VPC");
+});
+
+test('WHen providing VPC in construct and resource props, the vpcId must match', () => {
+  const fakeVpcOne = {vpcId: 'one'};
+  const fakeVpcTwo = {vpcId: 'two'};
+
+  const props = {
+    existingVpc: fakeVpcOne,
+    loadBalancerProps: {
+      vpc: fakeVpcTwo
+    }
+  };
+
+  const app = () => {
+    defaults.CheckAlbProps(props);
+  };
+
+  expect(app).toThrowError("Any existing VPC must be defined in the construct props (props.existingVpc). A VPC specified in the loadBalancerProps must be the same VPC");
+});
diff --git a/source/patterns/@aws-solutions-constructs/core/test/alb-helper.test.ts b/source/patterns/@aws-solutions-constructs/core/test/alb-helper.test.ts
index 4ed43e6fb..bdc7f34ee 100644
--- a/source/patterns/@aws-solutions-constructs/core/test/alb-helper.test.ts
+++ b/source/patterns/@aws-solutions-constructs/core/test/alb-helper.test.ts
@@ -17,7 +17,7 @@ import * as lambda from "aws-cdk-lib/aws-lambda";
 import * as defaults from '../index';
 import * as ec2 from 'aws-cdk-lib/aws-ec2';
 import * as ecs from 'aws-cdk-lib/aws-ecs';
-import { Template } from 'aws-cdk-lib/assertions';
+import { Match, Template } from 'aws-cdk-lib/assertions';
 
 test('Test ObtainAlb with existing ALB', () => {
   const stack = new Stack();
@@ -59,7 +59,6 @@ test('Test ObtainAlb for new ALB with provided props', () => {
     publicApi: true,
     loadBalancerProps: {
       loadBalancerName: 'new-loadbalancer',
-      vpc,
       internetFacing: true
     }
   });
@@ -93,6 +92,43 @@ test('Test ObtainAlb for new ALB with default props', () => {
   });
 });
 
+test('Test ObtainAlb with specific subnets', () => {
+  const stack = new Stack(undefined, undefined, {
+    env: { account: "123456789012", region: 'us-east-1' },
+  });
+  // Build VPC
+  const vpc = defaults.buildVpc(stack, {
+    defaultVpcProps: defaults.DefaultPublicPrivateVpcProps(),
+    userVpcProps: {
+      availabilityZones: [
+        "us-east-1a",
+        "us-east-1b",
+        "us-east-1c",
+        "us-east-1d",
+        "us-east-1e",
+      ],
+    }
+  });
+
+  defaults.ObtainAlb(stack, 'test', {
+    vpc,
+    publicApi: true,
+    loadBalancerProps: {
+      vpc,
+      vpcSubnets: { availabilityZones: ["us-east-1b", "us-east-1d"] }
+    }
+  });
+
+  const template = Template.fromStack(stack);
+
+  template.hasResourceProperties('AWS::ElasticLoadBalancingV2::LoadBalancer', {
+    Subnets: [
+      Match.anyValue(),
+      Match.anyValue(),
+    ]
+  });
+});
+
 test('Test with custom logging bucket props', () => {
   // Creating ALB logging requires a region and account (but
   // these can be fake in unit tests)
@@ -567,7 +603,6 @@ test('Test sending loadBalancerProps and existingLoadBalancerObj is an error', (
     existingLoadBalancerObj: existingLoadBalancer,
     loadBalancerProps: {
       loadBalancerName: 'new-loadbalancer',
-      vpc,
       internetFacing: true
     }
   };
@@ -608,7 +643,25 @@ test('Test sending VPC in loadBalancerProps error', () => {
     defaults.CheckAlbProps(props);
   };
 
-  expect(app).toThrowError('Specify any existing VPC at the construct level, not within loadBalancerProps.\n');
+  expect(app).toThrowError("Any existing VPC must be defined in the construct props (props.existingVpc). A VPC specified in the loadBalancerProps must be the same VPC");
+});
+
+test('WHen providing VPC in construct and resource props, the vpcId must match', () => {
+  const fakeVpcOne = {vpcId: 'one'};
+  const fakeVpcTwo = {vpcId: 'two'};
+
+  const props = {
+    existingVpc: fakeVpcOne,
+    loadBalancerProps: {
+      vpc: fakeVpcTwo
+    }
+  };
+
+  const app = () => {
+    defaults.CheckAlbProps(props);
+  };
+
+  expect(app).toThrowError("Any existing VPC must be defined in the construct props (props.existingVpc). A VPC specified in the loadBalancerProps must be the same VPC");
 });
 
 function CreateTestLoadBalancer(stack: Stack, vpc: ec2.IVpc): elb.ApplicationLoadBalancer {